Skip to content

Commit cf1b8ef

Browse files
committed
Merge main into docs/improve-vaults
2 parents ea73757 + e5723a8 commit cf1b8ef

60 files changed

Lines changed: 1322 additions & 137 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/docs/glossary.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ In 90% LTV Vaults, the process of closing an unhealthy osToken position that exc
5555
### LTV (Loan-to-Value)
5656
The ratio of minted osToken value to staked collateral value, expressed as a percentage. Standard Vaults have 90% LTV, while DAO-approved Vaults can have up to 99.99% LTV (osETH) or 99.95% LTV (osGNO). This determines the maximum amount of osToken that can be minted against staked assets.
5757

58-
### Meta Vault
58+
### MetaVault
5959
A Vault type that does not register validators directly, instead delegating accumulated assets to sub-Vaults managed by the Vault Admin. Deposits are distributed across underlying sub-Vaults (up to 50 maximum) according to curator-defined allocation logic.
6060

6161
### Minting

docs/docs/vaults/vault-types.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ A MetaVault is a specialized Vault that doesn't run validators itself. Instead,
3939

4040
### Use Cases
4141

42-
[Anyone can deploy a MetaVault permissionlessly](https://blog.stakewise.io/productUpdate/meta-vaults-in-different-flavors-now-open-to-everyone) through the [`EthMetaVaultFactory`](https://etherscan.io/address/0x6107dB0bdd84023228E0aB11099190E88B073c1D#code) contract. Two common setups:
42+
Any third party can deploy a MetaVault permissionlessly, which opens up modular, layered staking strategies:
4343

44-
- **Node operator diversification.** A Vault operator runs one MetaVault on top of several sub-vaults, spreading stake to simplify management and reduce single-operator risk.
45-
- **Institutional setups.** A top-level MetaVault routes deposits into client-specific MetaVaults — each with its own fee and branding — while a single validator fleet sits underneath them all.
44+
- [Diversified staking ↗](https://blog.stakewise.io/guide/diversified-staking-with-metavaults) — spread stake across multiple operators through a single MetaVault, reducing single-operator exposure, optimizing fees, and giving you one place to deposit, withdraw, and manage your osETH position.
45+
- [Dedicated MetaVaults for clients ↗](https://blog.stakewise.io/guide/setting-up-per-client-metavaults) — create a separate MetaVault for each client while routing all deposits into a shared Regular Vault. This keeps client funds isolated, supports per-client fees, delivers higher and more stable APY, and simplifies operations by managing a single validator fleet.
4646

4747
### How MetaVaults Work
4848

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
id: addSubVault
3+
slug: /sdk/api/vault/transactions/addsubvault
4+
description: Use the StakeWise SDK addSubVault method to add a sub-vault to a StakeWise V3 meta vault registry on Mainnet, Hoodi, or Gnosis. Called by the meta vault curator to extend the registry of underlying vaults receiving routed deposits.
5+
---
6+
7+
#### Description:
8+
9+
Adds a new sub-vault to a meta vault's registry. Called by the meta vault admin (curator).
10+
11+
#### Prerequisites before calling addSubVault
12+
13+
The contract reverts if the sub-vault is unreachable for the meta vault. Pre-check on the frontend so the user gets an informative error instead of a generic revert:
14+
15+
- If the sub-vault is **private** (`isPrivate: true` from `sdk.vault.getVault`), the meta vault address must be in the sub-vault's whitelist. Read the whitelist with `sdk.vault.getWhitelist({ vaultAddress: subVaultAddress })` and skip or warn the user if the meta vault is not present.
16+
- If the sub-vault has a **blocklist** (`isBlocklist: true`), the meta vault address must not be on the blocklist. Read it with `sdk.vault.getBlocklist({ vaultAddress: subVaultAddress })`.
17+
- The sub-vault itself must not be a meta vault (`isMetaVault: false`). Nesting meta vaults is not supported.
18+
- The sub-vault should not already be in the meta vault's registry. Check the current list with `sdk.vault.getSubVaults({ vaultAddress })`.
19+
20+
```ts
21+
const subVault = await sdk.vault.getVault({ vaultAddress: subVaultAddress })
22+
23+
if (subVault.isMetaVault) {
24+
throw new Error('Cannot add a meta vault as a sub-vault')
25+
}
26+
27+
if (subVault.isPrivate) {
28+
const whitelist = await sdk.vault.getWhitelist({ vaultAddress: subVaultAddress, limit: 1000, skip: 0 })
29+
const isWhitelisted = whitelist.some(({ address }) => address.toLowerCase() === metaVaultAddress.toLowerCase())
30+
31+
if (!isWhitelisted) {
32+
throw new Error('Meta vault must be on the sub-vault whitelist before it can be added')
33+
}
34+
}
35+
36+
if (subVault.isBlocklist) {
37+
const blocklist = await sdk.vault.getBlocklist({ vaultAddress: subVaultAddress, limit: 1000, skip: 0 })
38+
const isBlocked = blocklist.some(({ address }) => address.toLowerCase() === metaVaultAddress.toLowerCase())
39+
40+
if (isBlocked) {
41+
throw new Error('Meta vault is on the sub-vault blocklist and cannot be added')
42+
}
43+
}
44+
```
45+
46+
#### Arguments:
47+
48+
| Name | Type | Required | Description |
49+
|----------------|----------|----------|---------------------------|
50+
| subVaultAddress | `string` | **Yes** | New sub-vault address |
51+
| userAddress | `string` | **Yes** | The user address |
52+
| vaultAddress | `string` | **Yes** | The address of the vault |
53+
54+
#### Example:
55+
56+
```ts
57+
const params = {
58+
subVaultAddress: '0x...',
59+
vaultAddress: '0x...',
60+
userAddress: '0x...',
61+
}
62+
63+
// Send transaction
64+
const hash = await sdk.vault.addSubVault(params)
65+
66+
// Wait for the transaction to be confirmed and indexed
67+
await sdk.provider.waitForTransaction(hash)
68+
await sdk.utils.waitForSubgraph({ hash })
69+
70+
// When you sign transactions on the backend (for custodians)
71+
const { data, to } = await sdk.vault.addSubVault.encode(params)
72+
// Get an approximate gas per transaction
73+
const gas = await sdk.vault.addSubVault.estimateGas(params)
74+
```

docs/sdk/API/01-vault/01-transactions/claimExitQueue.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ const params = {
4242

4343
// Send transaction
4444
const hash = await sdk.vault.claimExitQueue(params)
45+
46+
// Wait for the transaction to be confirmed and indexed
47+
await sdk.provider.waitForTransaction(hash)
48+
await sdk.utils.waitForSubgraph({ hash })
49+
4550
// When you sign transactions on the backend (for custodians)
4651
const { data, to } = await sdk.vault.claimExitQueue.encode(params)
4752
// Get an approximate gas per transaction

docs/sdk/API/01-vault/01-transactions/createVault.md

Lines changed: 145 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,38 @@ description: Use the StakeWise SDK createVault method to deploy a new staking va
66

77
#### Description:
88

9-
Create a vault. When the transaction is executed, one gwei of the deposit token must be stored in the vault to avoid [inflation attack](https://blog.openzeppelin.com/a-novel-defense-against-erc4626-inflation-attacks).
10-
Pay attention to chains where the deposit token is not a native token such as Gnosis.
11-
On these chains before creating the vault, ensure that you call the `approve` function on the deposit token contract,
12-
allowing the vault factory address to spend one gwei.
13-
You can retrieve the vault factory contract using the helper function: `sdk.getVaultFactory({ vaultType: params.type, isErc20: params.isErc20 })`.
9+
How to deploy a StakeWise V3 vault: call `sdk.vault.create({ userAddress, type, ... })` on a write-capable SDK (browser wallet or backend signer). The simplest case is `VaultType.Default` with no `vaultToken`, no `capacity`, no `keysManagerFee` - the SDK applies sensible defaults and the returned hash points at a fully working open vault.
10+
11+
The optional `vaultToken: { name, symbol }` argument toggles between an **ERC20 vault** (vault mints a transferable share token) and a **non-ERC20 vault** (shares tracked internally). The `type` argument selects `Default`, `Private`, `Blocklist`, `MetaVault`, or `PrivateMetaVault` access. Regular vault types combine with the ERC20 toggle into six factories; meta vault types add their own factories. The SDK picks the right factory automatically.
12+
13+
When the transaction is executed, one gwei of the deposit token must be stored in the vault to avoid [inflation attack](https://blog.openzeppelin.com/a-novel-defense-against-erc4626-inflation-attacks).
14+
On Mainnet and Hoodi the deposit token is the native asset (ETH) and the SDK attaches the 1 gwei automatically.
15+
On Gnosis the deposit token is GNO, so before calling `sdk.vault.create` you must call `approve` on GNO to allow the vault factory to spend 1 gwei. Retrieve the factory address with `sdk.vault.getVaultFactory({ vaultType: params.type, isErc20: Boolean(params.vaultToken) })`.
16+
17+
**Important**: When creating a meta vault on Gnosis, only `VaultType.MetaVault` is supported (open access). `VaultType.PrivateMetaVault` is Mainnet and Hoodi only. ERC20 share tokens (`vaultToken`) are not available for meta vaults on Gnosis. All meta vaults reject the `isOwnMevEscrow` parameter on every chain.
18+
19+
1420

1521
#### Arguments:
1622

1723
| Name | Type | Required | Description |
1824
|----------------|------------------------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
19-
| userAddress | `string` | **Yes** | The address of the user initiating the action. This address will become the vault admin |
20-
| type | `VaultType` | **No** | Allowed vault types: Default, Private and Blocklist. Available vault types can be found in the `enum VaultType` which you can be imported from the library |
21-
| vaultToken | `{ name: string, symbol: string }` | **No** | If provided, the vault will be created with its own ERC20 token |
22-
| capacity | `bigint` | **No** | If provided, should be defined in gwei. By default, capacity is `MaxUint256`; the minimum allowed capacity is `parseEther('32')` |
23-
| keysManagerFee | `number` | **No** | If provided, should be between `0` and `100`, inclusive with a maximum of two decimal digits allowed (e.g., `15.35`). By default, the fee is `0` |
24-
| isOwnMevEscrow | `boolean` | **No** | Defines whether to send block rewards to the Smoothing Pool (`false`) or keep them only to your Vault (`true`). By default, this value is `false` |
25-
| image | `string` | **No** | The vault image in base64 string format (will be uploaded to IPFS; maximum size is 1 MB) |
26-
| displayName | `string` | **No** | The vault display name (will be uploaded to IPFS; maximum size is 30 characters) |
27-
| description | `string` | **No** | The vault description (will be uploaded to IPFS; maximum size is 1000 characters) |
28-
29-
#### Example:
25+
| userAddress | `string` | **Yes** | The address of the user initiating the action. This address will become the vault admin |
26+
| type | `VaultType` | **No** | One of: `Default`, `Private`, `Blocklist`, `MetaVault`, `PrivateMetaVault`. Imported from the library: `import { VaultType } from '@stakewise/v3-sdk'`. Use `MetaVault` / `PrivateMetaVault` to deploy a meta vault that holds a registry of sub-vaults; `PrivateMetaVault` is Mainnet/Hoodi only. |
27+
| vaultToken | `{ name: string, symbol: string }` | **No** | If provided, the vault will be created with its own ERC20 token |
28+
| capacity | `bigint` | **No** | If provided, should be defined in gwei. By default, capacity is `MaxUint256`; the minimum allowed capacity is `parseEther('32')`|
29+
| keysManagerFee | `number` | **No** | If provided, should be between `0` and `100`, inclusive with a maximum of two decimal digits allowed (e.g., `15.35`). By default, the fee is `0`|
30+
| isOwnMevEscrow | `boolean` | **No** | Defines whether to send block rewards to the Smoothing Pool (`false`) or keep them only to your Vault (`true`). By default, this value is `false`. **Note**: This parameter is not supported for metavaults|
31+
| image | `string` | **No** | The vault image in base64 string format (will be uploaded to IPFS; maximum size is 1 MB)|
32+
| displayName | `string` | **No** | The vault display name (will be uploaded to IPFS; maximum size is 30 characters)|
33+
| description | `string` | **No** | The vault description (will be uploaded to IPFS; maximum size is 1000 characters)|
34+
35+
#### Example: ERC20 vault on Mainnet
3036

3137
```ts
38+
import { MaxUint256 } from 'ethers'
39+
import { VaultType } from '@stakewise/v3-sdk'
40+
3241
const params = {
3342
userAddress: '0x...',
3443
type: VaultType.Default,
@@ -44,11 +53,127 @@ const params = {
4453
description: 'Example description',
4554
}
4655

47-
// Transaction example
48-
// Send transaction to create a vault
56+
// Send transaction
4957
const hash = await sdk.vault.create(params)
58+
59+
// Wait for the transaction to be confirmed and indexed
60+
await sdk.provider.waitForTransaction(hash)
61+
await sdk.utils.waitForSubgraph({ hash })
62+
5063
// When you sign transactions on the backend (for custodians)
51-
const { data, to, value } = await sdk.vault.deposit.encode(params)
64+
const { data, to, value } = await sdk.vault.create.encode(params)
5265
// Get an approximate gas per transaction
53-
const gas = await sdk.vault.deposit.estimateGas(params)
66+
const gas = await sdk.vault.create.estimateGas(params)
67+
```
68+
69+
#### Example: non-ERC20 Private vault
70+
71+
Omit `vaultToken` to deploy a non-ERC20 vault and switch `type` to restrict access:
72+
73+
```ts
74+
import { VaultType } from '@stakewise/v3-sdk'
75+
76+
const hash = await sdk.vault.create({
77+
userAddress: '0x...',
78+
type: VaultType.Private,
79+
isOwnMevEscrow: false,
80+
keysManagerFee: 5,
81+
displayName: 'Private vault',
82+
})
83+
84+
// Wait for the transaction to be confirmed and indexed
85+
await sdk.provider.waitForTransaction(hash)
86+
await sdk.utils.waitForSubgraph({ hash })
87+
```
88+
89+
#### Example: ERC20 vault on Gnosis (with GNO approve)
90+
91+
On Gnosis the 1 gwei security deposit is GNO, not native xDAI. Approve the vault factory before `sdk.vault.create`:
92+
93+
```ts
94+
import { MaxUint256 } from 'ethers'
95+
import { VaultType } from '@stakewise/v3-sdk'
96+
97+
const userAddress = '0x...'
98+
const isErc20 = true
99+
100+
const factoryAddress = await sdk.vault.getVaultFactory({
101+
vaultType: VaultType.Default,
102+
isErc20,
103+
})
104+
105+
const depositTokenAddress = sdk.config.addresses.tokens.depositToken
106+
const gno = sdk.contracts.helpers.createErc20(depositTokenAddress)
107+
const signer = await sdk.provider.getSigner(userAddress)
108+
109+
const approveTx = await gno.connect(signer).approve(factoryAddress, MaxUint256)
110+
await approveTx.wait()
111+
112+
const hash = await sdk.vault.create({
113+
userAddress,
114+
type: VaultType.Default,
115+
vaultToken: { name: 'Gnosis Vault Share', symbol: 'GVS' },
116+
isOwnMevEscrow: false,
117+
keysManagerFee: 5,
118+
displayName: 'Gnosis ERC20 vault',
119+
})
120+
121+
// Wait for the transaction to be confirmed and indexed
122+
await sdk.provider.waitForTransaction(hash)
123+
await sdk.utils.waitForSubgraph({ hash })
124+
```
125+
126+
#### Example: meta vault on Gnosis
127+
128+
On Gnosis a meta vault uses `VaultType.MetaVault` (open) - `PrivateMetaVault` is not supported. Meta vaults do not accept `vaultToken` (no ERC20 share token on Gnosis meta vaults) and never accept `isOwnMevEscrow`. The sub-vaults underneath the meta vault keep their own MEV configuration.
129+
130+
```ts
131+
import { MaxUint256 } from 'ethers'
132+
import { VaultType } from '@stakewise/v3-sdk'
133+
134+
const userAddress = '0x...'
135+
136+
const factoryAddress = await sdk.vault.getVaultFactory({
137+
vaultType: VaultType.MetaVault,
138+
isErc20: false,
139+
})
140+
141+
const depositTokenAddress = sdk.config.addresses.tokens.depositToken
142+
const gno = sdk.contracts.helpers.createErc20(depositTokenAddress)
143+
const signer = await sdk.provider.getSigner(userAddress)
144+
145+
const approveTx = await gno.connect(signer).approve(factoryAddress, MaxUint256)
146+
await approveTx.wait()
147+
148+
const hash = await sdk.vault.create({
149+
userAddress,
150+
type: VaultType.MetaVault,
151+
keysManagerFee: 5,
152+
displayName: 'Gnosis Meta Vault',
153+
})
154+
155+
// Wait for the transaction to be confirmed and indexed
156+
await sdk.provider.waitForTransaction(hash)
157+
await sdk.utils.waitForSubgraph({ hash })
158+
```
159+
160+
After deploying the meta vault, the curator (the `userAddress` above) populates its registry by calling `sdk.vault.addSubVault({ vaultAddress: metaVaultAddress, subVaultAddress, userAddress })` for each sub-vault.
161+
162+
#### Example: private meta vault on Mainnet
163+
164+
On Mainnet and Hoodi, `VaultType.PrivateMetaVault` is supported. Mainnet does not require an approve step (the 1 gwei security deposit is native ETH and the SDK attaches it automatically):
165+
166+
```ts
167+
import { VaultType } from '@stakewise/v3-sdk'
168+
169+
const hash = await sdk.vault.create({
170+
userAddress: '0x...',
171+
type: VaultType.PrivateMetaVault,
172+
keysManagerFee: 5,
173+
displayName: 'Private Meta Vault',
174+
})
175+
176+
// Wait for the transaction to be confirmed and indexed
177+
await sdk.provider.waitForTransaction(hash)
178+
await sdk.utils.waitForSubgraph({ hash })
54179
```

docs/sdk/API/01-vault/01-transactions/deposit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ const params = {
2727

2828
// Send transaction
2929
const hash = await sdk.vault.deposit(params)
30+
31+
// Wait for the transaction to be confirmed and indexed
32+
await sdk.provider.waitForTransaction(hash)
33+
await sdk.utils.waitForSubgraph({ hash })
34+
3035
// When you sign transactions on the backend (for custodians)
3136
const { data, to, value } = await sdk.vault.deposit.encode(params)
3237
// Get an approximate gas per transaction
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
id: ejectSubVault
3+
slug: /sdk/api/vault/transactions/ejectsubvault
4+
description: Use the StakeWise SDK ejectSubVault method to remove an active sub-vault from a StakeWise V3 meta vault registry. Called by the meta vault curator to drop an in-use sub-vault from the registry.
5+
---
6+
7+
#### Description:
8+
9+
Ejecting a sub-vault from the vault registry.
10+
11+
#### Arguments:
12+
13+
| Name | Type | Required | Description |
14+
|----------------|----------|----------|---------------------------|
15+
| subVaultAddress | `string` | **Yes** | The sub-vault address to eject |
16+
| userAddress | `string` | **Yes** | The user address |
17+
| vaultAddress | `string` | **Yes** | The address of the vault |
18+
19+
#### Example:
20+
21+
```ts
22+
const params = {
23+
subVaultAddress: '0x...',
24+
vaultAddress: '0x...',
25+
userAddress: '0x...',
26+
}
27+
28+
// Send transaction
29+
const hash = await sdk.vault.ejectSubVault(params)
30+
31+
// Wait for the transaction to be confirmed and indexed
32+
await sdk.provider.waitForTransaction(hash)
33+
await sdk.utils.waitForSubgraph({ hash })
34+
35+
// When you sign transactions on the backend (for custodians)
36+
const { data, to } = await sdk.vault.ejectSubVault.encode(params)
37+
// Get an approximate gas per transaction
38+
const gas = await sdk.vault.ejectSubVault.estimateGas(params)
39+
```

0 commit comments

Comments
 (0)