diff --git a/docs/sdk/API/01-vault/01-transactions/addSubVault.md b/docs/sdk/API/01-vault/01-transactions/addSubVault.md
new file mode 100644
index 00000000..442adbdd
--- /dev/null
+++ b/docs/sdk/API/01-vault/01-transactions/addSubVault.md
@@ -0,0 +1,74 @@
+---
+id: addSubVault
+slug: /sdk/api/vault/transactions/addsubvault
+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.
+---
+
+#### Description:
+
+Adds a new sub-vault to a meta vault's registry. Called by the meta vault admin (curator).
+
+#### Prerequisites before calling addSubVault
+
+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:
+
+- 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.
+- 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 })`.
+- The sub-vault itself must not be a meta vault (`isMetaVault: false`). Nesting meta vaults is not supported.
+- The sub-vault should not already be in the meta vault's registry. Check the current list with `sdk.vault.getSubVaults({ vaultAddress })`.
+
+```ts
+const subVault = await sdk.vault.getVault({ vaultAddress: subVaultAddress })
+
+if (subVault.isMetaVault) {
+ throw new Error('Cannot add a meta vault as a sub-vault')
+}
+
+if (subVault.isPrivate) {
+ const whitelist = await sdk.vault.getWhitelist({ vaultAddress: subVaultAddress, limit: 1000, skip: 0 })
+ const isWhitelisted = whitelist.some(({ address }) => address.toLowerCase() === metaVaultAddress.toLowerCase())
+
+ if (!isWhitelisted) {
+ throw new Error('Meta vault must be on the sub-vault whitelist before it can be added')
+ }
+}
+
+if (subVault.isBlocklist) {
+ const blocklist = await sdk.vault.getBlocklist({ vaultAddress: subVaultAddress, limit: 1000, skip: 0 })
+ const isBlocked = blocklist.some(({ address }) => address.toLowerCase() === metaVaultAddress.toLowerCase())
+
+ if (isBlocked) {
+ throw new Error('Meta vault is on the sub-vault blocklist and cannot be added')
+ }
+}
+```
+
+#### Arguments:
+
+| Name | Type | Required | Description |
+|----------------|----------|----------|---------------------------|
+| subVaultAddress | `string` | **Yes** | New sub-vault address |
+| userAddress | `string` | **Yes** | The user address |
+| vaultAddress | `string` | **Yes** | The address of the vault |
+
+#### Example:
+
+```ts
+const params = {
+ subVaultAddress: '0x...',
+ vaultAddress: '0x...',
+ userAddress: '0x...',
+}
+
+// Send transaction
+const hash = await sdk.vault.addSubVault(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
+// When you sign transactions on the backend (for custodians)
+const { data, to } = await sdk.vault.addSubVault.encode(params)
+// Get an approximate gas per transaction
+const gas = await sdk.vault.addSubVault.estimateGas(params)
+```
diff --git a/docs/sdk/API/01-vault/01-transactions/claimExitQueue.md b/docs/sdk/API/01-vault/01-transactions/claimExitQueue.md
index 4774f71d..e5b2ea20 100644
--- a/docs/sdk/API/01-vault/01-transactions/claimExitQueue.md
+++ b/docs/sdk/API/01-vault/01-transactions/claimExitQueue.md
@@ -42,6 +42,11 @@ const params = {
// Send transaction
const hash = await sdk.vault.claimExitQueue(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.vault.claimExitQueue.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/01-vault/01-transactions/createVault.md b/docs/sdk/API/01-vault/01-transactions/createVault.md
index 86d69700..aecdb543 100644
--- a/docs/sdk/API/01-vault/01-transactions/createVault.md
+++ b/docs/sdk/API/01-vault/01-transactions/createVault.md
@@ -6,29 +6,38 @@ description: Use the StakeWise SDK createVault method to deploy a new staking va
#### Description:
-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).
-Pay attention to chains where the deposit token is not a native token such as Gnosis.
-On these chains before creating the vault, ensure that you call the `approve` function on the deposit token contract,
-allowing the vault factory address to spend one gwei.
-You can retrieve the vault factory contract using the helper function: `sdk.getVaultFactory({ vaultType: params.type, isErc20: params.isErc20 })`.
+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.
+
+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.
+
+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).
+On Mainnet and Hoodi the deposit token is the native asset (ETH) and the SDK attaches the 1 gwei automatically.
+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) })`.
+
+**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.
+
+
#### Arguments:
| Name | Type | Required | Description |
|----------------|------------------------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| userAddress | `string` | **Yes** | The address of the user initiating the action. This address will become the vault admin |
-| 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 |
-| vaultToken | `{ name: string, symbol: string }` | **No** | If provided, the vault will be created with its own ERC20 token |
-| capacity | `bigint` | **No** | If provided, should be defined in gwei. By default, capacity is `MaxUint256`; the minimum allowed capacity is `parseEther('32')` |
-| 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` |
-| 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` |
-| image | `string` | **No** | The vault image in base64 string format (will be uploaded to IPFS; maximum size is 1 MB) |
-| displayName | `string` | **No** | The vault display name (will be uploaded to IPFS; maximum size is 30 characters) |
-| description | `string` | **No** | The vault description (will be uploaded to IPFS; maximum size is 1000 characters) |
-
-#### Example:
+| userAddress | `string` | **Yes** | The address of the user initiating the action. This address will become the vault admin |
+| 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. |
+| vaultToken | `{ name: string, symbol: string }` | **No** | If provided, the vault will be created with its own ERC20 token |
+| capacity | `bigint` | **No** | If provided, should be defined in gwei. By default, capacity is `MaxUint256`; the minimum allowed capacity is `parseEther('32')`|
+| 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`|
+| 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|
+| image | `string` | **No** | The vault image in base64 string format (will be uploaded to IPFS; maximum size is 1 MB)|
+| displayName | `string` | **No** | The vault display name (will be uploaded to IPFS; maximum size is 30 characters)|
+| description | `string` | **No** | The vault description (will be uploaded to IPFS; maximum size is 1000 characters)|
+
+#### Example: ERC20 vault on Mainnet
```ts
+import { MaxUint256 } from 'ethers'
+import { VaultType } from '@stakewise/v3-sdk'
+
const params = {
userAddress: '0x...',
type: VaultType.Default,
@@ -44,11 +53,127 @@ const params = {
description: 'Example description',
}
-// Transaction example
-// Send transaction to create a vault
+// Send transaction
const hash = await sdk.vault.create(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
-const { data, to, value } = await sdk.vault.deposit.encode(params)
+const { data, to, value } = await sdk.vault.create.encode(params)
// Get an approximate gas per transaction
-const gas = await sdk.vault.deposit.estimateGas(params)
+const gas = await sdk.vault.create.estimateGas(params)
+```
+
+#### Example: non-ERC20 Private vault
+
+Omit `vaultToken` to deploy a non-ERC20 vault and switch `type` to restrict access:
+
+```ts
+import { VaultType } from '@stakewise/v3-sdk'
+
+const hash = await sdk.vault.create({
+ userAddress: '0x...',
+ type: VaultType.Private,
+ isOwnMevEscrow: false,
+ keysManagerFee: 5,
+ displayName: 'Private vault',
+})
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+```
+
+#### Example: ERC20 vault on Gnosis (with GNO approve)
+
+On Gnosis the 1 gwei security deposit is GNO, not native xDAI. Approve the vault factory before `sdk.vault.create`:
+
+```ts
+import { MaxUint256 } from 'ethers'
+import { VaultType } from '@stakewise/v3-sdk'
+
+const userAddress = '0x...'
+const isErc20 = true
+
+const factoryAddress = await sdk.vault.getVaultFactory({
+ vaultType: VaultType.Default,
+ isErc20,
+})
+
+const depositTokenAddress = sdk.config.addresses.tokens.depositToken
+const gno = sdk.contracts.helpers.createErc20(depositTokenAddress)
+const signer = await sdk.provider.getSigner(userAddress)
+
+const approveTx = await gno.connect(signer).approve(factoryAddress, MaxUint256)
+await approveTx.wait()
+
+const hash = await sdk.vault.create({
+ userAddress,
+ type: VaultType.Default,
+ vaultToken: { name: 'Gnosis Vault Share', symbol: 'GVS' },
+ isOwnMevEscrow: false,
+ keysManagerFee: 5,
+ displayName: 'Gnosis ERC20 vault',
+})
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+```
+
+#### Example: meta vault on Gnosis
+
+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.
+
+```ts
+import { MaxUint256 } from 'ethers'
+import { VaultType } from '@stakewise/v3-sdk'
+
+const userAddress = '0x...'
+
+const factoryAddress = await sdk.vault.getVaultFactory({
+ vaultType: VaultType.MetaVault,
+ isErc20: false,
+})
+
+const depositTokenAddress = sdk.config.addresses.tokens.depositToken
+const gno = sdk.contracts.helpers.createErc20(depositTokenAddress)
+const signer = await sdk.provider.getSigner(userAddress)
+
+const approveTx = await gno.connect(signer).approve(factoryAddress, MaxUint256)
+await approveTx.wait()
+
+const hash = await sdk.vault.create({
+ userAddress,
+ type: VaultType.MetaVault,
+ keysManagerFee: 5,
+ displayName: 'Gnosis Meta Vault',
+})
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+```
+
+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.
+
+#### Example: private meta vault on Mainnet
+
+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):
+
+```ts
+import { VaultType } from '@stakewise/v3-sdk'
+
+const hash = await sdk.vault.create({
+ userAddress: '0x...',
+ type: VaultType.PrivateMetaVault,
+ keysManagerFee: 5,
+ displayName: 'Private Meta Vault',
+})
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
```
diff --git a/docs/sdk/API/01-vault/01-transactions/deposit.md b/docs/sdk/API/01-vault/01-transactions/deposit.md
index f33bda5c..d48c6aaf 100644
--- a/docs/sdk/API/01-vault/01-transactions/deposit.md
+++ b/docs/sdk/API/01-vault/01-transactions/deposit.md
@@ -27,6 +27,11 @@ const params = {
// Send transaction
const hash = await sdk.vault.deposit(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to, value } = await sdk.vault.deposit.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/01-vault/01-transactions/ejectSubVault.md b/docs/sdk/API/01-vault/01-transactions/ejectSubVault.md
new file mode 100644
index 00000000..f609f09e
--- /dev/null
+++ b/docs/sdk/API/01-vault/01-transactions/ejectSubVault.md
@@ -0,0 +1,39 @@
+---
+id: ejectSubVault
+slug: /sdk/api/vault/transactions/ejectsubvault
+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.
+---
+
+#### Description:
+
+Ejecting a sub-vault from the vault registry.
+
+#### Arguments:
+
+| Name | Type | Required | Description |
+|----------------|----------|----------|---------------------------|
+| subVaultAddress | `string` | **Yes** | The sub-vault address to eject |
+| userAddress | `string` | **Yes** | The user address |
+| vaultAddress | `string` | **Yes** | The address of the vault |
+
+#### Example:
+
+```ts
+const params = {
+ subVaultAddress: '0x...',
+ vaultAddress: '0x...',
+ userAddress: '0x...',
+}
+
+// Send transaction
+const hash = await sdk.vault.ejectSubVault(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
+// When you sign transactions on the backend (for custodians)
+const { data, to } = await sdk.vault.ejectSubVault.encode(params)
+// Get an approximate gas per transaction
+const gas = await sdk.vault.ejectSubVault.estimateGas(params)
+```
diff --git a/docs/sdk/API/01-vault/01-transactions/operate.md b/docs/sdk/API/01-vault/01-transactions/operate.md
index b8a03fa3..0b651416 100644
--- a/docs/sdk/API/01-vault/01-transactions/operate.md
+++ b/docs/sdk/API/01-vault/01-transactions/operate.md
@@ -8,6 +8,12 @@ description: Use the StakeWise SDK operate method to update vault settings such
Updates the vault by authorized personnel such as the vault admin, whitelistManager, blocklist manager, validators manager.
+#### Constraints
+
+- **One access mode per call.** The vault is either Private (whitelist) or Blocklist, never both. Passing `whitelist` / `whitelistManager` together with `blocklist` / `blocklistManager` in the same call throws an error before sending the transaction. Update one mode at a time.
+- **700-address chunk limit.** `whitelist` and `blocklist` arrays are capped at 700 entries per call. Larger lists must be split across multiple sequential transactions; the SDK throws before sending if the limit is exceeded.
+- **No vault-type pre-check.** The SDK does not verify that the target vault matches the access mode you are updating. Calling `whitelist` updates on a Default vault reverts at execution. Pre-check the vault type with `sdk.vault.getVault({ vaultAddress })` (`isPrivate`, `isBlocklist`) before calling `operate`.
+
#### Arguments:
| Name | Type | Required | Access | Description |
@@ -25,7 +31,6 @@ Updates the vault by authorized personnel such as the vault admin, whitelistMana
| vaultAddress | `string` | **Yes** | - | The address of the vault |
| admin | `string` | **No** | - | Changing the vault administrator |
| feePercent | `number` | **No** | Admin | Changing fee percent charged by the vault |
-
#### Example:
```ts
@@ -88,6 +93,11 @@ const blocklistParams = {
// Send transaction
const hash = await sdk.vault.operate(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.vault.operate.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/01-vault/01-transactions/rejectSubVault.md b/docs/sdk/API/01-vault/01-transactions/rejectSubVault.md
new file mode 100644
index 00000000..416c053f
--- /dev/null
+++ b/docs/sdk/API/01-vault/01-transactions/rejectSubVault.md
@@ -0,0 +1,39 @@
+---
+id: rejectSubVault
+slug: /sdk/api/vault/transactions/rejectsubvault
+description: Use the StakeWise SDK rejectSubVault method to reject a proposed sub-vault from a StakeWise V3 meta vault registry before it becomes active. Called by the meta vault curator to discard a pending sub-vault proposal.
+---
+
+#### Description:
+
+Rejecting a sub-vault from the vault registry.
+
+#### Arguments:
+
+| Name | Type | Required | Description |
+|----------------|----------|----------|---------------------------|
+| subVaultAddress | `string` | **Yes** | The sub-vault address to reject |
+| userAddress | `string` | **Yes** | The user address |
+| vaultAddress | `string` | **Yes** | The address of the vault |
+
+#### Example:
+
+```ts
+const params = {
+ subVaultAddress: '0x...',
+ vaultAddress: '0x...',
+ userAddress: '0x...',
+}
+
+// Send transaction
+const hash = await sdk.vault.rejectSubVault(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
+// When you sign transactions on the backend (for custodians)
+const { data, to } = await sdk.vault.rejectSubVault.encode(params)
+// Get an approximate gas per transaction
+const gas = await sdk.vault.rejectSubVault.estimateGas(params)
+```
diff --git a/docs/sdk/API/01-vault/01-transactions/setDepositDataManager.md b/docs/sdk/API/01-vault/01-transactions/setDepositDataManager.md
index 3ecdf0c4..6e913e52 100644
--- a/docs/sdk/API/01-vault/01-transactions/setDepositDataManager.md
+++ b/docs/sdk/API/01-vault/01-transactions/setDepositDataManager.md
@@ -27,6 +27,11 @@ const params = {
// Send transaction
const hash = await sdk.vault.setDepositDataManager(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.vault.setDepositDataManager.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/01-vault/01-transactions/setDepositDataRoot.md b/docs/sdk/API/01-vault/01-transactions/setDepositDataRoot.md
index c389a8df..3deb43e2 100644
--- a/docs/sdk/API/01-vault/01-transactions/setDepositDataRoot.md
+++ b/docs/sdk/API/01-vault/01-transactions/setDepositDataRoot.md
@@ -27,6 +27,11 @@ const params = {
// Send transaction
const hash = await sdk.vault.setDepositDataRoot(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.vault.setDepositDataRoot.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/01-vault/01-transactions/updateState.md b/docs/sdk/API/01-vault/01-transactions/updateState.md
new file mode 100644
index 00000000..8072a0d0
--- /dev/null
+++ b/docs/sdk/API/01-vault/01-transactions/updateState.md
@@ -0,0 +1,42 @@
+---
+id: updateState
+slug: /sdk/api/vault/transactions/updatestate
+description: Use the StakeWise SDK updateState method to update the on-chain state of a StakeWise V3 vault before reading state-dependent data or sending a follow-up transaction. Returns an empty hash when the vault is already up to date.
+---
+
+#### Description:
+
+Update the state of a vault if required. This is used to update the state of a vault before a transaction has been executed.
+
+#### Arguments:
+
+| Name | Type | Required | Description |
+|----------------|----------|----------|---------------------------|
+| userAddress | `string` | **Yes** | The user address |
+| vaultAddress | `string` | **Yes** | The address of the vault |
+
+#### Example:
+
+```ts
+const params = {
+ vaultAddress: '0x...',
+ userAddress: '0x...',
+}
+
+// Send transaction
+// Will return empty string if updateState is not required
+const hash = await sdk.vault.updateState(params)
+
+if (hash) {
+ // Wait for the transaction to be confirmed and indexed
+ await sdk.provider.waitForTransaction(hash)
+ await sdk.utils.waitForSubgraph({ hash })
+}
+
+// When you sign transactions on the backend (for custodians)
+// Will return empty object if updateState is not required
+const { data, to } = await sdk.vault.updateState.encode(params)
+// Get an approximate gas per transaction
+// Will return 0n if updateState is not required
+const gas = await sdk.vault.updateState.estimateGas(params)
+```
diff --git a/docs/sdk/API/01-vault/01-transactions/withdraw.md b/docs/sdk/API/01-vault/01-transactions/withdraw.md
index 01aa4453..0de3ffcb 100644
--- a/docs/sdk/API/01-vault/01-transactions/withdraw.md
+++ b/docs/sdk/API/01-vault/01-transactions/withdraw.md
@@ -62,6 +62,11 @@ const params = {
// Send transaction
const hash = await sdk.vault.withdraw(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.vault.withdraw.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/01-vault/getSubVaults.md b/docs/sdk/API/01-vault/getSubVaults.md
new file mode 100644
index 00000000..3bd891ab
--- /dev/null
+++ b/docs/sdk/API/01-vault/getSubVaults.md
@@ -0,0 +1,49 @@
+---
+id: getSubVaults
+slug: /sdk/api/vault/requests/getsubvaults
+description: Use the StakeWise SDK getSubVaults method to list sub-vaults of a StakeWise V3 meta vault with paging support. Returns per-vault APY, display name, image URL, staking and exiting assets.
+---
+
+#### Description:
+
+Returns the list of sub vaults for a given meta vault.
+
+#### Arguments:
+
+| Name | Type | Required | Description |
+|------|------|----------|-----------------------------------------------------------|
+| vaultAddress | `string` | **Yes** | The address of the meta vault |
+| limit | `number` | **Yes** | Limits the number of sub vaults returned |
+| skip | `number` | **Yes** | Skips the specified number of sub vaults |
+
+#### Returns:
+
+```ts
+type Output = Array<{
+ id: string
+ apy: string
+ imageUrl: string
+ displayName: string
+ stakingAssets: bigint
+ exitingAssets: bigint
+}>
+```
+
+| Name | Description |
+|------|-------------|
+| `id` | Address of vault |
+| `apy` | Current vault apy |
+| `imageUrl` | Link for vault logo |
+| `displayName` | Name of vault |
+| `stakingAssets` | The amount of assets staking in the sub vault |
+| `exitingAssets` | The total number of assets that are exiting the sub vault |
+
+#### Example:
+
+```ts
+await sdk.vault.getSubVaults({
+ skip: 0,
+ limit: 20,
+ vaultAddress: '0x...'
+})
+```
diff --git a/docs/sdk/API/01-vault/getVault.md b/docs/sdk/API/01-vault/getVault.md
index 21777e01..fa103e7c 100644
--- a/docs/sdk/API/01-vault/getVault.md
+++ b/docs/sdk/API/01-vault/getVault.md
@@ -28,6 +28,7 @@ type Output = {
isPrivate: boolean
isGenesis: boolean
vaultAdmin: string
+ canHarvest: boolean
totalAssets: string
performance: number
isMetaVault: boolean
@@ -35,15 +36,21 @@ type Output = {
vaultAddress: string
mevRecipient: string
queuedShares: string
+ exitingAssets: string
whitelistCount: number
lastFeePercent: number
blocklistCount: number
+ exitingTickets: string
imageUrl: string | null
isSmoothingPool: boolean
tokenName: string | null
whitelistManager: string
+ whitelistManager: string
blocklistManager: string
+ ejectingSubVault: string
+ subVaultsRegistry: string
depositDataManager: string
+ pendingMetaSubVault: string
tokenSymbol: string | null
displayName: string | null
description: string | null
@@ -84,6 +91,9 @@ type Output = {
| `tokenName` | ERC20 token name |
| `tokenSymbol` | ERC20 token symbol |
| `displayName` | Name of vault |
+| `pendingMetaSubVault` | The address of the meta vault that is pending to join as a sub vault |
+| `ejectingSubVault` | The address of the sub vault currently being ejected (for meta vaults) |
+| `canHarvest` | Defines whether the vault can harvest new rewards |
| `allocatorMaxBoostApy` | The average max boost APY earned in this vault by the allocator |
| `description` | Description of vault |
| `whitelist` | List of authorized users for deposits |
@@ -91,6 +101,9 @@ type Output = {
| `performance` | Vault performance indicator (percent) |
| `lastFeeUpdateTimestamp` | The timestamp of the last fee update |
| `lastFeePercent` | The vault last fee percent |
+| `exitingAssets` | The total number of assets that are exiting (in V2 vaults) |
+| `exitingTickets` | The total number of tickets that are exiting (in V2 vaults) |
+| `subVaultsRegistry` | The address of the SubVaultsRegistry contract (for meta vaults v4+ on Gnosis, v6+ on mainnet/hoodi) |
| `osTokenConfig` | contains the ltvPercent, which is the percentage used to calculate how much a user can mint in OsToken shares, and thresholdPercent, which is the liquidation threshold percentage used to calculate the health factor for the OsToken position |
#### Example:
@@ -98,3 +111,4 @@ type Output = {
```ts
await sdk.vault.getVault({ vaultAddress: '0x...' })
```
+# test
diff --git a/docs/sdk/API/02-boost/01-transactions/claimQueue.md b/docs/sdk/API/02-boost/01-transactions/claimQueue.md
index ff55553b..b58a1f7e 100644
--- a/docs/sdk/API/02-boost/01-transactions/claimQueue.md
+++ b/docs/sdk/API/02-boost/01-transactions/claimQueue.md
@@ -39,6 +39,11 @@ const params = {
// Send transaction
const hash = await sdk.boost.claimQueue(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to, value } = await sdk.boost.claimQueue.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/02-boost/01-transactions/lock.md b/docs/sdk/API/02-boost/01-transactions/lock.md
index 190458c5..7f598059 100644
--- a/docs/sdk/API/02-boost/01-transactions/lock.md
+++ b/docs/sdk/API/02-boost/01-transactions/lock.md
@@ -17,7 +17,7 @@ Boost your osToken apy using leverage staking
| vaultAddress | `string` | **Yes** | The address of the vault that will mint osTokens for leverage staking |
| boostAddress | `string` | **Yes** | The address of the strategy proxy using the [sdk.boost.getLeverageStrategyProxy](/sdk/api/boost/requests/getleveragestrategyproxy) method |
| referrerAddress | `string` | **No** | The address of the referrer |
-| permitParams | `PermitParams` | **No** | The permit signature is required if there isn’t enough osToken allowance for the strategy proxy contract.
**For MultiSig**
The permit signature is not necessary for Multi Sig (e.g. Safe Wallet), as it should use `sdk.contracts.mintToken.approve(boostAddress, MaxUint256)` instead of a permit call to set up osToken allowance. This will be called in the action if needed.
**For other wallets**
The permit signature is optional since it will be obtained automatically using the [utils.getPermitSignature](/sdk/api/utils/getpermitsignature) method. |
+| permitParams | `PermitParams` | **No** | The permit signature is required if there isn’t enough osToken allowance for the strategy proxy contract.
**For MultiSig**
The permit signature is not necessary for Multi Sig (e.g. Safe Wallet), as it should use `sdk.contracts.tokens.mintToken.approve(boostAddress, MaxUint256)` instead of a permit call to set up osToken allowance. This will be called in the action if needed.
**For other wallets**
The permit signature is optional since it will be obtained automatically using the [utils.getPermitSignature](/sdk/api/utils/getpermitsignature) method. |
| leverageStrategyData | `LeverageStrategyData` | **No** | Leverage strategy data from [sdk.boost.getLeverageStrategyData](/sdk/api/boost/requests/getleveragestrategydata). If not provided, it will be fetched automatically during the transaction |
```ts
@@ -54,6 +54,11 @@ const params = {
// Send transaction
const hash = await sdk.boost.lock(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
// `lockTxData` will always be returned, while `approveTxData` will only be returned for MultiSig e.g. Safe Wallet
// if there isn’t enough osToken allowance, otherwise it will be null
diff --git a/docs/sdk/API/02-boost/01-transactions/unlock.md b/docs/sdk/API/02-boost/01-transactions/unlock.md
index 9e44ab29..c5be4155 100644
--- a/docs/sdk/API/02-boost/01-transactions/unlock.md
+++ b/docs/sdk/API/02-boost/01-transactions/unlock.md
@@ -12,7 +12,7 @@ Unboost your boosted osToken
| Name | Type | Required | Description |
|----------------------|------------|----------|-----------------------------------------------------|
-| percent | `number` | **Yes** | The percent to unboost (100 at max) |
+| percent | `number` | **Yes** | The percent of the boosted position to unboost. Must be in the range `(0, 100]` - strictly greater than 0 and at most 100. The SDK throws before sending the transaction if `percent` is `0` or below, or above `100`. |
| userAddress | `string` | **Yes** | The user address |
| vaultAddress | `string` | **Yes** | The address of the vault where the osTokens boosted |
| leverageStrategyData | `LeverageStrategyData` | **No** | Leverage strategy data from [sdk.boost.getLeverageStrategyData](/sdk/api/boost/requests/getleveragestrategydata). If not provided, it will be fetched automatically during the transaction |
@@ -41,6 +41,11 @@ const params = {
// Send transaction
const hash = await sdk.boost.unlock(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
// `lockTxData` will always be returned, while `upgradeLeverageStrategyTxData` will be returned if the leverage strategy contract upgrade is required
const { lockTxData, upgradeLeverageStrategyTxData } = await sdk.boost.unlock.encode(params)
diff --git a/docs/sdk/API/02-boost/01-transactions/upgradeLeverageStrategy.md b/docs/sdk/API/02-boost/01-transactions/upgradeLeverageStrategy.md
index b5d5f509..30ff8566 100644
--- a/docs/sdk/API/02-boost/01-transactions/upgradeLeverageStrategy.md
+++ b/docs/sdk/API/02-boost/01-transactions/upgradeLeverageStrategy.md
@@ -26,6 +26,11 @@ const params = {
// Send transaction
const hash = await sdk.boost.upgradeLeverageStrategy(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to, value } = await sdk.boost.unlock.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/03-osToken/01-transactions/burn.md b/docs/sdk/API/03-osToken/01-transactions/burn.md
index 6ce87bf4..c5a1cee8 100644
--- a/docs/sdk/API/03-osToken/01-transactions/burn.md
+++ b/docs/sdk/API/03-osToken/01-transactions/burn.md
@@ -27,6 +27,11 @@ const params = {
// Send transaction
const hash = await sdk.osToken.burn(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to, value } = await sdk.osToken.burn.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/03-osToken/01-transactions/mint.md b/docs/sdk/API/03-osToken/01-transactions/mint.md
index e4322e26..062ad353 100644
--- a/docs/sdk/API/03-osToken/01-transactions/mint.md
+++ b/docs/sdk/API/03-osToken/01-transactions/mint.md
@@ -81,6 +81,11 @@ const params = {
// Send transaction
const hash = await sdk.osToken.mint(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.osToken.mint.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/04-rewardSplitter/01-transactions/claimRewards.md b/docs/sdk/API/04-rewardSplitter/01-transactions/claimRewards.md
index 5549cd4e..dc8f8e4e 100644
--- a/docs/sdk/API/04-rewardSplitter/01-transactions/claimRewards.md
+++ b/docs/sdk/API/04-rewardSplitter/01-transactions/claimRewards.md
@@ -28,6 +28,11 @@ const params = {
// Send transaction
const hash = await sdk.rewardSplitter.claimRewards(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.rewardSplitter.claimRewards.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/04-rewardSplitter/01-transactions/createRewardSplitter.md b/docs/sdk/API/04-rewardSplitter/01-transactions/createRewardSplitter.md
index 58260f1a..6336dbd1 100644
--- a/docs/sdk/API/04-rewardSplitter/01-transactions/createRewardSplitter.md
+++ b/docs/sdk/API/04-rewardSplitter/01-transactions/createRewardSplitter.md
@@ -29,6 +29,11 @@ const params = {
// Send transaction
const hash = await sdk.rewardSplitter.create(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.rewardSplitter.create.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/04-rewardSplitter/01-transactions/setClaimer.md b/docs/sdk/API/04-rewardSplitter/01-transactions/setClaimer.md
index 559318cf..cfe581d7 100644
--- a/docs/sdk/API/04-rewardSplitter/01-transactions/setClaimer.md
+++ b/docs/sdk/API/04-rewardSplitter/01-transactions/setClaimer.md
@@ -26,6 +26,11 @@ const params = {
// Send transaction
const hash = await sdk.rewardSplitter.setClaimer(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.rewardSplitter.setClaimer.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/04-rewardSplitter/01-transactions/updateFeeRecipients.md b/docs/sdk/API/04-rewardSplitter/01-transactions/updateFeeRecipients.md
index a9834d2c..afb485a1 100644
--- a/docs/sdk/API/04-rewardSplitter/01-transactions/updateFeeRecipients.md
+++ b/docs/sdk/API/04-rewardSplitter/01-transactions/updateFeeRecipients.md
@@ -55,6 +55,11 @@ const params = {
// Send transaction
const hash = await sdk.rewardSplitter.updateFeeRecipients(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.rewardSplitter.updateFeeRecipients.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/05-distributorRewards/01-transactions/claim.md b/docs/sdk/API/05-distributorRewards/01-transactions/claim.md
index 3140def0..e8e05b21 100644
--- a/docs/sdk/API/05-distributorRewards/01-transactions/claim.md
+++ b/docs/sdk/API/05-distributorRewards/01-transactions/claim.md
@@ -40,6 +40,11 @@ const params = {
// Send transaction
const hash = await sdk.distributorRewards.claim(params)
+
+// Wait for the transaction to be confirmed and indexed
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+
// When you sign transactions on the backend (for custodians)
const { data, to } = await sdk.distributorRewards.claim.encode(params)
// Get an approximate gas per transaction
diff --git a/docs/sdk/API/06-utils/waitForSubgraph.md b/docs/sdk/API/06-utils/waitForSubgraph.md
new file mode 100644
index 00000000..6d8733d1
--- /dev/null
+++ b/docs/sdk/API/06-utils/waitForSubgraph.md
@@ -0,0 +1,27 @@
+---
+id: waitForSubgraph
+slug: /sdk/api/utils/waitforsubgraph
+description: Use the StakeWise SDK waitForSubgraph utility to poll the subgraph after a write transaction until indexing catches up, so the next read returns fresh data instead of pre-tx state.
+---
+
+#### Description:
+
+Polls the subgraph until a transaction with the given hash is indexed. Required after every write operation (`deposit`, `withdraw`, `mint`, `burn`, `lock`, `unlock`, `createVault`) before refetching read methods - otherwise the subgraph returns stale data because it indexes blocks asynchronously.
+
+`sdk.provider.waitForTransaction(hash)` waits for the receipt only, not for subgraph indexing. There is typically a 1 to 5 second additional gap.
+
+#### Arguments:
+
+| Name | Type | Required | Description |
+|--------|----------|----------|---------------------------------------------|
+| `hash` | `string` | **Yes** | Transaction hash returned by the write call |
+
+#### Returns:
+
+`AbortPromise`. Call `.abort()` to stop polling on unmount or navigation.
+
+#### Example:
+
+```ts
+await sdk.utils.waitForSubgraph({ hash: '0x...' })
+```
diff --git a/docs/sdk/connecting-wallet.md b/docs/sdk/connecting-wallet.md
index 4d286ba7..6b85eecb 100644
--- a/docs/sdk/connecting-wallet.md
+++ b/docs/sdk/connecting-wallet.md
@@ -2,7 +2,7 @@
id: connecting
title: Connecting a Wallet
sidebar_position: 2
-description: Connect wallets to the StakeWise SDK using EIP-1193 providers — MetaMask, WalletConnect, Coinbase, Safe, Binance, and Wagmi integration.
+description: Connect wallets to the StakeWise SDK using EIP-1193 providers - MetaMask, WalletConnect, Coinbase, Safe, Binance, and Wagmi integration.
---
# Connecting a Wallet
@@ -29,7 +29,7 @@ interface Eip1193Provider {
## Wallet Connection Types
Different wallets implement the EIP-1193 provider in different ways.
-The most common type is **injected wallets** — wallets that automatically inject a provider into the global `window` object under the `window.ethereum` property. This type of connection is used by wallets that are provided in the form of browsers or browser extensions, the most popular of which is **MetaMask**.
+The most common type is **injected wallets** - wallets that automatically inject a provider into the global `window` object under the `window.ethereum` property. This type of connection is used by wallets that are provided in the form of browsers or browser extensions, the most popular of which is **MetaMask**.
This is the simplest connection method and works out of the box for most browser wallets.
diff --git a/docs/sdk/endpoints.md b/docs/sdk/endpoints.md
new file mode 100644
index 00000000..e152f2b4
--- /dev/null
+++ b/docs/sdk/endpoints.md
@@ -0,0 +1,91 @@
+---
+id: endpoints
+title: Available Endpoints
+sidebar_position: 4
+description: RPC, API and Subgraph endpoints for Mainnet, Gnosis and Hoodi networks.
+---
+
+# Available Endpoints
+
+---
+
+## How to retrieve the subgraph endpoint URL
+
+The StakeWise V3 subgraph URL depends on the network the SDK is bound to. For SDK-driven workflows the subgraph URL is configured at construction time and the SDK queries it transparently - you do not need to read the URL yourself for typical reads. For direct GraphQL calls (custom queries, dashboards, monitoring), use the production URLs listed below under [Subgraph Endpoint](#subgraph-endpoint).
+
+```typescript
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const stats = await sdk.utils.getStakewiseStats()
+```
+
+The SDK ships with the StakeWise V3 subgraph URL baked in for each `Network` (Mainnet, Hoodi, Gnosis), so all read methods (`getVault`, `getStakeBalance`, `getStakewiseStats`, etc.) hit the right subgraph automatically.
+
+---
+
+## RPC Endpoint
+
+The RPC endpoint is the JSON-RPC URL of an Ethereum node for the selected network. You can use any public RPC provider - a curated list with public nodes for each chain is available at:
+
+🔗 **[chainlist.org](https://chainlist.org/)**
+
+✅ **Tip:** For production, prefer a dedicated provider (Infura, Alchemy, QuickNode, etc.) over a free public RPC - public RPCs are rate-limited and may go down.
+
+---
+
+## API Endpoint
+
+GraphQL endpoint of the StakeWise backend.
+
+| Network | Endpoint |
+|---------|----------|
+| **Mainnet** | `https://mainnet-api.stakewise.io/graphql` |
+| **Gnosis** | `https://gnosis-api.stakewise.io/graphql` |
+| **Hoodi** | `https://hoodi-api.stakewise.io/graphql` |
+
+---
+
+## Subgraph Endpoint
+
+GraphQL endpoint of the StakeWise subgraph.
+
+### Production
+
+Use the **`prod`** endpoint for production environments.
+
+| Network | Endpoint |
+|---------|----------|
+| **Mainnet** | `https://graphs.stakewise.io/mainnet/subgraphs/name/stakewise/prod` |
+| **Gnosis** | `https://graphs.stakewise.io/gnosis/subgraphs/name/stakewise/prod` |
+| **Hoodi** | `https://graphs.stakewise.io/hoodi/subgraphs/name/stakewise/prod` |
+
+#### Fallback
+
+| Network | Endpoint |
+|---------|----------|
+| **Mainnet** | `https://graphs-replica.stakewise.io/mainnet/subgraphs/name/stakewise/prod` |
+| **Gnosis** | `https://graphs-replica.stakewise.io/gnosis/subgraphs/name/stakewise/prod` |
+| **Hoodi** | `https://graphs-replica.stakewise.io/hoodi/subgraphs/name/stakewise/prod` |
+
+### Stage
+
+Mirrors the production schema but tracks the staging deployment - use it only when reproducing issues against a non-production environment.
+
+| Network | Endpoint |
+|---------|----------|
+| **Mainnet** | `https://graphs.stakewise.io/mainnet/subgraphs/name/stakewise/stage` |
+| **Gnosis** | `https://graphs.stakewise.io/gnosis/subgraphs/name/stakewise/stage` |
+| **Hoodi** | `https://graphs.stakewise.io/hoodi/subgraphs/name/stakewise/stage` |
+
+#### Fallback
+
+| Network | Endpoint |
+|---------|----------|
+| **Mainnet** | `https://graphs-replica.stakewise.io/mainnet/subgraphs/name/stakewise/stage` |
+| **Gnosis** | `https://graphs-replica.stakewise.io/gnosis/subgraphs/name/stakewise/stage` |
+| **Hoodi** | `https://graphs-replica.stakewise.io/hoodi/subgraphs/name/stakewise/stage` |
diff --git a/docs/sdk/fundamentals/_category_.json b/docs/sdk/fundamentals/_category_.json
new file mode 100644
index 00000000..d0b3a51d
--- /dev/null
+++ b/docs/sdk/fundamentals/_category_.json
@@ -0,0 +1,4 @@
+{
+ "position": 2,
+ "label": "Fundamentals"
+}
diff --git a/docs/sdk/fundamentals/aggregate-queries.md b/docs/sdk/fundamentals/aggregate-queries.md
new file mode 100644
index 00000000..04462ad8
--- /dev/null
+++ b/docs/sdk/fundamentals/aggregate-queries.md
@@ -0,0 +1,58 @@
+---
+id: aggregate-queries
+title: Network-wide aggregates
+sidebar_position: 3
+description: Use sdk.utils.getStakewiseStats() to fetch total ETH staked, user count, and total earned rewards across all StakeWise V3 vaults on the configured chain.
+---
+
+# Network-wide aggregates
+
+
+To answer "how much is staked across all StakeWise V3 vaults?" use `sdk.utils.getStakewiseStats()`. It returns the protocol-wide aggregate for the network the SDK is bound to.
+
+## Total ETH staked across all V3 vaults
+
+```typescript
+import { formatEther } from 'ethers'
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const stats = await sdk.utils.getStakewiseStats()
+
+console.log(`Total: ${formatEther(stats.totalAssets)} ETH`)
+console.log(`Users: ${stats.usersCount}`)
+console.log(`Earned: ${formatEther(stats.totalEarnedAssets)} ETH`)
+```
+
+`stats.totalAssets` and `stats.totalEarnedAssets` are wei strings; convert with `BigInt` for math, with `formatEther` for display. On Gnosis swap `Network.Mainnet` for `Network.Gnosis` and the value is in GNO.
+
+## Cross-chain TVL
+
+Instantiate one SDK per chain, run them concurrently:
+
+```typescript
+import { formatEther } from 'ethers'
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const mainnet = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const gnosis = new StakeWiseSDK({
+ network: Network.Gnosis,
+ endpoints: { web3: 'https://gnosis-rpc.io' },
+})
+
+const [ mainnetStats, gnosisStats ] = await Promise.all([
+ mainnet.utils.getStakewiseStats(),
+ gnosis.utils.getStakewiseStats(),
+])
+
+console.log(`Mainnet TVL: ${formatEther(mainnetStats.totalAssets)} ETH`)
+console.log(`Gnosis TVL: ${formatEther(gnosisStats.totalAssets)} GNO`)
+```
diff --git a/docs/sdk/fundamentals/concepts.md b/docs/sdk/fundamentals/concepts.md
new file mode 100644
index 00000000..253f55a8
--- /dev/null
+++ b/docs/sdk/fundamentals/concepts.md
@@ -0,0 +1,112 @@
+---
+id: concepts
+title: Core concepts
+sidebar_position: 0
+description: Conceptual overview of StakeWise V3 building blocks - vaults, osToken (osETH/osGNO), Boost leverage staking, MEV escrow vs Smoothing Pool, reward splitter, and the harvest workflow. Maps each concept to the SDK methods that interact with it.
+---
+
+# Core concepts
+
+Short primer on the StakeWise V3 building blocks the SDK exposes. Each section maps the concept to the relevant SDK call.
+
+## What is a Vault?
+
+A **vault** is an on-chain contract that pools staked assets (ETH on Mainnet/Hoodi, GNO on Gnosis), runs validators against them, and tracks each staker's share. Anyone can deploy and manage their own vault. The SDK fetches vault state with `sdk.vault.getVault({ vaultAddress })` and creates new vaults with `sdk.vault.create({...})`.
+
+```ts
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const vault = await sdk.vault.getVault({ vaultAddress: '0xVaultAddress' })
+```
+
+## What are the vault types?
+
+Five vault types, set at creation time via the `type` argument of `sdk.vault.create`:
+
+- **`VaultType.Default`** - regular vault open to any depositor.
+- **`VaultType.Private`** - regular vault restricted to a whitelist managed by the vault admin (`sdk.vault.getWhitelist`, whitelist write methods).
+- **`VaultType.Blocklist`** - regular vault open to anyone except blocked addresses (`sdk.vault.getBlocklist`).
+- **`VaultType.MetaVault`** - meta vault that does not run validators directly and routes deposits across a registry of sub-vaults. Open to any depositor.
+- **`VaultType.PrivateMetaVault`** - meta vault restricted to a whitelist (Mainnet and Hoodi only, not supported on Gnosis).
+
+Orthogonal to that, the optional `vaultToken: { name, symbol }` argument turns a regular vault into an **ERC20 vault** that issues a transferable share token. Omit it for a non-ERC20 vault where shares are tracked internally. Meta vaults do not support ERC20 share tokens on Gnosis.
+
+## What are meta vaults and sub-vaults?
+
+A **meta vault** is a vault that does not run validators directly - instead it holds a registry of **sub-vaults** and aggregates their staking activity. Stakers deposit once into the meta vault; the meta vault routes the assets across its sub-vaults. The meta vault admin (the **curator**) controls which sub-vaults are part of the registry.
+
+Lifecycle methods:
+
+- **`sdk.vault.addSubVault({ vaultAddress, subVaultAddress, userAddress })`** - propose a new sub-vault for the meta vault registry.
+- **`sdk.vault.rejectSubVault({...})`** - remove a proposed sub-vault before it becomes active.
+- **`sdk.vault.ejectSubVault({...})`** - remove an active sub-vault from the registry.
+- **`sdk.vault.getSubVaults({ vaultAddress, limit, skip })`** - list sub-vaults of a meta vault with paging, returning per-vault APY, staking and exiting assets.
+
+Before calling `addSubVault`, pre-check the sub-vault's access flags via `sdk.vault.getVault({ vaultAddress: subVaultAddress })`: if the sub-vault is private (`isPrivate: true`), the meta vault address must be on the sub-vault's whitelist; if the sub-vault has a blocklist (`isBlocklist: true`), the meta vault must not be on it. The sub-vault itself must not be a meta vault (`isMetaVault: false`), nesting is not supported.
+
+Use `VaultType.MetaVault` (or `VaultType.PrivateMetaVault` on Mainnet/Hoodi) when calling `sdk.vault.create` to deploy a meta vault. Meta vaults never support `isOwnMevEscrow`. On Gnosis, meta vaults additionally cannot use the `vaultToken` ERC20 share token, and `VaultType.PrivateMetaVault` is not supported at all.
+
+## What is osToken (osETH / osGNO)?
+
+**osToken** is the StakeWise liquid staking token: **osETH** on Ethereum Mainnet and Hoodi, **osGNO** on Gnosis. Stakers mint osToken against their vault deposit, keeping the underlying stake productive while gaining a transferable, redeemable token. The osToken ERC20 address lives at `sdk.config.addresses.tokens.mintToken`.
+
+```ts
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const apy = await sdk.osToken.getAPY()
+const osTokenAddress = sdk.config.addresses.tokens.mintToken
+const osToken = sdk.contracts.helpers.createErc20(osTokenAddress)
+const totalSupply = await osToken.totalSupply()
+```
+
+osToken redemption is at the protocol exchange rate - instant if unbonded assets are available in the vault, otherwise the vault exits validators to fulfill the request.
+
+## What is the osToken health factor?
+
+When a user mints osToken against their stake, the position has a **health factor** that reflects the LTV ratio against the vault's liquidation threshold. Use `sdk.osToken.getHealthFactor` before minting to avoid unhealthy positions; the result is one of `OsTokenPositionHealth.Healthy / Moderate / Risky / Unhealthy`.
+
+## What is Boost?
+
+**StakeWise Boost** is a leverage strategy: users mint osToken against their vault stake, then re-stake the borrowed value in a loop to amplify yield. The SDK exposes the strategy proxy address via `sdk.boost.getLeverageStrategyProxy` and the lock/unlock transactions via `sdk.boost.lock` / `sdk.boost.unlock`. Position state comes from `sdk.boost.getData`.
+
+The strategy keeps near-perfect collateral correlation (osETH against ETH, osGNO against GNO), so liquidation risk is bounded by the protocol's redemption guarantee.
+
+## What is the difference between MEV escrow and Smoothing Pool?
+
+When creating a vault, `isOwnMevEscrow` decides where block rewards go:
+
+- **`false` (default)** - rewards flow to the **Smoothing Pool**, a network-wide MEV pool that distributes proportionally across all participating vaults. Reduces variance.
+- **`true`** - the vault has its **own MEV escrow** contract. The vault keeps its own MEV rewards but bears the variance.
+
+## What is a reward splitter?
+
+A **reward splitter** is a contract that distributes a vault's rewards across multiple recipients in fixed proportions. The vault admin deploys one with `sdk.rewardSplitter.createRewardSplitter`, configures shares with `updateFeeRecipients`, and recipients claim with `claimRewards`. List existing splitters for a vault with `sdk.vault.getRewardSplitters`.
+
+## What is harvest and how to update vault state?
+
+Before deposits, mints, or other state-dependent reads can use the latest validator rewards, the vault must be **harvested** - its on-chain state updated with the latest oracle proof. The SDK provides `sdk.vault.getHarvestParams` to fetch the proof and `canHarvest` flag, and `sdk.vault.updateState` to submit the state update transaction (returns an empty hash if the vault is already up to date). Most user-facing flows do not need to call this manually; the deposit/withdraw paths handle it transparently.
+
+## How to wait for subgraph indexing after a transaction?
+
+Every write transaction (`deposit`, `withdraw`, `mint`, `burn`, `lock`, `unlock`, `createVault`, ...) updates the StakeWise subgraph asynchronously. Always wait for the subgraph to catch up before refetching read methods, otherwise data is stale:
+
+```ts
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({ network: Network.Mainnet, endpoints: { web3: 'https://main-rpc.io' } })
+
+const hash = await sdk.vault.deposit({ vaultAddress: '0x...', userAddress: '0x...', assets: 1n })
+
+await sdk.provider.waitForTransaction(hash)
+await sdk.utils.waitForSubgraph({ hash })
+```
diff --git a/docs/sdk/fundamentals/custom-contracts.md b/docs/sdk/fundamentals/custom-contracts.md
new file mode 100644
index 00000000..5cef7d8d
--- /dev/null
+++ b/docs/sdk/fundamentals/custom-contracts.md
@@ -0,0 +1,127 @@
+---
+id: custom-contracts
+title: Call arbitrary contracts
+sidebar_position: 5
+description: Interact with any StakeWise V3-compatible contract not yet integrated into the SDK's default StakeWise.Contracts namespace. Pass the contract address, ABI, and sdk.provider (which carries the SDK's network configuration and RPC endpoints) to createContract from @stakewise/v3-sdk to get a typed ethers Contract bound to the same chain as the SDK instance.
+---
+
+# Call arbitrary contracts
+
+
+The default `StakeWise.Contracts` namespace, exposed at `sdk.contracts`, ships with built-ins (Keeper, RewardSplitter, MintTokenController, etc.). For any contract that is not yet integrated there - a freshly deployed StakeWise V3-compatible contract, a Chainlink oracle, a partner protocol - use the top-level `createContract` helper exported from `@stakewise/v3-sdk`. Pass the contract address, its ABI, and `sdk.provider` (which carries the SDK's network configuration and fallback RPC endpoints) to get a typed ethers `Contract` bound to the same chain as the SDK instance.
+
+## Interact with a StakeWise V3-compatible contract not in the default namespace
+
+Suppose a new StakeWise V3-compatible contract is deployed and is not yet integrated into the SDK's default `StakeWise.Contracts`. To interact with it, reuse the SDK's provider and configuration via `createContract`:
+
+```typescript
+import { createContract, StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const customAbi = [
+ 'function getValue() view returns (uint256)',
+] as const
+
+type CustomContract = {
+ getValue(): Promise
+}
+
+const custom = createContract(
+ '0xCustomContractAddress',
+ customAbi,
+ sdk.provider,
+)
+
+const value = await custom.getValue()
+```
+
+`sdk.provider` carries the network the SDK was initialized for and any fallback RPC endpoints, so the custom contract talks to the same chain (Mainnet, Hoodi, or Gnosis) as the rest of the SDK calls.
+
+## Read from a Chainlink oracle
+
+```typescript
+import { createContract, StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const aggregatorV3Abi = [
+ 'function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)',
+ 'function decimals() view returns (uint8)',
+] as const
+
+type ChainlinkOracle = {
+ latestRoundData(): Promise
+ decimals(): Promise
+}
+
+const ethUsdOracle = createContract(
+ '0xChainlinkETHUSDFeedAddress',
+ aggregatorV3Abi,
+ sdk.provider,
+)
+
+const [ , answer ] = await ethUsdOracle.latestRoundData()
+const decimals = await ethUsdOracle.decimals()
+
+console.log(`ETH/USD = ${Number(answer) / 10 ** decimals}`)
+```
+
+Passing `sdk.provider` reuses the SDK's RPC configuration (network, fallback rotation), so a Gnosis-configured SDK automatically talks to Gnosis RPC.
+
+## Write through a custom contract
+
+For state-changing calls you need a signer-connected SDK. Build the contract, fetch a signer with `sdk.provider.getSigner()`, then `connect(signer)` on the returned contract:
+
+```typescript
+import { BrowserProvider } from 'ethers'
+import { createContract, StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ provider: new BrowserProvider(window.ethereum),
+})
+
+const partnerEscrowAbi = [
+ 'function claim(address recipient) returns (uint256)',
+] as const
+
+type PartnerEscrow = {
+ claim(recipient: string): Promise<{ hash: string }>
+}
+
+const escrow = createContract(
+ '0xPartnerEscrowAddress',
+ partnerEscrowAbi,
+ sdk.provider,
+)
+
+const signer = await sdk.provider.getSigner()
+const signedEscrow = escrow.connect(signer) as PartnerEscrow
+
+const tx = await signedEscrow.claim('0xRecipientAddress')
+console.log('tx hash:', tx.hash)
+```
+
+## Using a different provider
+
+`createContract` accepts any ethers provider, not just `sdk.provider`. Useful for archive nodes or cross-chain reads that bypass the SDK's RPC config:
+
+```typescript
+import { JsonRpcProvider } from 'ethers'
+import { createContract } from '@stakewise/v3-sdk'
+
+const archiveProvider = new JsonRpcProvider('https://archive-rpc.io')
+
+const erc20 = createContract(
+ '0xTokenAddress',
+ [ 'function balanceOf(address) view returns (uint256)' ],
+ archiveProvider,
+)
+```
diff --git a/docs/sdk/fundamentals/error-handling.md b/docs/sdk/fundamentals/error-handling.md
new file mode 100644
index 00000000..f13bbd47
--- /dev/null
+++ b/docs/sdk/fundamentals/error-handling.md
@@ -0,0 +1,54 @@
+---
+id: error-handling
+title: Error handling
+sidebar_position: 4
+description: Catch SDK errors, decode contract reverts via error.name, debug failed transactions with the JSON payload in error.message, and inspect pre-flight argument validation errors.
+---
+
+# Error handling
+
+
+Every SDK method validates arguments synchronously before any network call. Network errors (RPC, subgraph) and Solidity reverts come back as rejections from the returned promise.
+
+## Catching transaction reverts
+
+When a write reverts, the SDK wraps the underlying error in a `ContractError`. If the SDK can decode the Solidity revert reason, `error.name` becomes the contract error name (e.g. `ZeroSharesAmount`); `error.message` contains a JSON dump of `{ solidityError, to, from, data }` for debugging.
+
+```typescript
+import { parseEther } from 'ethers'
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ provider: browserProvider,
+})
+
+try {
+ await sdk.vault.deposit({
+ vaultAddress: '0x...',
+ userAddress: '0x...',
+ assets: parseEther('1.0'),
+ referrerAddress: '0x0000000000000000000000000000000000000000',
+ })
+}
+catch (error: any) {
+ console.error('revert reason:', error.name)
+ console.error('debug payload:', error.message)
+}
+```
+
+If the revert cannot be decoded (unknown error selector), `error.message` keeps the original payload. Copy `to` / `data` / `from` from there into Tenderly's [Simulator](https://dashboard.tenderly.co/explorer) for a human-readable call trace.
+
+## Pre-flight argument validation
+
+User-input mistakes throw synchronously before any network call:
+
+| Symptom | Cause |
+|---|---|
+| `Provider or endpoints.web3 should be provided` | Init without a provider AND without `endpoints.web3` |
+| `The "" argument must be a valid address` | Address malformed |
+| `The "" argument must be of type bigint` | Number/string passed where bigint expected |
+| `The "" argument must be a valid 32-byte hex hash` | Tx hash malformed (e.g. wrong length) |
+| `To send this transaction, please provide BrowserProvider to the StakeWiseSDK` | Calling default-form write on a read-only SDK |
+
+For read-only flows, use `sdk.vault..encode(...)` (returns calldata) instead of the default form.
diff --git a/docs/sdk/fundamentals/network-switching.md b/docs/sdk/fundamentals/network-switching.md
new file mode 100644
index 00000000..e970f273
--- /dev/null
+++ b/docs/sdk/fundamentals/network-switching.md
@@ -0,0 +1,77 @@
+---
+id: network-switching
+title: Switch networks at runtime
+sidebar_position: 4
+description: SDK instances are immutable per network. To switch from Mainnet to Gnosis (or back) at runtime, construct a new SDK and cache one instance per chain via a singleton factory. Listen on EIP-1193 chainChanged to react to wallet network changes.
+---
+
+# Switch networks at runtime
+
+
+A `StakeWiseSDK` instance is bound to a single network for its lifetime. `sdk.network`, `sdk.config`, `sdk.provider`, and `sdk.contracts` are derived from the constructor's `network` argument. To switch from Mainnet to Gnosis at runtime construct a new instance.
+
+## Singleton factory
+
+Recreating the SDK on every render is wasteful and breaks request caching. Cache one instance per chain:
+
+```typescript
+import { BrowserProvider } from 'ethers'
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdkCache = new Map()
+
+const rpcUrls: Record = {
+ [Network.Mainnet]: 'https://main-rpc.io',
+ [Network.Gnosis]: 'https://gnosis-rpc.io',
+ [Network.Hoodi]: 'https://hoodi-rpc.io',
+}
+
+export const getSDK = (network: Network, provider?: BrowserProvider): StakeWiseSDK => {
+ if (provider) {
+ return new StakeWiseSDK({ network, provider })
+ }
+
+ const cached = sdkCache.get(network)
+ if (cached) {
+ return cached
+ }
+
+ const sdk = new StakeWiseSDK({
+ network,
+ endpoints: { web3: rpcUrls[network] },
+ })
+
+ sdkCache.set(network, sdk)
+
+ return sdk
+}
+```
+
+## Reacting to wallet `chainChanged`
+
+When the user switches chains in MetaMask, listen on the EIP-1193 provider and rebuild the SDK on the new chain:
+
+```typescript
+import { BrowserProvider } from 'ethers'
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+let sdk: StakeWiseSDK = new StakeWiseSDK({
+ network: Network.Mainnet,
+ provider: new BrowserProvider(window.ethereum),
+})
+
+const supportedNetworks = new Set([ Network.Mainnet, Network.Gnosis, Network.Hoodi ])
+
+const handleChainChanged = (chainIdHex: string) => {
+ const chainId = Number.parseInt(chainIdHex, 16)
+
+ if (!supportedNetworks.has(chainId)) return
+
+ sdk = new StakeWiseSDK({
+ network: chainId,
+ provider: new BrowserProvider(window.ethereum),
+ })
+}
+
+window.ethereum?.on('chainChanged', handleChainChanged)
+```
diff --git a/docs/sdk/fundamentals/subgraph-indexing.md b/docs/sdk/fundamentals/subgraph-indexing.md
new file mode 100644
index 00000000..61b6e319
--- /dev/null
+++ b/docs/sdk/fundamentals/subgraph-indexing.md
@@ -0,0 +1,89 @@
+---
+id: subgraph-indexing
+title: Wait for subgraph indexing
+sidebar_position: 1
+description: After every write transaction (deposit, withdraw, mint, burn, lock, unlock, createVault), poll the StakeWise subgraph with sdk.utils.waitForSubgraph before refetching read methods - otherwise data is stale.
+---
+
+# Wait for subgraph indexing
+
+
+The SDK reads on-chain state from a subgraph (Graph Protocol indexer), which lags blockchain finality by 1-5 seconds. Calling any read method (`getVault`, `getStakeBalance`, `getExitQueuePositions`, etc.) right after a write returns stale data.
+
+`sdk.utils.waitForSubgraph` polls until the tx is indexed, then resolves. Always await it between write and refetch.
+
+## The canonical post-write flow
+
+```typescript
+import { BrowserProvider, parseEther } from 'ethers'
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ provider: new BrowserProvider(window.ethereum),
+})
+
+const txHash = await sdk.vault.deposit({
+ vaultAddress: '0x...',
+ userAddress: '0x...',
+ assets: parseEther('1.0'),
+ referrerAddress: '0x0000000000000000000000000000000000000000',
+})
+
+// 1. Wait for subgraph to index the tx (also covers receipt confirmation)
+await sdk.utils.waitForSubgraph({ hash: txHash })
+
+// 2. Now safe to refetch - subgraph reflects the deposit
+const stake = await sdk.vault.getStakeBalance({
+ vaultAddress: '0x...',
+ userAddress: '0x...',
+})
+```
+
+The helper polls `sdk.utils.getTransactions({ hash })` once per second until the tx is indexed. Subgraph fallback / retry on transient errors is handled by the SDK's own GraphQL fetch layer.
+
+The returned object is an `AbortPromise` (same shape as every other read in the SDK). Call `.abort()` on it to stop polling, the call is a no-op if the helper has already resolved.
+
+## Cancelling on unmount (React)
+
+Use the same ref-based pattern as the rest of the SDK - see [`useFetchList.ts`](https://github.com/stakewise/frontwise/blob/master/apps/web/src/views/VaultView/Modals/access/ListModal/util/useFetchList.ts) for the canonical example.
+
+```typescript
+import { useEffect, useRef, useState } from 'react'
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const useDepositFlow = (txHash: string | null) => {
+ const promiseRef = useRef | null>(null)
+ const [ ready, setReady ] = useState(false)
+
+ useEffect(() => {
+ if (!txHash) {
+ return
+ }
+
+ promiseRef.current?.abort()
+ promiseRef.current = sdk.utils.waitForSubgraph({ hash: txHash })
+
+ promiseRef.current
+ .then(() => setReady(true))
+ .catch((error) => console.error(error))
+
+ return () => promiseRef.current?.abort()
+ }, [ txHash ])
+
+ return ready
+}
+```
+
+`.abort()` suppresses any pending success/failure callbacks, your `.then` / `.catch` will never fire after it, so you don't need a separate `if (!aborted)` check inside them.
+
+## Common pitfalls
+
+- **Calling `getStakeBalance` immediately after `sdk.vault.deposit()` returns the *old* balance.** Always await `waitForSubgraph` between the two.
+- **Network changes during waiting.** When the user switches chains mid-poll, the SDK is bound to the previous network and the helper may keep polling the old subgraph. Call `.abort()` on the wait promise and recreate the SDK on the new chain.
+- **`sdk.provider.waitForTransaction(hash)` is not enough.** That waits for receipt confirmation (block inclusion) but does not wait for subgraph indexing, there is typically a 1–5 second additional gap.
diff --git a/docs/sdk/index.md b/docs/sdk/index.md
index bf2ebe4f..3d0f965d 100644
--- a/docs/sdk/index.md
+++ b/docs/sdk/index.md
@@ -2,14 +2,14 @@
id: overview
title: Overview
sidebar_position: 0
-description: StakeWise V3 SDK overview for TypeScript developers — vault management, staking transactions, and blockchain data queries.
+description: StakeWise V3 SDK overview for TypeScript developers - vault management, staking transactions, and blockchain data queries.
---
# Overview
The SDK provides an API for interacting with a **Vault**, allowing you to create and manage vaults, fetch user balances for stakers, and send blockchain transactions related to the vault. With this SDK, you can easily build and integrate your own vault interface.
-The SDK is written in **TypeScript** and is designed to remain lightweight — we carefully avoid adding unnecessary dependencies.
+The SDK is written in **TypeScript** and is designed to remain lightweight - we carefully avoid adding unnecessary dependencies.
To use the SDK, you must have the **ethers** library installed, version **6.14.3 or higher**.
The SDK retrieves data from both **subgraphs** and **on-chain smart contracts**, and it can also **send transactions** if you pass a provider instance connected to the user's wallet.
@@ -25,13 +25,24 @@ The SDK retrieves data from both **subgraphs** and **on-chain smart contracts**,
## Supported Networks
-The SDK currently supports:
+| Network | `Network` enum | Chain ID | Native asset | osToken symbol |
+|---|---|---|---|---|
+| Ethereum Mainnet | `Network.Mainnet` | `1` | ETH | osETH |
+| Gnosis | `Network.Gnosis` | `100` | xDAI | osGNO |
+| Hoodi Testnet | `Network.Hoodi` | `560048` | ETH | osETH |
-- **Ethereum Mainnet**
-- **Hoodi Testnet**
-- **Gnosis Network**
+You specify the target network when creating the SDK instance:
-You can specify the target network when creating the SDK instance.
+```ts
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+```
+
+> SDK instances are immutable per network. To switch networks at runtime, construct a new instance.
---
@@ -46,7 +57,7 @@ You can provide endpoints in several formats:
* An array of objects to include custom headers (e.g., for authenticated RPC endpoints).
-```typescript
+```ts
import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
const sdk = new StakeWiseSDK({
@@ -71,7 +82,7 @@ const sdk = new StakeWiseSDK({
## Transaction Flexibility
-The SDK supports both **client-side** (browser wallet) and **backend** (custodial) transaction execution. Each write method provides three execution patterns:
+The SDK supports both **client-side** (browser wallet) and **backend** (custodial) transaction execution. Each write method provides three execution patterns (with one exception - see below):
| Method | Description | Use Case |
|--------|-------------|----------|
@@ -79,13 +90,15 @@ The SDK supports both **client-side** (browser wallet) and **backend** (custodia
| `sdk.vault.method.encode(...)` | Returns encoded calldata | Custom transaction building & custodial wallets |
| `sdk.vault.method.estimateGas(...)` | Estimates gas costs | cost calculation |
+> **Exception:** `sdk.vault.multicall(values)` is a plain function with no `.encode` / `.estimateGas` siblings. It batches several vault operations into a single transaction and returns `Promise` where `T` is a user-typed tuple of per-operation results.
+
---
## Aborting Data Requests
Most data-fetching methods in the SDK return a **promise-like object** that includes built-in control methods such as `abort()`.
-This allows you to safely cancel network requests that are no longer relevant — for example, when a user changes a filter or input before the previous request completes.
+This allows you to safely cancel network requests that are no longer relevant - for example, when a user changes a filter or input before the previous request completes.
When `abort()` is called, the ongoing network request is **canceled**, and the associated promise will **not resolve or reject**.
If the request has already finished, calling `abort()` has **no effect**.
diff --git a/docs/sdk/installation-setup.md b/docs/sdk/installation-setup.md
index b34ba53a..9e81f4f6 100644
--- a/docs/sdk/installation-setup.md
+++ b/docs/sdk/installation-setup.md
@@ -17,27 +17,6 @@ yarn add @stakewise/v3-sdk
---
-## GraphQL Loader Setup
-
-The SDK includes `.graphql` queries. If your build tool does not natively support importing `.graphql` files, you’ll need to install and configure a corresponding plugin.
-
-### `Webpack Configuration`
-
-```ts
-// webpack.config.js
-module.exports = {
- module: {
- rules: [
- {
- test: /\.(graphql|gql)$/,
- exclude: /node_modules/,
- loader: 'graphql-tag/loader'
- }
- ]
- }
-}
-```
-
### `Next.js Configuration`
```ts
diff --git a/docs/sdk/03-quick-start/_category_.json b/docs/sdk/quick-start/_category_.json
similarity index 63%
rename from docs/sdk/03-quick-start/_category_.json
rename to docs/sdk/quick-start/_category_.json
index 867b92db..78dfab50 100644
--- a/docs/sdk/03-quick-start/_category_.json
+++ b/docs/sdk/quick-start/_category_.json
@@ -1,3 +1,4 @@
{
+ "position": 5,
"label": "Quick Start"
}
diff --git a/docs/sdk/03-quick-start/boost.md b/docs/sdk/quick-start/boost.md
similarity index 83%
rename from docs/sdk/03-quick-start/boost.md
rename to docs/sdk/quick-start/boost.md
index 1ac59bf4..886bcd74 100644
--- a/docs/sdk/03-quick-start/boost.md
+++ b/docs/sdk/quick-start/boost.md
@@ -2,13 +2,17 @@
id: boost-os-token
title: Boost osToken
sidebar_position: 6
-description: Boost staking rewards with StakeWise SDK — leverage osTokens as Aave collateral to amplify your vault staking position.
+description: Boost staking rewards with StakeWise SDK - leverage osTokens as Aave collateral to amplify your vault staking position.
---
# Boost osToken
StakeWise Boost enhances staking rewards by leveraging your osTokens as collateral to borrow additional assets from Aave. The borrowed funds are then restaked, creating a compounding effect that amplifies your staking position.
+## How to lock osToken into the Boost leverage strategy
+
+To boost a user's osToken position, look up the leverage strategy proxy with `sdk.boost.getLeverageStrategyProxy`, ensure the proxy has osToken allowance (via permit signature for EOAs or `approve` for multisig wallets), then call `sdk.boost.lock`. The full flow below covers both wallet types.
+
```ts
import { BrowserProvider, parseEther } from 'ethers'
import { StakeWiseSDK, Network, OsTokenPositionHealth } from '@stakewise/v3-sdk'
@@ -70,6 +74,8 @@ const boost = async (values: Input) => {
const { hash } = await signedContract.approve(permitAddress, shares)
await sdk.provider.waitForTransaction(hash)
+
+ await sdk.utils.waitForSubgraph({ hash })
}
else {
// Use gasless permit for EOAs
@@ -98,6 +104,8 @@ const boost = async (values: Input) => {
})
await sdk.provider.waitForTransaction(hash)
+
+ await sdk.utils.waitForSubgraph({ hash })
}
catch (error) {
console.error(error)
diff --git a/docs/sdk/03-quick-start/burn-os-token.md b/docs/sdk/quick-start/burn-os-token.md
similarity index 94%
rename from docs/sdk/03-quick-start/burn-os-token.md
rename to docs/sdk/quick-start/burn-os-token.md
index 7dbf0680..947ac334 100644
--- a/docs/sdk/03-quick-start/burn-os-token.md
+++ b/docs/sdk/quick-start/burn-os-token.md
@@ -2,7 +2,7 @@
id: burn-os-token
title: Burn osToken
sidebar_position: 5
-description: Burn osETH tokens in a StakeWise vault using the SDK — monitor osToken position health and execute burn transactions safely.
+description: Burn osETH tokens in a StakeWise vault using the SDK - monitor osToken position health and execute burn transactions safely.
---
# Burn osToken
@@ -68,6 +68,8 @@ const burn = async (values: Input) => {
})
await sdk.provider.waitForTransaction(hash)
+
+ await sdk.utils.waitForSubgraph({ hash })
}
catch (error) {
console.error(error)
diff --git a/docs/sdk/03-quick-start/deposit-to-vault.md b/docs/sdk/quick-start/deposit-to-vault.md
similarity index 86%
rename from docs/sdk/03-quick-start/deposit-to-vault.md
rename to docs/sdk/quick-start/deposit-to-vault.md
index f81ecc0e..6cef3fc7 100644
--- a/docs/sdk/03-quick-start/deposit-to-vault.md
+++ b/docs/sdk/quick-start/deposit-to-vault.md
@@ -2,14 +2,14 @@
id: deposit-to-vault
title: Deposit to vault
sidebar_position: 2
-description: Deposit ETH into a StakeWise vault using the SDK — client-side browser wallet and backend custodial wallet integration examples.
+description: Deposit ETH into a StakeWise vault using the SDK - client-side browser wallet and backend custodial wallet integration examples.
---
# Deposit to vault
This guide demonstrates two different approaches for depositing into a vault:
-### 1. Client-Side (Browser Wallet)
+## How to deposit from a browser wallet
Use this method when users connect their non-custodial wallet (like MetaMask) directly in the browser.
@@ -48,6 +48,8 @@ const deposit = async (values: Input) => {
const hash = await sdk.vault.deposit(params)
await sdk.provider.waitForTransaction(hash)
+
+ await sdk.utils.waitForSubgraph({ hash })
}
catch (error) {
console.error(error)
@@ -62,7 +64,7 @@ deposit({
```
---
-### 2. Backend-Side (Custodial Wallet)
+## How to deposit from a backend or custodial wallet
Use this method when you manage private keys on your backend server.
@@ -95,6 +97,8 @@ const deposit = async (values: Input) => {
const { hash } = await yourSigningService.sendTransaction()
await sdk.provider.waitForTransaction(hash)
+
+ await sdk.utils.waitForSubgraph({ hash })
}
catch (error) {
console.error(error)
diff --git a/docs/sdk/03-quick-start/get-user-data.md b/docs/sdk/quick-start/get-user-data.md
similarity index 94%
rename from docs/sdk/03-quick-start/get-user-data.md
rename to docs/sdk/quick-start/get-user-data.md
index 9f1694a0..16e34472 100644
--- a/docs/sdk/03-quick-start/get-user-data.md
+++ b/docs/sdk/quick-start/get-user-data.md
@@ -2,7 +2,7 @@
id: get-user-data
title: Get user data
sidebar_position: 1
-description: Query user staking data from StakeWise vaults — balances, osToken positions, boost amounts, APY, exit queues, and action history.
+description: Query user staking data from StakeWise vaults - balances, osToken positions, boost amounts, APY, exit queues, and action history.
---
# Get user data
diff --git a/docs/sdk/03-quick-start/get-vault-data.md b/docs/sdk/quick-start/get-vault-data.md
similarity index 88%
rename from docs/sdk/03-quick-start/get-vault-data.md
rename to docs/sdk/quick-start/get-vault-data.md
index 43b6abdc..f3c2db3b 100644
--- a/docs/sdk/03-quick-start/get-vault-data.md
+++ b/docs/sdk/quick-start/get-vault-data.md
@@ -2,7 +2,7 @@
id: get-vault-data
title: Get vault data
sidebar_position: 0
-description: Fetch StakeWise vault data using the SDK — retrieve vault details, stats charts, validators, whitelist, and blocklist information.
+description: Fetch StakeWise vault data using the SDK - retrieve vault details, stats charts, validators, whitelist, and blocklist information.
---
# Get vault data
diff --git a/docs/sdk/03-quick-start/mint-os-token.md b/docs/sdk/quick-start/mint-os-token.md
similarity index 93%
rename from docs/sdk/03-quick-start/mint-os-token.md
rename to docs/sdk/quick-start/mint-os-token.md
index 52a6f3b5..f79ffae9 100644
--- a/docs/sdk/03-quick-start/mint-os-token.md
+++ b/docs/sdk/quick-start/mint-os-token.md
@@ -2,7 +2,7 @@
id: mint-os-token
title: Mint osToken
sidebar_position: 4
-description: Mint osETH tokens against your StakeWise vault stake using the SDK — check max mintable amounts and execute minting transactions.
+description: Mint osETH tokens against your StakeWise vault stake using the SDK - check max mintable amounts and execute minting transactions.
---
# Mint osToken
@@ -57,6 +57,8 @@ const mint = async (values: Input) => {
})
await sdk.provider.waitForTransaction(hash)
+
+ await sdk.utils.waitForSubgraph({ hash })
}
catch (error) {
console.error(error)
diff --git a/docs/sdk/03-quick-start/unboost.md b/docs/sdk/quick-start/unboost.md
similarity index 93%
rename from docs/sdk/03-quick-start/unboost.md
rename to docs/sdk/quick-start/unboost.md
index 24bc4231..151d2c64 100644
--- a/docs/sdk/03-quick-start/unboost.md
+++ b/docs/sdk/quick-start/unboost.md
@@ -2,7 +2,7 @@
id: unboost-os-token
title: Unboost osToken
sidebar_position: 7
-description: Exit a leveraged boost position with the StakeWise SDK — repay Aave debt, unlock osToken collateral, and track the withdrawal queue.
+description: Exit a leveraged boost position with the StakeWise SDK - repay Aave debt, unlock osToken collateral, and track the withdrawal queue.
---
# Unboost osToken
@@ -62,6 +62,8 @@ const unboost = async (values: Input) => {
await sdk.provider.waitForTransaction(hash)
+ await sdk.utils.waitForSubgraph({ hash })
+
const unboostQueue = await sdk.boost.getQueuePosition({ userAddress, vaultAddress })
// After you have unboosted, your funds are placed in the withdrawal queue.
diff --git a/docs/sdk/03-quick-start/unstake.md b/docs/sdk/quick-start/unstake.md
similarity index 78%
rename from docs/sdk/03-quick-start/unstake.md
rename to docs/sdk/quick-start/unstake.md
index 7b8d0ec2..97205b54 100644
--- a/docs/sdk/03-quick-start/unstake.md
+++ b/docs/sdk/quick-start/unstake.md
@@ -2,13 +2,17 @@
id: unstake-example
title: Unstake
sidebar_position: 3
-description: Unstake deposits from a StakeWise vault using the SDK — withdraw staked assets, handle osToken burns, and track exit queue positions.
+description: Unstake deposits from a StakeWise vault using the SDK - withdraw staked assets, handle osToken burns, and track exit queue positions.
---
# Unstake
This guide outlines the implementation approach for unstaking user deposits.
+## How to unstake from a StakeWise V3 vault
+
+To unstake (withdraw) user assets from a vault, fetch the current stake balance and the maximum withdrawable amount, check whether any osToken must be burned first via `getBurnAmountForUnstake`, then call `sdk.vault.withdraw`. The withdrawal lands in the vault's exit queue, which you can read with `sdk.vault.getExitQueuePositions` after the subgraph has indexed the transaction.
+
```ts
import { BrowserProvider, parseEther, formatEther } from 'ethers'
import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
@@ -65,6 +69,8 @@ const unstake = async (values: Input) => {
await sdk.provider.waitForTransaction(hash)
+ await sdk.utils.waitForSubgraph({ hash })
+
const positions = await sdk.vault.getExitQueuePositions({ userAddress, vaultAddress })
// After unstaking, the withdrawal of funds will be added to the exit queue, which can be obtained using the SDK.
diff --git a/docs/sdk/reference.md b/docs/sdk/reference.md
index 5a46b109..6e5aff0e 100644
--- a/docs/sdk/reference.md
+++ b/docs/sdk/reference.md
@@ -1,11 +1,13 @@
---
id: sdk-reference
title: SDK Reference
-sidebar_position: 2
+sidebar_position: 3
description: StakeWise SDK reference for network configuration, global TypeScript types, contract access, and built-in ethers helpers.
---
-# Configuration for the Current Network
+# SDK Reference
+
+## Configuration for the Current Network
For each network, the SDK stores configuration containing essential data for working with the current network.
@@ -21,7 +23,7 @@ The configuration contains the following data:
- **addresses** - addresses of main contracts operating in the network
- **tokens** - token symbols for the current network
-# Global Types
+## Global Types
The SDK adds the `StakeWise` namespace to the global scope, from which you can access necessary data types.
@@ -34,6 +36,13 @@ The SDK adds the `StakeWise` namespace to the global scope, from which you can a
To get the return type of the `sdk.boost.getData` method, you can use two approaches:
```typescript
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
// First approach
type BoostData = Awaited>
@@ -41,16 +50,60 @@ type BoostData = Awaited>
type BoostData = Awaited>
```
-# Contracts
+## Built-in Contracts
You can make calls to the built-in ethers contracts if needed. To access contracts for the current network, use `sdk.contracts`.
-For example, if you want to quickly create a standard ERC20 contract, you can use the helper `sdk.contracts.helpers.createErc20(tokenAddress)`.
+For a standard ERC20 contract at any address, use `sdk.contracts.helpers.createErc20(tokenAddress)`.
+
+The osToken (osETH on Mainnet/Hoodi, osGNO on Gnosis) is the StakeWise V3 staking token. Its ERC20 address lives at `sdk.config.addresses.tokens.mintToken`:
+
+```typescript
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const osTokenAddress = sdk.config.addresses.tokens.mintToken
+const osToken = sdk.contracts.helpers.createErc20(osTokenAddress)
+
+const totalSupply = await osToken.totalSupply()
+```
+
+## How to read osETH ERC20 balance and total supply
+
+osETH (osGNO on Gnosis) is the StakeWise V3 liquid staking token. The SDK exposes its ERC20 contract pre-instantiated at `sdk.contracts.tokens.mintToken` - call `balanceOf`, `totalSupply`, `allowance`, etc. directly without wrapping the address yourself:
+
+```typescript
+import { formatEther } from 'ethers'
+import { StakeWiseSDK, Network } from '@stakewise/v3-sdk'
+
+const sdk = new StakeWiseSDK({
+ network: Network.Mainnet,
+ endpoints: { web3: 'https://main-rpc.io' },
+})
+
+const [ totalSupply, userBalance ] = await Promise.all([
+ sdk.contracts.tokens.mintToken.totalSupply(),
+ sdk.contracts.tokens.mintToken.balanceOf('0xUserAddress'),
+])
+
+console.log(`osETH supply: ${formatEther(totalSupply)}`)
+console.log(`User osETH balance: ${formatEther(userBalance)}`)
+```
+
+On Gnosis, swap `Network.Mainnet` for `Network.Gnosis` and the same `sdk.contracts.tokens.mintToken` returns osGNO supply and balance values.
+
+## Error Handling and Transaction Debugging
-# Error Handling and Transaction Debugging
Each SDK method validates the arguments passed to it before execution and throws an error if any argument is invalid. For example, if you attempt to send a transaction without providing a provider during SDK initialization, an error will be thrown.
+
If an error occurs during a transaction, you will see a detailed error logged in the console, including the transaction data. If the error object contains a `solidityError` field, it means the error was successfully parsed and the root cause is immediately visible.
+
If the `solidityError` field is not present, the error log will still include the `data`, `to`, and `from` fields. Using these values, you can reproduce and debug the transaction with Tenderly:
+
1. Open Tenderly and go to the Simulator section.
2. Select the appropriate network.
3. Paste the `to` address into the left column (the contract address field).
@@ -58,4 +111,5 @@ If the `solidityError` field is not present, the error log will still include th
5. Paste the `from` address into the From field on the right.
6. Disable Use Pending Block.
7. Enter the current block number into the Block Number field.
+
After running the simulation, you will see the exact error that occurred in the smart contract, making it significantly easier to understand and resolve the issue.