@oydual31/more-vaults-sdk 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +859 -215
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,26 +1,183 @@
|
|
|
1
1
|
# @oydual31/more-vaults-sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for the MoreVaults protocol. Supports **viem/wagmi
|
|
3
|
+
TypeScript SDK for the MoreVaults protocol. Supports **viem/wagmi**, **ethers.js v6**, and **React hooks**.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @oydual31/more-vaults-sdk
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Table of contents
|
|
12
|
+
|
|
13
|
+
1. [Installation](#installation)
|
|
14
|
+
2. [Quick start](#quick-start)
|
|
15
|
+
3. [Module overview](#module-overview)
|
|
16
|
+
4. [Feature parity table](#feature-parity-table)
|
|
17
|
+
5. [Core concepts](#core-concepts)
|
|
18
|
+
6. [Deposit flows (D1–D7)](#deposit-flows)
|
|
19
|
+
7. [Redeem flows (R1–R5)](#redeem-flows)
|
|
20
|
+
8. [Cross-chain flows](#cross-chain-flows)
|
|
21
|
+
9. [Curator operations](#curator-operations)
|
|
22
|
+
10. [Vault topology & distribution](#vault-topology--distribution)
|
|
23
|
+
11. [Spoke routes](#spoke-routes)
|
|
24
|
+
12. [React hooks reference](#react-hooks-reference)
|
|
25
|
+
13. [Stargate vs Standard OFT handling](#stargate-vs-standard-oft-handling)
|
|
26
|
+
14. [Supported chains](#supported-chains)
|
|
27
|
+
15. [LZ timeouts](#lz-timeouts)
|
|
28
|
+
16. [Pre-flight validation](#pre-flight-validation)
|
|
29
|
+
17. [Error types](#error-types)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @oydual31/more-vaults-sdk
|
|
37
|
+
# or
|
|
38
|
+
yarn add @oydual31/more-vaults-sdk
|
|
39
|
+
# or
|
|
40
|
+
pnpm add @oydual31/more-vaults-sdk
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Peer dependencies** (install only what you use — all are optional):
|
|
44
|
+
|
|
45
|
+
| Package | Version |
|
|
46
|
+
|---------|---------|
|
|
47
|
+
| `viem` | `>=2` |
|
|
48
|
+
| `ethers` | `>=6` |
|
|
49
|
+
| `react` | `>=18` |
|
|
50
|
+
| `wagmi` | `>=2` |
|
|
51
|
+
| `@tanstack/react-query` | `>=5` |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
### viem
|
|
58
|
+
|
|
9
59
|
```ts
|
|
10
|
-
|
|
11
|
-
import {
|
|
60
|
+
import { smartDeposit, smartRedeem, getVaultStatus, waitForAsyncRequest, LZ_TIMEOUTS } from '@oydual31/more-vaults-sdk/viem'
|
|
61
|
+
import { createPublicClient, createWalletClient, http, parseUnits } from 'viem'
|
|
62
|
+
import { base } from 'viem/chains'
|
|
63
|
+
|
|
64
|
+
const VAULT = '0x8f740aba022b3fcc934ab75c581c04b75e72aba6'
|
|
65
|
+
const RPC = 'https://mainnet.base.org'
|
|
66
|
+
|
|
67
|
+
const publicClient = createPublicClient({ chain: base, transport: http(RPC) })
|
|
68
|
+
const walletClient = createWalletClient({ account, chain: base, transport: http(RPC) })
|
|
69
|
+
|
|
70
|
+
// --- Deposit 100 USDC ---
|
|
71
|
+
const depositResult = await smartDeposit(
|
|
72
|
+
walletClient, publicClient,
|
|
73
|
+
{ vault: VAULT },
|
|
74
|
+
parseUnits('100', 6), // 100 USDC
|
|
75
|
+
account.address,
|
|
76
|
+
)
|
|
12
77
|
|
|
13
|
-
|
|
14
|
-
|
|
78
|
+
if ('guid' in depositResult) {
|
|
79
|
+
// Async vault — wait for LZ Read callback (~5 min)
|
|
80
|
+
const final = await waitForAsyncRequest(publicClient, VAULT, depositResult.guid)
|
|
81
|
+
console.log('Shares minted:', final.result)
|
|
82
|
+
} else {
|
|
83
|
+
console.log('Shares minted:', depositResult.shares)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- Redeem shares ---
|
|
87
|
+
const redeemResult = await smartRedeem(
|
|
88
|
+
walletClient, publicClient,
|
|
89
|
+
{ vault: VAULT },
|
|
90
|
+
shares,
|
|
91
|
+
account.address,
|
|
92
|
+
account.address,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if ('guid' in redeemResult) {
|
|
96
|
+
const final = await waitForAsyncRequest(publicClient, VAULT, redeemResult.guid)
|
|
97
|
+
console.log('Assets received:', final.result)
|
|
98
|
+
} else {
|
|
99
|
+
console.log('Assets received:', redeemResult.assets)
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### ethers.js
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { smartDeposit, smartRedeem, getVaultStatus } from '@oydual31/more-vaults-sdk/ethers'
|
|
107
|
+
import { Wallet, JsonRpcProvider, parseUnits } from 'ethers'
|
|
108
|
+
|
|
109
|
+
const provider = new JsonRpcProvider('https://mainnet.base.org')
|
|
110
|
+
const signer = new Wallet(PRIVATE_KEY, provider)
|
|
111
|
+
const VAULT = '0x8f740aba022b3fcc934ab75c581c04b75e72aba6'
|
|
112
|
+
|
|
113
|
+
const result = await smartDeposit(signer, { vault: VAULT }, parseUnits('100', 6), signer.address)
|
|
15
114
|
```
|
|
16
115
|
|
|
17
116
|
---
|
|
18
117
|
|
|
19
|
-
##
|
|
118
|
+
## Module overview
|
|
20
119
|
|
|
21
|
-
|
|
120
|
+
| Import path | Description | Dependencies |
|
|
121
|
+
|-------------|-------------|--------------|
|
|
122
|
+
| `@oydual31/more-vaults-sdk/viem` | Full-featured SDK — all flows, curator, topology | `viem` |
|
|
123
|
+
| `@oydual31/more-vaults-sdk/ethers` | Same feature set, ethers.js v6 API | `ethers` |
|
|
124
|
+
| `@oydual31/more-vaults-sdk/react` | React hooks built on wagmi + @tanstack/react-query | `react`, `wagmi`, `@tanstack/react-query` |
|
|
22
125
|
|
|
23
|
-
|
|
126
|
+
All three modules expose the same logical features. Choose based on your stack.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Feature parity table
|
|
131
|
+
|
|
132
|
+
| Feature | viem | ethers | react |
|
|
133
|
+
|---------|------|--------|-------|
|
|
134
|
+
| `smartDeposit` / `useSmartDeposit` | Yes | Yes | Yes |
|
|
135
|
+
| `smartRedeem` / `useSmartRedeem` | Yes | Yes | Yes |
|
|
136
|
+
| `depositSimple` / `useDepositSimple` | Yes | Yes | Yes |
|
|
137
|
+
| `redeemShares` / `useRedeemShares` | Yes | Yes | Yes |
|
|
138
|
+
| `depositAsync`, `mintAsync` | Yes | Yes | — |
|
|
139
|
+
| `redeemAsync` | Yes | Yes | — |
|
|
140
|
+
| `depositMultiAsset` | Yes | Yes | — |
|
|
141
|
+
| `requestRedeem`, `getWithdrawalRequest` | Yes | Yes | — |
|
|
142
|
+
| `withdrawAssets` | Yes | Yes | — |
|
|
143
|
+
| `depositFromSpoke`, `depositFromSpokeAsync` | Yes | Yes | — |
|
|
144
|
+
| `quoteDepositFromSpokeFee` | Yes | Yes | — |
|
|
145
|
+
| `waitForCompose`, `quoteComposeFee`, `executeCompose` | Yes | Yes | — |
|
|
146
|
+
| `bridgeSharesToHub`, `bridgeAssetsToSpoke` | Yes | Yes | — |
|
|
147
|
+
| `resolveRedeemAddresses`, `quoteShareBridgeFee` | Yes | Yes | — |
|
|
148
|
+
| `getVaultStatus` | Yes | Yes | `useVaultStatus` |
|
|
149
|
+
| `getVaultMetadata` | Yes | Yes | `useVaultMetadata` |
|
|
150
|
+
| `getUserPosition` | Yes | Yes | `useUserPosition` |
|
|
151
|
+
| `getUserPositionMultiChain` | Yes | Yes | `useUserPositionMultiChain` |
|
|
152
|
+
| `previewDeposit`, `previewRedeem` | Yes | Yes | — |
|
|
153
|
+
| `canDeposit` | Yes | Yes | — |
|
|
154
|
+
| `getUserBalances`, `getMaxWithdrawable` | Yes | Yes | — |
|
|
155
|
+
| `getVaultSummary` | Yes | Yes | — |
|
|
156
|
+
| `quoteLzFee` | Yes | Yes | `useLzFee` |
|
|
157
|
+
| `getAsyncRequestStatusLabel` | Yes | Yes | `useAsyncRequestStatus` |
|
|
158
|
+
| `waitForAsyncRequest` | Yes | — | — |
|
|
159
|
+
| `getVaultTopology`, `getFullVaultTopology`, `discoverVaultTopology` | Yes | Yes | `useVaultTopology` |
|
|
160
|
+
| `isOnHubChain`, `getAllVaultChainIds` | Yes | Yes | — |
|
|
161
|
+
| `getVaultDistribution`, `getVaultDistributionWithTopology` | Yes | Yes | `useVaultDistribution` |
|
|
162
|
+
| `getInboundRoutes` | Yes | Yes | `useInboundRoutes` |
|
|
163
|
+
| `getUserBalancesForRoutes` | Yes | Yes | — |
|
|
164
|
+
| `getOutboundRoutes`, `quoteRouteDepositFee` | Yes | Yes | — |
|
|
165
|
+
| `getCuratorVaultStatus` | Yes | Yes | `useCuratorVaultStatus` |
|
|
166
|
+
| `getPendingActions` | Yes | Yes | `usePendingActions` |
|
|
167
|
+
| `isCurator` | Yes | Yes | `useIsCurator` |
|
|
168
|
+
| `getVaultAnalysis` | Yes | Yes | `useVaultAnalysis` |
|
|
169
|
+
| `getVaultAssetBreakdown` | Yes | Yes | `useVaultAssetBreakdown` |
|
|
170
|
+
| `checkProtocolWhitelist` | Yes | Yes | `useProtocolWhitelist` |
|
|
171
|
+
| `encodeCuratorAction`, `buildCuratorBatch` | Yes | Yes | — |
|
|
172
|
+
| `submitActions` | Yes | Yes | `useSubmitActions` |
|
|
173
|
+
| `executeActions` | Yes | Yes | `useExecuteActions` |
|
|
174
|
+
| `vetoActions` | Yes | Yes | `useVetoActions` |
|
|
175
|
+
| `buildUniswapV3Swap`, `encodeUniswapV3SwapCalldata` | Yes | Yes | — |
|
|
176
|
+
| `detectStargateOft` | Yes | Yes | — |
|
|
177
|
+
| `preflightSync`, `preflightAsync` | Yes | Yes | — |
|
|
178
|
+
| `preflightSpokeDeposit`, `preflightSpokeRedeem` | Yes | Yes | — |
|
|
179
|
+
| `preflightRedeemLiquidity` | Yes | Yes | — |
|
|
180
|
+
| Chain constants, ABIs, error types | Yes | Yes | — |
|
|
24
181
|
|
|
25
182
|
---
|
|
26
183
|
|
|
@@ -28,9 +185,9 @@ Each vault is a **diamond proxy** (EIP-2535) — a single address that routes ca
|
|
|
28
185
|
|
|
29
186
|
### Assets and shares
|
|
30
187
|
|
|
31
|
-
- **Asset
|
|
32
|
-
- **Shares**: what the vault mints when you deposit.
|
|
33
|
-
- **Share price**: how many assets one share is worth
|
|
188
|
+
- **Asset**: the token users deposit (e.g. USDC). Always the same token in and out.
|
|
189
|
+
- **Shares**: what the vault mints when you deposit. Represent your ownership percentage. As the vault earns yield, each share becomes worth more assets. Shares are ERC-20 tokens at the vault address.
|
|
190
|
+
- **Share price**: how many assets one share is worth. Starts at 1:1 and grows over time.
|
|
34
191
|
|
|
35
192
|
```
|
|
36
193
|
Deposit 100 USDC → receive 100 shares (at launch, price = 1)
|
|
@@ -42,122 +199,105 @@ Redeem 100 shares → receive 105 USDC
|
|
|
42
199
|
|
|
43
200
|
### Hub and spoke
|
|
44
201
|
|
|
45
|
-
MoreVaults uses a **hub-and-spoke** model
|
|
202
|
+
MoreVaults uses a **hub-and-spoke** model:
|
|
46
203
|
|
|
47
|
-
- **Hub** (`isHub = true`): the chain where the vault does its accounting — mints/burns shares, accepts deposits and redemptions.
|
|
48
|
-
- **Spoke**: a
|
|
204
|
+
- **Hub** (`isHub = true`): the chain where the vault does its accounting — mints/burns shares, accepts deposits and redemptions.
|
|
205
|
+
- **Spoke**: a chain where the vault has deployed funds for yield. Users on spoke chains bridge tokens to the hub via LayerZero OFT.
|
|
49
206
|
|
|
50
|
-
If
|
|
207
|
+
If `isHub = false`, the vault is a single-chain vault — no cross-chain flows apply, use D1/R1.
|
|
51
208
|
|
|
52
209
|
### Vault modes
|
|
53
210
|
|
|
54
|
-
|
|
211
|
+
Use `getVaultStatus()` to read the current mode:
|
|
55
212
|
|
|
56
|
-
| Mode | `isHub` | Oracle |
|
|
57
|
-
|
|
213
|
+
| Mode | `isHub` | Oracle | Description | Applicable flows |
|
|
214
|
+
|------|---------|--------|-------------|-----------------|
|
|
58
215
|
| `local` | false | — | Single-chain vault. No cross-chain. | D1, D2, R1, R2 |
|
|
59
|
-
| `cross-chain-oracle` | true | ON | Hub with
|
|
60
|
-
| `cross-chain-async` | true | OFF | Hub where spoke balances
|
|
216
|
+
| `cross-chain-oracle` | true | ON | Hub with oracle-fed spoke balances. Synchronous like `local`. | D1/D3, D2, R1, R2 |
|
|
217
|
+
| `cross-chain-async` | true | OFF | Hub where spoke balances require a LZ Read query. Async deposits/redeems. | D4, D5, R5 |
|
|
61
218
|
| `paused` | — | — | No deposits or redeems accepted. | None |
|
|
62
219
|
| `full` | — | — | Deposit capacity reached. Redeems still work. | R1, R2 only |
|
|
63
220
|
|
|
64
221
|
### Oracle ON vs OFF
|
|
65
222
|
|
|
66
|
-
When `oraclesCrossChainAccounting = true`, the vault has a configured oracle
|
|
223
|
+
When `oraclesCrossChainAccounting = true`, the vault has a configured oracle that knows the current value of spoke deployments. `totalAssets()` resolves instantly — flows are synchronous.
|
|
67
224
|
|
|
68
|
-
When
|
|
225
|
+
When `false`, the vault must query spokes via **LayerZero Read** to calculate share prices. Deposits and redeems are **async** — the user locks funds, waits for the oracle response (~1–5 min), and a keeper finalizes.
|
|
69
226
|
|
|
70
227
|
### Hub liquidity and repatriation
|
|
71
228
|
|
|
72
|
-
In a cross-chain vault, the hub typically holds only a
|
|
73
|
-
|
|
74
|
-
This means:
|
|
229
|
+
In a cross-chain vault, the hub typically holds only a fraction of TVL as liquid assets. The rest is deployed to spoke chains.
|
|
75
230
|
|
|
76
|
-
-
|
|
77
|
-
- **Redeemable now** = hub liquid balance only.
|
|
78
|
-
- For async redeems (R5), a failed `executeRequest` causes the vault to
|
|
79
|
-
|
|
80
|
-
**Repatriation** is the process of moving funds from spokes back to the hub so they become liquid again. This is a **manual, curator-only operation** (`executeBridging`). There is no automatic mechanism — the protocol does not pull funds from spokes on behalf of users.
|
|
231
|
+
- `totalAssets()` = hub liquid balance + value of all spoke positions.
|
|
232
|
+
- **Redeemable now** = hub liquid balance only. Attempting to redeem more than the hub holds fails.
|
|
233
|
+
- For async redeems (R5), a failed `executeRequest` causes the vault to refund shares — no assets are lost.
|
|
234
|
+
- **Repatriation** (moving funds from spokes to the hub) is a curator-only operation.
|
|
81
235
|
|
|
82
236
|
### Withdrawal queue and timelock
|
|
83
237
|
|
|
84
|
-
Some vaults require shares to be
|
|
85
|
-
|
|
86
|
-
- **`withdrawalQueueEnabled = true`**: users must call `requestRedeem` first, then `redeemShares` separately.
|
|
87
|
-
- **`withdrawalTimelockSeconds > 0`**: there is a mandatory waiting period between `requestRedeem` and `redeemShares`. Useful for vaults that need time to rebalance liquidity.
|
|
238
|
+
Some vaults require shares to be queued before redemption:
|
|
88
239
|
|
|
89
|
-
|
|
240
|
+
- `withdrawalQueueEnabled = true`: users must call `requestRedeem` first, then `redeemShares` separately.
|
|
241
|
+
- `withdrawalTimelockSeconds > 0`: mandatory waiting period between `requestRedeem` and `redeemShares`.
|
|
90
242
|
|
|
91
243
|
### Escrow
|
|
92
244
|
|
|
93
|
-
The `MoreVaultsEscrow`
|
|
245
|
+
The `MoreVaultsEscrow` temporarily holds user funds during async flows (D4, D5, R5). Tokens go to the escrow while the LZ Read resolves. The SDK handles the approve-to-escrow step internally.
|
|
94
246
|
|
|
95
|
-
**You never interact with the escrow directly.** The SDK handles the approve-to-escrow step internally. You just need to pass its address in `VaultAddresses.escrow`.
|
|
96
|
-
|
|
97
|
-
To get the escrow address: read it from the vault itself:
|
|
98
247
|
```ts
|
|
99
248
|
const status = await getVaultStatus(publicClient, VAULT_ADDRESS)
|
|
100
249
|
const escrow = status.escrow // address(0) if not configured
|
|
101
250
|
```
|
|
102
251
|
|
|
103
|
-
### Same address on every chain (CREATE3)
|
|
104
|
-
|
|
105
|
-
MoreVaults deploys all contracts using **CREATE3**, which means a vault has the **same address on every chain** where it exists. If the hub vault on Base is `0xABC...`, the corresponding escrow and spoke-side contracts are also at predictable, identical addresses across Arbitrum, Ethereum, etc.
|
|
106
|
-
|
|
107
252
|
### VaultAddresses
|
|
108
253
|
|
|
109
|
-
Every flow function takes a `VaultAddresses` object:
|
|
110
|
-
|
|
111
254
|
```ts
|
|
112
255
|
interface VaultAddresses {
|
|
113
256
|
vault: Address // Vault address — same on every chain (CREATE3)
|
|
114
257
|
escrow?: Address // MoreVaultsEscrow — required for D4, D5, R5 (auto-resolved if omitted)
|
|
115
|
-
hubChainId?: number // Optional chain validation
|
|
258
|
+
hubChainId?: number // Optional chain validation guard
|
|
116
259
|
}
|
|
117
260
|
```
|
|
118
261
|
|
|
119
|
-
For simple hub flows (D1, R1) you only need `vault`. For async flows the SDK auto-resolves the escrow from the vault if not provided.
|
|
120
|
-
|
|
121
262
|
### LayerZero EID
|
|
122
263
|
|
|
123
|
-
LayerZero identifies chains by an **Endpoint ID (EID)** — different from the
|
|
264
|
+
LayerZero identifies chains by an **Endpoint ID (EID)** — different from the EVM chain ID:
|
|
124
265
|
|
|
125
266
|
| Chain | Chain ID | LayerZero EID |
|
|
126
267
|
|-------|----------|---------------|
|
|
127
268
|
| Ethereum | 1 | 30101 |
|
|
128
269
|
| Arbitrum | 42161 | 30110 |
|
|
270
|
+
| Optimism | 10 | 30111 |
|
|
129
271
|
| Base | 8453 | 30184 |
|
|
272
|
+
| BNB Chain | 56 | 30102 |
|
|
273
|
+
| Sonic | 146 | 30332 |
|
|
130
274
|
| Flow EVM | 747 | 30336 |
|
|
131
275
|
|
|
132
276
|
### GUID (async request ID)
|
|
133
277
|
|
|
134
|
-
When you call `depositAsync`, `mintAsync`, or `redeemAsync`, the function returns a `guid` — a `bytes32` identifier for that
|
|
278
|
+
When you call `depositAsync`, `mintAsync`, or `redeemAsync`, the function returns a `guid` — a `bytes32` identifier for that cross-chain request:
|
|
135
279
|
|
|
136
280
|
```ts
|
|
137
281
|
const { guid } = await depositAsync(...)
|
|
138
282
|
|
|
139
|
-
//
|
|
140
|
-
const final = await waitForAsyncRequest(publicClient,
|
|
283
|
+
// Wait for finalization (recommended)
|
|
284
|
+
const final = await waitForAsyncRequest(publicClient, VAULT, guid)
|
|
141
285
|
// final.status: 'completed' | 'refunded'
|
|
142
286
|
// final.result: exact shares minted or assets received (bigint)
|
|
143
287
|
|
|
144
|
-
//
|
|
145
|
-
const info = await getAsyncRequestStatusLabel(publicClient,
|
|
288
|
+
// Check status once
|
|
289
|
+
const info = await getAsyncRequestStatusLabel(publicClient, VAULT, guid)
|
|
146
290
|
// info.label: 'pending' | 'fulfilled' | 'finalized' | 'refunded'
|
|
147
291
|
```
|
|
148
292
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
## Clients
|
|
152
|
-
|
|
153
|
-
Every SDK function takes one or two "clients" as its first arguments — the objects that talk to the blockchain.
|
|
293
|
+
### Clients
|
|
154
294
|
|
|
155
295
|
**viem** uses two separate objects:
|
|
156
296
|
|
|
157
|
-
| Client | Role |
|
|
158
|
-
|
|
159
|
-
| `publicClient` | Read-only
|
|
160
|
-
| `walletClient` | Signs and sends transactions.
|
|
297
|
+
| Client | Role |
|
|
298
|
+
|--------|------|
|
|
299
|
+
| `publicClient` | Read-only. `createPublicClient({ chain, transport: http(RPC_URL) })` |
|
|
300
|
+
| `walletClient` | Signs and sends transactions. `createWalletClient({ account, chain, transport: http(RPC_URL) })` |
|
|
161
301
|
|
|
162
302
|
In React with wagmi:
|
|
163
303
|
```ts
|
|
@@ -166,257 +306,761 @@ const publicClient = usePublicClient()
|
|
|
166
306
|
const { data: walletClient } = useWalletClient()
|
|
167
307
|
```
|
|
168
308
|
|
|
169
|
-
**ethers.js** uses a single `Signer
|
|
309
|
+
**ethers.js** uses a single `Signer`:
|
|
170
310
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
311
|
+
```ts
|
|
312
|
+
// Browser
|
|
313
|
+
const signer = await new BrowserProvider(window.ethereum).getSigner()
|
|
314
|
+
// Node.js
|
|
315
|
+
const signer = new Wallet(PRIVATE_KEY, new JsonRpcProvider(RPC_URL))
|
|
316
|
+
```
|
|
175
317
|
|
|
176
|
-
> The client's chain must match the chain where the vault lives. Hub flows
|
|
318
|
+
> The client's chain must match the chain where the vault lives. Hub flows use the hub chain client. Spoke flows (D6/D7) use the spoke chain client.
|
|
177
319
|
|
|
178
320
|
---
|
|
179
321
|
|
|
180
|
-
##
|
|
322
|
+
## Deposit flows
|
|
181
323
|
|
|
182
|
-
|
|
324
|
+
### Smart flows (recommended)
|
|
183
325
|
|
|
184
|
-
|
|
326
|
+
`smartDeposit` auto-detects the vault type and routes to the correct flow:
|
|
327
|
+
|
|
328
|
+
| Vault mode | What `smartDeposit` calls |
|
|
329
|
+
|------------|--------------------------|
|
|
330
|
+
| `local` or `cross-chain-oracle` | `depositSimple` (synchronous) |
|
|
331
|
+
| `cross-chain-async` | `depositAsync` (async, returns `guid`) |
|
|
185
332
|
|
|
186
333
|
```ts
|
|
187
|
-
import { smartDeposit
|
|
188
|
-
import { createPublicClient, createWalletClient, http, parseUnits } from 'viem'
|
|
189
|
-
import { base } from 'viem/chains'
|
|
334
|
+
import { smartDeposit } from '@oydual31/more-vaults-sdk/viem'
|
|
190
335
|
|
|
191
|
-
const
|
|
192
|
-
const walletClient = createWalletClient({ account, chain: base, transport: http(RPC_URL) })
|
|
336
|
+
const result = await smartDeposit(walletClient, publicClient, { vault: VAULT }, amount, receiver)
|
|
193
337
|
|
|
194
|
-
|
|
338
|
+
if ('guid' in result) {
|
|
339
|
+
// Async vault — poll for finalization
|
|
340
|
+
console.log(result.guid)
|
|
341
|
+
} else {
|
|
342
|
+
// Sync vault — shares available immediately
|
|
343
|
+
console.log(result.shares)
|
|
344
|
+
}
|
|
345
|
+
```
|
|
195
346
|
|
|
196
|
-
|
|
197
|
-
|
|
347
|
+
### Hub-chain deposit flows
|
|
348
|
+
|
|
349
|
+
| ID | Function | When to use |
|
|
350
|
+
|----|----------|-------------|
|
|
351
|
+
| — | `smartDeposit` | Recommended. Auto-detects vault type. |
|
|
352
|
+
| D1 | `depositSimple` | User on hub chain, oracle ON or local vault |
|
|
353
|
+
| D2 | `depositMultiAsset` | Deposit multiple tokens in one call |
|
|
354
|
+
| D3 | `depositCrossChainOracleOn` | Alias for D1 — hub with oracle ON |
|
|
355
|
+
| D4 | `depositAsync` | Hub with oracle OFF — async LZ Read. Returns `guid`. |
|
|
356
|
+
| D5 | `mintAsync` | Same as D4 but user specifies exact share amount |
|
|
357
|
+
|
|
358
|
+
**D1 — Simple deposit:**
|
|
359
|
+
```ts
|
|
360
|
+
import { depositSimple } from '@oydual31/more-vaults-sdk/viem'
|
|
361
|
+
|
|
362
|
+
const { txHash, shares } = await depositSimple(
|
|
198
363
|
walletClient, publicClient,
|
|
199
364
|
{ vault: VAULT },
|
|
200
|
-
parseUnits('100', 6),
|
|
365
|
+
parseUnits('100', 6),
|
|
201
366
|
account.address,
|
|
202
367
|
)
|
|
368
|
+
```
|
|
203
369
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
console.log('GUID:', depositResult.guid)
|
|
370
|
+
**D4 — Async deposit (oracle OFF):**
|
|
371
|
+
```ts
|
|
372
|
+
import { depositAsync, waitForAsyncRequest, quoteLzFee } from '@oydual31/more-vaults-sdk/viem'
|
|
208
373
|
|
|
209
|
-
|
|
210
|
-
const final = await waitForAsyncRequest(publicClient, VAULT, depositResult.guid)
|
|
211
|
-
// final.status: 'completed' | 'refunded'
|
|
212
|
-
// final.result: shares minted (bigint)
|
|
213
|
-
console.log('Shares minted:', final.result)
|
|
214
|
-
} else {
|
|
215
|
-
console.log('Sync deposit — shares:', depositResult.shares)
|
|
216
|
-
}
|
|
374
|
+
const lzFee = await quoteLzFee(publicClient, VAULT)
|
|
217
375
|
|
|
218
|
-
|
|
219
|
-
const redeemResult = await smartRedeem(
|
|
376
|
+
const { txHash, guid } = await depositAsync(
|
|
220
377
|
walletClient, publicClient,
|
|
221
378
|
{ vault: VAULT },
|
|
222
|
-
|
|
223
|
-
account.address,
|
|
379
|
+
parseUnits('100', 6),
|
|
224
380
|
account.address,
|
|
381
|
+
lzFee,
|
|
225
382
|
)
|
|
226
383
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const final = await waitForAsyncRequest(publicClient, VAULT, redeemResult.guid)
|
|
231
|
-
console.log('Assets received:', final.result)
|
|
232
|
-
} else {
|
|
233
|
-
console.log('Sync redeem — assets:', redeemResult.assets)
|
|
234
|
-
}
|
|
384
|
+
const final = await waitForAsyncRequest(publicClient, VAULT, guid)
|
|
385
|
+
// final.status: 'completed' | 'refunded'
|
|
386
|
+
// final.result: shares minted (bigint)
|
|
235
387
|
```
|
|
236
388
|
|
|
237
389
|
---
|
|
238
390
|
|
|
239
|
-
##
|
|
391
|
+
## Redeem flows
|
|
240
392
|
|
|
241
|
-
### Smart flows (
|
|
393
|
+
### Smart flows (recommended)
|
|
242
394
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
|
246
|
-
|
|
395
|
+
`smartRedeem` auto-detects the vault type and routes to the correct flow:
|
|
396
|
+
|
|
397
|
+
| Vault mode | What `smartRedeem` calls |
|
|
398
|
+
|------------|------------------------|
|
|
399
|
+
| `local` or `cross-chain-oracle` | `redeemShares` (synchronous) |
|
|
400
|
+
| `cross-chain-async` | `redeemAsync` (async, returns `guid`) |
|
|
401
|
+
|
|
402
|
+
### Hub-chain redeem flows
|
|
403
|
+
|
|
404
|
+
| ID | Function | When to use |
|
|
405
|
+
|----|----------|-------------|
|
|
406
|
+
| — | `smartRedeem` | Recommended. Auto-detects vault type. |
|
|
407
|
+
| R1 | `redeemShares` | Standard redeem, hub chain, no queue |
|
|
408
|
+
| R2 | `withdrawAssets` | Specify exact asset amount to receive |
|
|
409
|
+
| R3 | `requestRedeem` | Withdrawal queue enabled, no timelock |
|
|
410
|
+
| R4 | `requestRedeem` | Withdrawal queue + mandatory wait period |
|
|
411
|
+
| R5 | `redeemAsync` | Hub with oracle OFF — async LZ Read. Returns `guid`. |
|
|
247
412
|
|
|
248
|
-
|
|
413
|
+
**R1 — Simple redeem:**
|
|
414
|
+
```ts
|
|
415
|
+
import { redeemShares } from '@oydual31/more-vaults-sdk/viem'
|
|
416
|
+
|
|
417
|
+
const { txHash, assets } = await redeemShares(
|
|
418
|
+
walletClient, publicClient,
|
|
419
|
+
{ vault: VAULT },
|
|
420
|
+
shares,
|
|
421
|
+
account.address, // receiver
|
|
422
|
+
account.address, // owner
|
|
423
|
+
)
|
|
424
|
+
```
|
|
249
425
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
| D1 | `depositSimple` | User on hub chain, oracle ON or local vault | [->](./docs/flows/D1-deposit-simple.md) |
|
|
254
|
-
| D2 | `depositMultiAsset` | Deposit multiple tokens in one call | [->](./docs/flows/D2-deposit-multi-asset.md) |
|
|
255
|
-
| D3 | `depositCrossChainOracleOn` | Alias for D1 — hub with oracle ON | [->](./docs/flows/D3-deposit-oracle-on.md) |
|
|
256
|
-
| D4 | `depositAsync` | Hub with oracle OFF — async LZ Read | [->](./docs/flows/D4-deposit-async.md) |
|
|
257
|
-
| D5 | `mintAsync` | Same as D4 but user specifies exact share amount | [->](./docs/flows/D5-mint-async.md) |
|
|
426
|
+
**R3/R4 — Queued redeem:**
|
|
427
|
+
```ts
|
|
428
|
+
import { requestRedeem, redeemShares, getWithdrawalRequest } from '@oydual31/more-vaults-sdk/viem'
|
|
258
429
|
|
|
259
|
-
|
|
430
|
+
// Step 1: queue the request
|
|
431
|
+
await requestRedeem(walletClient, publicClient, { vault: VAULT }, shares, account.address)
|
|
260
432
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
433
|
+
// Step 2: wait for timelock to expire (if configured), then redeem
|
|
434
|
+
// Check status
|
|
435
|
+
const request = await getWithdrawalRequest(publicClient, VAULT, account.address)
|
|
264
436
|
|
|
265
|
-
|
|
437
|
+
// Step 3: execute redeem
|
|
438
|
+
await redeemShares(walletClient, publicClient, { vault: VAULT }, shares, account.address, account.address)
|
|
439
|
+
```
|
|
266
440
|
|
|
267
|
-
|
|
441
|
+
---
|
|
268
442
|
|
|
269
|
-
|
|
270
|
-
|----|----------|-------------|-----|
|
|
271
|
-
| — | `smartRedeem` | **Recommended.** Auto-detects vault type. | — |
|
|
272
|
-
| R1 | `redeemShares` | Standard redeem, hub chain, no queue | [->](./docs/flows/R1-redeem-shares.md) |
|
|
273
|
-
| R2 | `withdrawAssets` | Specify exact asset amount to receive | [->](./docs/flows/R2-withdraw-assets.md) |
|
|
274
|
-
| R3 | `requestRedeem` | Withdrawal queue enabled, no timelock | [->](./docs/flows/R3-R4-request-redeem.md) |
|
|
275
|
-
| R4 | `requestRedeem` | Withdrawal queue + mandatory wait period | [->](./docs/flows/R3-R4-request-redeem.md) |
|
|
276
|
-
| R5 | `redeemAsync` | Hub with oracle OFF — async LZ Read | [->](./docs/flows/R5-redeem-async.md) |
|
|
443
|
+
## Cross-chain flows
|
|
277
444
|
|
|
278
|
-
###
|
|
445
|
+
### Spoke deposit (D6 / D7)
|
|
279
446
|
|
|
280
|
-
|
|
447
|
+
Deposits from a spoke chain to the hub vault via LayerZero OFT Compose:
|
|
281
448
|
|
|
449
|
+
- **D6 (oracle ON)**: composer calls `_depositAndSend` — shares arrive on spoke in ~1 LZ round-trip.
|
|
450
|
+
- **D7 (oracle OFF)**: composer calls `_initDeposit` — requires an additional LZ Read round-trip.
|
|
451
|
+
|
|
452
|
+
The interface is identical for both. The SDK detects which path the composer takes.
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
import {
|
|
456
|
+
getInboundRoutes,
|
|
457
|
+
quoteDepositFromSpokeFee,
|
|
458
|
+
depositFromSpoke,
|
|
459
|
+
waitForCompose,
|
|
460
|
+
quoteComposeFee,
|
|
461
|
+
executeCompose,
|
|
462
|
+
} from '@oydual31/more-vaults-sdk/viem'
|
|
463
|
+
import { LZ_EIDS } from '@oydual31/more-vaults-sdk/viem'
|
|
464
|
+
|
|
465
|
+
// 1. Discover available routes
|
|
466
|
+
const routes = await getInboundRoutes(hubChainId, VAULT, vaultAsset, userAddress)
|
|
467
|
+
|
|
468
|
+
// 2. Quote the LZ fee for the chosen route
|
|
469
|
+
const lzFee = await quoteDepositFromSpokeFee(
|
|
470
|
+
spokePublicClient,
|
|
471
|
+
VAULT,
|
|
472
|
+
route.spokeOft,
|
|
473
|
+
LZ_EIDS.BASE, // hubEid
|
|
474
|
+
LZ_EIDS.ETH, // spokeEid
|
|
475
|
+
amount,
|
|
476
|
+
account.address,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
// 3. Send from spoke chain
|
|
480
|
+
const { txHash, guid, composeData } = await depositFromSpoke(
|
|
481
|
+
spokeWalletClient, spokePublicClient,
|
|
482
|
+
VAULT,
|
|
483
|
+
route.spokeOft,
|
|
484
|
+
LZ_EIDS.BASE, // hubEid
|
|
485
|
+
LZ_EIDS.ETH, // spokeEid
|
|
486
|
+
amount,
|
|
487
|
+
account.address,
|
|
488
|
+
lzFee,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
// 4. For Stargate OFTs: execute the pending compose on the hub (2-TX flow)
|
|
492
|
+
if (composeData) {
|
|
493
|
+
const fullComposeData = await waitForCompose(hubPublicClient, composeData, account.address)
|
|
494
|
+
const composeFee = await quoteComposeFee(hubPublicClient, VAULT, LZ_EIDS.ETH, account.address)
|
|
495
|
+
const { txHash: composeTxHash, guid: asyncGuid } = await executeCompose(
|
|
496
|
+
hubWalletClient, hubPublicClient, fullComposeData, composeFee,
|
|
497
|
+
)
|
|
498
|
+
// For D7 vaults, asyncGuid is present — poll finalization
|
|
499
|
+
if (asyncGuid) {
|
|
500
|
+
const final = await waitForAsyncRequest(hubPublicClient, VAULT, asyncGuid)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// For standard OFTs: no action needed — compose executes automatically in 1 TX.
|
|
282
504
|
```
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
505
|
+
|
|
506
|
+
### Spoke redeem (3-step flow)
|
|
507
|
+
|
|
508
|
+
Full spoke redeem moves shares from spoke to hub, redeems, then bridges assets back:
|
|
509
|
+
|
|
510
|
+
```
|
|
511
|
+
Step 1 (Spoke): bridgeSharesToHub() — bridge shares spoke→hub via SHARE_OFT (~7 min)
|
|
512
|
+
Step 2 (Hub): smartRedeem() — redeem on hub (auto-detects async, ~5 min callback)
|
|
513
|
+
Step 3 (Hub): bridgeAssetsToSpoke() — bridge assets hub→spoke via Stargate/OFT (~13 min)
|
|
286
514
|
```
|
|
287
515
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
516
|
+
```ts
|
|
517
|
+
import {
|
|
518
|
+
resolveRedeemAddresses,
|
|
519
|
+
preflightSpokeRedeem,
|
|
520
|
+
bridgeSharesToHub,
|
|
521
|
+
quoteShareBridgeFee,
|
|
522
|
+
smartRedeem,
|
|
523
|
+
bridgeAssetsToSpoke,
|
|
524
|
+
} from '@oydual31/more-vaults-sdk/viem'
|
|
525
|
+
|
|
526
|
+
// Pre-step: resolve all contract addresses dynamically
|
|
527
|
+
const addresses = await resolveRedeemAddresses(publicClient, VAULT, spokeChainId)
|
|
528
|
+
|
|
529
|
+
// Pre-step: validate balances and gas
|
|
530
|
+
const check = await preflightSpokeRedeem(route, shares, userAddress, shareBridgeFee)
|
|
295
531
|
|
|
296
|
-
|
|
532
|
+
// Step 1: bridge shares to hub
|
|
533
|
+
const shareFee = await quoteShareBridgeFee(spokePublicClient, VAULT, hubEid, account.address)
|
|
534
|
+
const { txHash } = await bridgeSharesToHub(spokeWalletClient, spokePublicClient, route, shares, account.address, shareFee)
|
|
297
535
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
###
|
|
306
|
-
|
|
307
|
-
Full reference: [docs/user-helpers.md](./docs/user-helpers.md)
|
|
308
|
-
|
|
309
|
-
| Function | What it returns |
|
|
310
|
-
|----------|----------------|
|
|
311
|
-
| `getUserPosition` | shares, asset value, share price, pending withdrawal (single chain) |
|
|
312
|
-
| `getUserPositionMultiChain` | shares across hub + all spokes, total shares, estimated assets |
|
|
313
|
-
| `previewDeposit` | estimated shares for a given asset amount |
|
|
314
|
-
| `previewRedeem` | estimated assets for a given share amount |
|
|
315
|
-
| `canDeposit` | `{ allowed, reason }` — paused / cap-full / ok |
|
|
316
|
-
| `getVaultMetadata` | name, symbol, decimals, underlying, TVL, capacity |
|
|
317
|
-
| `getVaultStatus` | full config snapshot + recommended flow + issues list |
|
|
318
|
-
| `quoteLzFee` | native fee required for D4, D5, R5 |
|
|
319
|
-
| `getAsyncRequestStatusLabel` | pending / ready-to-execute / completed / refunded |
|
|
320
|
-
| `getUserBalances` | shares + underlying balance in one call |
|
|
321
|
-
| `getMaxWithdrawable` | max assets redeemable given hub liquidity |
|
|
322
|
-
| `getVaultSummary` | metadata + status + user position combined |
|
|
323
|
-
|
|
324
|
-
### Spoke route discovery
|
|
536
|
+
// Step 2: redeem on hub (after shares arrive ~7 min)
|
|
537
|
+
const redeemResult = await smartRedeem(hubWalletClient, hubPublicClient, { vault: VAULT }, shares, account.address, account.address)
|
|
538
|
+
|
|
539
|
+
// Step 3: bridge assets back to spoke
|
|
540
|
+
await bridgeAssetsToSpoke(hubWalletClient, hubPublicClient, route, assets, account.address, bridgeFee)
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Compose helpers
|
|
325
544
|
|
|
326
545
|
| Function | Description |
|
|
327
546
|
|----------|-------------|
|
|
328
|
-
| `
|
|
329
|
-
| `
|
|
330
|
-
| `
|
|
331
|
-
|
|
332
|
-
|
|
547
|
+
| `waitForCompose` | Poll for pending compose in LZ Endpoint's `composeQueue`. Scans `ComposeSent` events from hub block captured at TX1. |
|
|
548
|
+
| `quoteComposeFee` | Quote ETH needed for `executeCompose` (readFee + shareSendFee + 10% buffer) |
|
|
549
|
+
| `executeCompose` | Execute pending compose on hub chain. Returns `{ txHash, guid? }` — `guid` present for async D7 vaults |
|
|
550
|
+
|
|
551
|
+
---
|
|
333
552
|
|
|
334
|
-
|
|
553
|
+
## Curator operations
|
|
335
554
|
|
|
336
|
-
|
|
555
|
+
Curator operations are for vault managers, not end users. All reads are multicall-batched. All writes use the simulate-then-write pattern.
|
|
337
556
|
|
|
338
|
-
|
|
339
|
-
|----------|-------------|
|
|
340
|
-
| `getCuratorVaultStatus` | Full status snapshot: curator address, timelock, slippage, nonce, available assets, LZ adapter |
|
|
341
|
-
| `getPendingActions` | Pending actions for a given nonce, with `isExecutable` flag (timelock check) |
|
|
342
|
-
| `isCurator` | Check if an address is the vault's curator |
|
|
557
|
+
### Status reads
|
|
343
558
|
|
|
344
559
|
```ts
|
|
345
|
-
import { getCuratorVaultStatus, isCurator } from '@oydual31/more-vaults-sdk/viem'
|
|
560
|
+
import { getCuratorVaultStatus, getPendingActions, isCurator, getVaultAnalysis, getVaultAssetBreakdown, checkProtocolWhitelist } from '@oydual31/more-vaults-sdk/viem'
|
|
346
561
|
|
|
347
562
|
const status = await getCuratorVaultStatus(publicClient, VAULT)
|
|
348
563
|
// status.curator — curator address
|
|
349
564
|
// status.timeLockPeriod — seconds (0 = immediate execution)
|
|
565
|
+
// status.maxSlippagePercent — slippage limit for swaps
|
|
350
566
|
// status.currentNonce — latest action nonce
|
|
351
|
-
// status.availableAssets — whitelisted
|
|
352
|
-
// status.lzAdapter — cross-chain accounting manager
|
|
567
|
+
// status.availableAssets — whitelisted token addresses
|
|
568
|
+
// status.lzAdapter — cross-chain accounting manager address
|
|
353
569
|
// status.paused — vault paused state
|
|
354
570
|
|
|
355
571
|
const isManager = await isCurator(publicClient, VAULT, myAddress)
|
|
572
|
+
|
|
573
|
+
// Full analysis — available assets with name/symbol/decimals, depositable assets, whitelist config
|
|
574
|
+
const analysis = await getVaultAnalysis(publicClient, VAULT)
|
|
575
|
+
// analysis.availableAssets — AssetInfo[] with metadata
|
|
576
|
+
// analysis.depositableAssets — AssetInfo[]
|
|
577
|
+
// analysis.depositWhitelistEnabled
|
|
578
|
+
// analysis.registryAddress
|
|
579
|
+
|
|
580
|
+
// Per-asset balance breakdown on the hub
|
|
581
|
+
const breakdown = await getVaultAssetBreakdown(publicClient, VAULT)
|
|
582
|
+
// breakdown.assets — AssetBalance[] (address, name, symbol, decimals, balance)
|
|
583
|
+
// breakdown.totalAssets
|
|
584
|
+
// breakdown.totalSupply
|
|
585
|
+
|
|
586
|
+
// Check pending actions for a nonce
|
|
587
|
+
const pending = await getPendingActions(publicClient, VAULT, nonce)
|
|
588
|
+
// pending.actionsData — raw calldata bytes[]
|
|
589
|
+
// pending.pendingUntil — timestamp when executable
|
|
590
|
+
// pending.isExecutable — boolean (timelock expired)
|
|
591
|
+
|
|
592
|
+
// Check protocol whitelist
|
|
593
|
+
const whitelist = await checkProtocolWhitelist(publicClient, VAULT, [routerAddress])
|
|
594
|
+
// { '0xRouter...': true }
|
|
356
595
|
```
|
|
357
596
|
|
|
358
|
-
###
|
|
597
|
+
### Batch actions
|
|
359
598
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
599
|
+
Curator actions are encoded and submitted as a batch. When `timeLockPeriod == 0`, actions execute immediately on submission. With a timelock, they queue and must be executed separately.
|
|
600
|
+
|
|
601
|
+
```ts
|
|
602
|
+
import {
|
|
603
|
+
buildUniswapV3Swap,
|
|
604
|
+
encodeCuratorAction,
|
|
605
|
+
buildCuratorBatch,
|
|
606
|
+
submitActions,
|
|
607
|
+
executeActions,
|
|
608
|
+
vetoActions,
|
|
609
|
+
} from '@oydual31/more-vaults-sdk/viem'
|
|
610
|
+
|
|
611
|
+
// Build a Uniswap V3 swap action (router auto-resolved per chainId)
|
|
612
|
+
const swapAction = buildUniswapV3Swap({
|
|
613
|
+
chainId: 8453, // Base — uses SwapRouter02 (no deadline)
|
|
614
|
+
tokenIn: USDC_ADDRESS,
|
|
615
|
+
tokenOut: WETH_ADDRESS,
|
|
616
|
+
fee: 500, // 0.05% pool
|
|
617
|
+
amountIn: parseUnits('1000', 6),
|
|
618
|
+
minAmountOut: parseUnits('0.39', 18),
|
|
619
|
+
recipient: VAULT,
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
// Build additional actions using the discriminated union type
|
|
623
|
+
const depositAction: CuratorAction = {
|
|
624
|
+
type: 'erc4626Deposit',
|
|
625
|
+
vault: MORPHO_VAULT,
|
|
626
|
+
assets: parseUnits('500', 6),
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Encode and submit the batch
|
|
630
|
+
const batch = buildCuratorBatch([swapAction, depositAction])
|
|
631
|
+
const { txHash, nonce } = await submitActions(walletClient, publicClient, VAULT, batch)
|
|
632
|
+
|
|
633
|
+
// If timeLockPeriod > 0: wait for timelock, then execute
|
|
634
|
+
await executeActions(walletClient, publicClient, VAULT, nonce)
|
|
635
|
+
|
|
636
|
+
// Guardian: cancel pending actions
|
|
637
|
+
await vetoActions(guardianWalletClient, publicClient, VAULT, [nonce])
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Supported CuratorAction types
|
|
641
|
+
|
|
642
|
+
| Type | Description |
|
|
643
|
+
|------|-------------|
|
|
644
|
+
| `swap` | Single Uniswap V3 exactInputSingle swap |
|
|
645
|
+
| `batchSwap` | Multiple swaps in one action |
|
|
646
|
+
| `erc4626Deposit` | Deposit assets into an ERC-4626 vault |
|
|
647
|
+
| `erc4626Redeem` | Redeem shares from an ERC-4626 vault |
|
|
648
|
+
| `erc7540RequestDeposit` | Request deposit into an ERC-7540 async vault |
|
|
649
|
+
| `erc7540Deposit` | Finalize ERC-7540 deposit |
|
|
650
|
+
| `erc7540RequestRedeem` | Request redeem from an ERC-7540 async vault |
|
|
651
|
+
| `erc7540Redeem` | Finalize ERC-7540 redeem |
|
|
652
|
+
|
|
653
|
+
### Swap helpers
|
|
654
|
+
|
|
655
|
+
`buildUniswapV3Swap` automatically selects the correct router and ABI variant per chain:
|
|
656
|
+
|
|
657
|
+
| Chain | Router | ABI variant |
|
|
658
|
+
|-------|--------|-------------|
|
|
659
|
+
| Base (8453) | SwapRouter02 `0x2626...` | No `deadline` field |
|
|
660
|
+
| Ethereum (1) | SwapRouter `0xE592...` | Has `deadline` field |
|
|
661
|
+
| Arbitrum (42161) | SwapRouter `0xE592...` | Has `deadline` field |
|
|
662
|
+
| Optimism (10) | SwapRouter `0xE592...` | Has `deadline` field |
|
|
663
|
+
| Flow EVM (747) | FlowSwap V3 `0xeEDC...` | Has `deadline` field |
|
|
664
|
+
|
|
665
|
+
To get raw calldata without wrapping in a `CuratorAction`:
|
|
666
|
+
```ts
|
|
667
|
+
const { targetContract, swapCallData } = encodeUniswapV3SwapCalldata({
|
|
668
|
+
chainId: 8453,
|
|
669
|
+
tokenIn: USDC_ADDRESS,
|
|
670
|
+
tokenOut: WETH_ADDRESS,
|
|
671
|
+
fee: 500,
|
|
672
|
+
amountIn: parseUnits('1000', 6),
|
|
673
|
+
minAmountOut: 0n,
|
|
674
|
+
recipient: VAULT,
|
|
675
|
+
})
|
|
676
|
+
```
|
|
367
677
|
|
|
368
678
|
---
|
|
369
679
|
|
|
370
|
-
##
|
|
680
|
+
## Vault topology & distribution
|
|
681
|
+
|
|
682
|
+
### Topology
|
|
683
|
+
|
|
684
|
+
Resolve the hub/spoke structure of any vault:
|
|
685
|
+
|
|
686
|
+
```ts
|
|
687
|
+
import {
|
|
688
|
+
getVaultTopology,
|
|
689
|
+
getFullVaultTopology,
|
|
690
|
+
discoverVaultTopology,
|
|
691
|
+
isOnHubChain,
|
|
692
|
+
getAllVaultChainIds,
|
|
693
|
+
OMNI_FACTORY_ADDRESS,
|
|
694
|
+
} from '@oydual31/more-vaults-sdk/viem'
|
|
695
|
+
|
|
696
|
+
// Query from a known chain
|
|
697
|
+
const topo = await getVaultTopology(baseClient, VAULT)
|
|
698
|
+
// { role: 'hub', hubChainId: 8453, spokeChainIds: [1, 42161] }
|
|
699
|
+
|
|
700
|
+
// Query from any chain — same vault is a spoke on Ethereum
|
|
701
|
+
const topo2 = await getVaultTopology(ethClient, VAULT)
|
|
702
|
+
// { role: 'spoke', hubChainId: 8453, spokeChainIds: [1] }
|
|
703
|
+
|
|
704
|
+
// Auto-discover across all supported chains (no wallet needed)
|
|
705
|
+
const topo3 = await discoverVaultTopology(VAULT)
|
|
706
|
+
// Iterates all supported chains, finds the hub, returns full topology
|
|
707
|
+
|
|
708
|
+
// Get full spoke list — must use hub-chain client
|
|
709
|
+
const fullTopo = await getFullVaultTopology(baseClient, VAULT)
|
|
710
|
+
|
|
711
|
+
// Helpers
|
|
712
|
+
const onHub = isOnHubChain(walletChainId, topo) // boolean
|
|
713
|
+
const allChains = getAllVaultChainIds(topo) // [8453, 1, 42161]
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
`VaultTopology` shape:
|
|
717
|
+
```ts
|
|
718
|
+
interface VaultTopology {
|
|
719
|
+
role: 'hub' | 'spoke' | 'local'
|
|
720
|
+
hubChainId: number
|
|
721
|
+
spokeChainIds: number[]
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Distribution
|
|
726
|
+
|
|
727
|
+
Read the cross-chain capital distribution (hub liquid, hub strategies, spoke balances):
|
|
728
|
+
|
|
729
|
+
```ts
|
|
730
|
+
import { getVaultDistribution, getVaultDistributionWithTopology } from '@oydual31/more-vaults-sdk/viem'
|
|
731
|
+
|
|
732
|
+
// With explicit spoke clients — reads spoke balances in parallel
|
|
733
|
+
const dist = await getVaultDistribution(baseClient, VAULT, {
|
|
734
|
+
[1]: ethClient,
|
|
735
|
+
[42161]: arbClient,
|
|
736
|
+
})
|
|
737
|
+
// dist.hubLiquidBalance — idle on hub (not deployed)
|
|
738
|
+
// dist.hubStrategyBalance — deployed to hub-side strategies (Morpho, Aave, etc.)
|
|
739
|
+
// dist.hubTotalAssets — hubLiquidBalance + hubStrategyBalance
|
|
740
|
+
// dist.spokesDeployedBalance — what hub accounting thinks is on spokes
|
|
741
|
+
// dist.spokeBalances — SpokeBalance[] { chainId, totalAssets, isReachable }
|
|
742
|
+
// dist.totalActual — hub + reachable spoke totals
|
|
743
|
+
// dist.oracleAccountingEnabled
|
|
744
|
+
|
|
745
|
+
// Hub-only — discovers spoke chain IDs but does not read them
|
|
746
|
+
const dist2 = await getVaultDistributionWithTopology(baseClient, VAULT)
|
|
747
|
+
// dist2.spokeChainIds — list of spoke chain IDs to query if needed
|
|
748
|
+
// dist2.spokeBalances === [] (empty — no spoke clients provided)
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
## Spoke routes
|
|
754
|
+
|
|
755
|
+
Discover available deposit and redeem routes across chains:
|
|
756
|
+
|
|
757
|
+
```ts
|
|
758
|
+
import {
|
|
759
|
+
getInboundRoutes,
|
|
760
|
+
getUserBalancesForRoutes,
|
|
761
|
+
getOutboundRoutes,
|
|
762
|
+
quoteRouteDepositFee,
|
|
763
|
+
NATIVE_SYMBOL,
|
|
764
|
+
} from '@oydual31/more-vaults-sdk/viem'
|
|
765
|
+
|
|
766
|
+
// All routes a user can deposit from
|
|
767
|
+
const inbound = await getInboundRoutes(hubChainId, VAULT, vaultAsset, userAddress)
|
|
768
|
+
// Returns InboundRoute[]:
|
|
769
|
+
// - depositType: 'direct' | 'direct-async' | 'oft-compose'
|
|
770
|
+
// - spokeChainId, spokeOft, spokeToken, hubOft
|
|
771
|
+
// - sourceTokenSymbol — display this to users (e.g. 'USDC', 'weETH')
|
|
772
|
+
// - lzFeeEstimate (using 1 USDC placeholder amount)
|
|
773
|
+
// - nativeSymbol — gas token for the spoke chain
|
|
774
|
+
|
|
775
|
+
// Fetch user balances for each route
|
|
776
|
+
const withBalances = await getUserBalancesForRoutes(inbound, userAddress)
|
|
777
|
+
// Adds userBalance: bigint to each route
|
|
778
|
+
|
|
779
|
+
// Precise fee quote for a real deposit amount
|
|
780
|
+
const fee = await quoteRouteDepositFee(route, hubChainId, amount, userAddress)
|
|
781
|
+
// Returns 0n for 'direct' routes (no LZ fee needed)
|
|
782
|
+
|
|
783
|
+
// All chains a user can receive assets when redeeming
|
|
784
|
+
const outbound = await getOutboundRoutes(hubChainId, VAULT)
|
|
785
|
+
// Returns OutboundRoute[]:
|
|
786
|
+
// - chainId, routeType: 'hub' | 'spoke', eid, nativeSymbol
|
|
787
|
+
|
|
788
|
+
// Native gas symbol per chain
|
|
789
|
+
NATIVE_SYMBOL[8453] // 'ETH'
|
|
790
|
+
NATIVE_SYMBOL[747] // 'FLOW'
|
|
791
|
+
NATIVE_SYMBOL[146] // 'S'
|
|
792
|
+
NATIVE_SYMBOL[56] // 'BNB'
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
**InboundRoute deposit types:**
|
|
796
|
+
|
|
797
|
+
| `depositType` | User location | LZ fee | What happens |
|
|
798
|
+
|---------------|--------------|--------|--------------|
|
|
799
|
+
| `direct` | Hub chain, sync vault | None | Standard ERC-4626 `deposit()` |
|
|
800
|
+
| `direct-async` | Hub chain, async vault | Yes | `depositAsync()` with LZ Read |
|
|
801
|
+
| `oft-compose` | Spoke chain | Yes | OFT bridge + composer on hub |
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
## React hooks reference
|
|
806
|
+
|
|
807
|
+
Import from `@oydual31/more-vaults-sdk/react`. Requires wagmi v2 + @tanstack/react-query v5.
|
|
808
|
+
|
|
809
|
+
### Read hooks
|
|
810
|
+
|
|
811
|
+
| Hook | Returns | Description |
|
|
812
|
+
|------|---------|-------------|
|
|
813
|
+
| `useVaultStatus(vault)` | `VaultStatus` | Full config snapshot + recommended flow |
|
|
814
|
+
| `useVaultMetadata(vault)` | `VaultMetadata` | name, symbol, decimals, underlying, TVL, capacity |
|
|
815
|
+
| `useUserPosition(vault, user)` | `UserPosition` | shares, asset value, share price, pending withdrawal |
|
|
816
|
+
| `useUserPositionMultiChain(vault, user)` | `MultiChainUserPosition` | shares across hub + all spokes |
|
|
817
|
+
| `useLzFee(vault)` | `bigint` | Native fee required for async flows |
|
|
818
|
+
| `useAsyncRequestStatus(vault, guid)` | `AsyncRequestStatusInfo` | Status label for async request |
|
|
819
|
+
| `useVaultTopology(vault)` | `VaultTopology` | Hub/spoke chain structure |
|
|
820
|
+
| `useVaultDistribution(vault)` | `VaultDistribution` | TVL breakdown across chains |
|
|
821
|
+
| `useInboundRoutes(hubChainId, vault, asset, user)` | `InboundRoute[]` | Available deposit routes |
|
|
822
|
+
|
|
823
|
+
### Action hooks
|
|
824
|
+
|
|
825
|
+
| Hook | Description |
|
|
826
|
+
|------|-------------|
|
|
827
|
+
| `useSmartDeposit()` | Auto-routing deposit (sync or async) |
|
|
828
|
+
| `useSmartRedeem()` | Auto-routing redeem (sync or async) |
|
|
829
|
+
| `useDepositSimple()` | D1 — simple hub deposit |
|
|
830
|
+
| `useRedeemShares()` | R1 — standard hub redeem |
|
|
831
|
+
| `useOmniDeposit()` | Full omni-chain deposit with routing |
|
|
832
|
+
| `useOmniRedeem()` | Full omni-chain redeem with routing |
|
|
833
|
+
|
|
834
|
+
### Curator read hooks
|
|
835
|
+
|
|
836
|
+
| Hook | Returns | Description |
|
|
837
|
+
|------|---------|-------------|
|
|
838
|
+
| `useCuratorVaultStatus(vault)` | `CuratorVaultStatus` | Curator, timelock, nonce, assets, LZ adapter |
|
|
839
|
+
| `useVaultAnalysis(vault)` | `VaultAnalysis` | Available/depositable assets with metadata |
|
|
840
|
+
| `useVaultAssetBreakdown(vault)` | `VaultAssetBreakdown` | Per-asset balance breakdown |
|
|
841
|
+
| `usePendingActions(vault, nonce)` | `PendingAction` | Pending action batch with `isExecutable` flag |
|
|
842
|
+
| `useIsCurator(vault, address)` | `boolean` | Whether address is the current curator |
|
|
843
|
+
| `useProtocolWhitelist(vault, protocols)` | `Record<string, boolean>` | Protocol whitelist status |
|
|
844
|
+
|
|
845
|
+
### Curator write hooks
|
|
846
|
+
|
|
847
|
+
| Hook | Description |
|
|
848
|
+
|------|-------------|
|
|
849
|
+
| `useSubmitActions()` | Submit a batch of curator actions |
|
|
850
|
+
| `useExecuteActions()` | Execute queued actions after timelock |
|
|
851
|
+
| `useVetoActions()` | Guardian: cancel pending actions |
|
|
852
|
+
|
|
853
|
+
### React example
|
|
854
|
+
|
|
855
|
+
```tsx
|
|
856
|
+
import {
|
|
857
|
+
useVaultStatus,
|
|
858
|
+
useUserPosition,
|
|
859
|
+
useSmartDeposit,
|
|
860
|
+
} from '@oydual31/more-vaults-sdk/react'
|
|
861
|
+
import { parseUnits } from 'viem'
|
|
862
|
+
|
|
863
|
+
const VAULT = '0x8f740aba022b3fcc934ab75c581c04b75e72aba6'
|
|
864
|
+
|
|
865
|
+
function VaultDashboard() {
|
|
866
|
+
const { data: status } = useVaultStatus(VAULT)
|
|
867
|
+
const { data: position } = useUserPosition(VAULT, userAddress)
|
|
868
|
+
const { deposit, isPending } = useSmartDeposit()
|
|
869
|
+
|
|
870
|
+
const handleDeposit = () =>
|
|
871
|
+
deposit({ vault: VAULT }, parseUnits('100', 6), userAddress)
|
|
872
|
+
|
|
873
|
+
return (
|
|
874
|
+
<div>
|
|
875
|
+
<p>Mode: {status?.mode}</p>
|
|
876
|
+
<p>Your shares: {position?.shares?.toString()}</p>
|
|
877
|
+
<button onClick={handleDeposit} disabled={isPending}>Deposit 100 USDC</button>
|
|
878
|
+
</div>
|
|
879
|
+
)
|
|
880
|
+
}
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
---
|
|
884
|
+
|
|
885
|
+
## Stargate vs Standard OFT handling
|
|
886
|
+
|
|
887
|
+
The SDK auto-detects the OFT type via `detectStargateOft()`:
|
|
888
|
+
|
|
889
|
+
| OFT type | Examples | `extraOptions` | Compose delivery | User action after TX1 |
|
|
890
|
+
|----------|----------|---------------|-----------------|----------------------|
|
|
891
|
+
| **Stargate OFT** | stgUSDC, USDT, WETH | `'0x'` (empty) | Compose stays pending in LZ Endpoint `composeQueue` | Must execute TX2 on hub: `waitForCompose` → `executeCompose` |
|
|
892
|
+
| **Standard OFT** | Custom OFT adapters | LZCOMPOSE type-3 option injected with native ETH | LZ executor forwards ETH, compose auto-executes | No action needed |
|
|
893
|
+
|
|
894
|
+
Stargate's `TokenMessaging` contract rejects LZCOMPOSE type-3 executor options (`InvalidExecutorOption(3)`). The SDK handles this transparently — `depositFromSpoke` returns `composeData` when a 2nd TX is required.
|
|
895
|
+
|
|
896
|
+
**Detecting Stargate OFTs:**
|
|
897
|
+
```ts
|
|
898
|
+
import { detectStargateOft } from '@oydual31/more-vaults-sdk/viem'
|
|
899
|
+
|
|
900
|
+
const isStargate = await detectStargateOft(publicClient, oftAddress)
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
---
|
|
904
|
+
|
|
905
|
+
## Supported chains
|
|
906
|
+
|
|
907
|
+
Chains where the MoreVaults OMNI factory is deployed (`OMNI_FACTORY_ADDRESS = 0x7bDB8B17604b03125eFAED33cA0c55FBf856BB0C`):
|
|
908
|
+
|
|
909
|
+
| Chain | Chain ID | LZ EID | Native gas |
|
|
910
|
+
|-------|----------|--------|------------|
|
|
911
|
+
| Ethereum | 1 | 30101 | ETH |
|
|
912
|
+
| Arbitrum | 42161 | 30110 | ETH |
|
|
913
|
+
| Optimism | 10 | 30111 | ETH |
|
|
914
|
+
| Base | 8453 | 30184 | ETH |
|
|
915
|
+
| BNB Chain | 56 | 30102 | BNB |
|
|
916
|
+
| Sonic | 146 | 30332 | S |
|
|
917
|
+
| Flow EVM | 747 | 30336 | FLOW |
|
|
918
|
+
|
|
919
|
+
```ts
|
|
920
|
+
import { CHAIN_IDS, LZ_EIDS, EID_TO_CHAIN_ID, CHAIN_ID_TO_EID } from '@oydual31/more-vaults-sdk/viem'
|
|
921
|
+
|
|
922
|
+
CHAIN_IDS.BASE // 8453
|
|
923
|
+
LZ_EIDS.BASE // 30184
|
|
924
|
+
EID_TO_CHAIN_ID[30184] // 8453
|
|
925
|
+
CHAIN_ID_TO_EID[8453] // 30184
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
The `createChainTransport` and `createChainClient` helpers (exported from viem) build public-RPC clients for all supported chains using fallback transports:
|
|
929
|
+
|
|
930
|
+
```ts
|
|
931
|
+
import { createChainTransport } from '@oydual31/more-vaults-sdk/viem'
|
|
932
|
+
|
|
933
|
+
// Use with your own wallet client — useful for cross-chain flows
|
|
934
|
+
const transport = createChainTransport(8453)
|
|
935
|
+
const walletClient = createWalletClient({ account, chain: base, transport })
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
---
|
|
939
|
+
|
|
940
|
+
## LZ timeouts
|
|
941
|
+
|
|
942
|
+
Use these constants as timeout values in UI progress indicators:
|
|
371
943
|
|
|
372
944
|
```ts
|
|
373
945
|
import { LZ_TIMEOUTS } from '@oydual31/more-vaults-sdk/viem'
|
|
374
946
|
|
|
375
|
-
LZ_TIMEOUTS.POLL_INTERVAL //
|
|
947
|
+
LZ_TIMEOUTS.POLL_INTERVAL // 30 s — balance poll interval
|
|
376
948
|
LZ_TIMEOUTS.OFT_BRIDGE // 15 min — standard OFT bridge (shares or assets)
|
|
377
|
-
LZ_TIMEOUTS.STARGATE_BRIDGE // 30 min — Stargate bridge
|
|
949
|
+
LZ_TIMEOUTS.STARGATE_BRIDGE // 30 min — Stargate bridge
|
|
378
950
|
LZ_TIMEOUTS.LZ_READ_CALLBACK // 15 min — async deposit/redeem LZ Read callback
|
|
379
951
|
LZ_TIMEOUTS.COMPOSE_DELIVERY // 45 min — compose delivery to hub (spoke deposit)
|
|
380
|
-
LZ_TIMEOUTS.FULL_SPOKE_REDEEM // 60 min — full spoke
|
|
952
|
+
LZ_TIMEOUTS.FULL_SPOKE_REDEEM // 60 min — full spoke→hub→spoke redeem
|
|
381
953
|
```
|
|
382
954
|
|
|
383
|
-
|
|
955
|
+
Do not timeout before these values — cross-chain operations can legitimately take this long under network congestion.
|
|
384
956
|
|
|
385
957
|
---
|
|
386
958
|
|
|
387
959
|
## Pre-flight validation
|
|
388
960
|
|
|
389
|
-
Run pre-flight checks before submitting transactions to
|
|
961
|
+
Run pre-flight checks before submitting transactions to surface issues early with clear error messages:
|
|
390
962
|
|
|
391
963
|
```ts
|
|
392
|
-
import {
|
|
964
|
+
import {
|
|
965
|
+
preflightSync,
|
|
966
|
+
preflightAsync,
|
|
967
|
+
preflightRedeemLiquidity,
|
|
968
|
+
preflightSpokeDeposit,
|
|
969
|
+
preflightSpokeRedeem,
|
|
970
|
+
} from '@oydual31/more-vaults-sdk/viem'
|
|
971
|
+
|
|
972
|
+
// Before D1/D3 — sync hub deposit
|
|
973
|
+
await preflightSync(publicClient, vault, escrow)
|
|
974
|
+
// Validates: vault not paused, not full
|
|
975
|
+
|
|
976
|
+
// Before D4/D5/R5 — async flow
|
|
977
|
+
await preflightAsync(publicClient, vault, escrow)
|
|
978
|
+
// Validates: CCManager configured, escrow registered, isHub, oracle OFF, not paused
|
|
979
|
+
|
|
980
|
+
// Before R1/R2 — check hub has enough liquidity
|
|
981
|
+
await preflightRedeemLiquidity(publicClient, vault, assets)
|
|
982
|
+
// Throws InsufficientLiquidityError if hub liquid balance < assets
|
|
393
983
|
|
|
394
984
|
// Before spoke deposit
|
|
395
|
-
|
|
396
|
-
// Validates: spoke balance, gas, hub composer setup
|
|
985
|
+
await preflightSpokeDeposit(...)
|
|
986
|
+
// Validates: spoke balance, spoke gas (LZ fee), hub composer setup
|
|
397
987
|
|
|
398
988
|
// Before spoke redeem
|
|
399
989
|
const check = await preflightSpokeRedeem(route, shares, userAddress, shareBridgeFee)
|
|
400
|
-
// Validates: shares on spoke, spoke gas
|
|
401
|
-
// Returns: estimatedAssetBridgeFee, hubLiquidBalance
|
|
990
|
+
// Validates: shares on spoke, spoke gas, hub gas
|
|
991
|
+
// Returns: estimatedAssetBridgeFee, hubLiquidBalance
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
## Error types
|
|
997
|
+
|
|
998
|
+
All SDK errors extend `MoreVaultsError`. Import typed errors for `instanceof` checks:
|
|
999
|
+
|
|
1000
|
+
```ts
|
|
1001
|
+
import {
|
|
1002
|
+
MoreVaultsError,
|
|
1003
|
+
VaultPausedError,
|
|
1004
|
+
CapacityFullError,
|
|
1005
|
+
NotWhitelistedError,
|
|
1006
|
+
InsufficientLiquidityError,
|
|
1007
|
+
CCManagerNotConfiguredError,
|
|
1008
|
+
EscrowNotConfiguredError,
|
|
1009
|
+
NotHubVaultError,
|
|
1010
|
+
MissingEscrowAddressError,
|
|
1011
|
+
WrongChainError,
|
|
1012
|
+
} from '@oydual31/more-vaults-sdk/viem'
|
|
1013
|
+
|
|
1014
|
+
try {
|
|
1015
|
+
await smartDeposit(...)
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
if (err instanceof VaultPausedError) {
|
|
1018
|
+
// vault is paused
|
|
1019
|
+
} else if (err instanceof CapacityFullError) {
|
|
1020
|
+
// deposit capacity reached
|
|
1021
|
+
} else if (err instanceof InsufficientLiquidityError) {
|
|
1022
|
+
// hub doesn't have enough liquid assets to cover the redeem
|
|
1023
|
+
} else if (err instanceof WrongChainError) {
|
|
1024
|
+
// wallet is on the wrong chain
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
402
1027
|
```
|
|
403
1028
|
|
|
404
1029
|
---
|
|
405
1030
|
|
|
1031
|
+
## User helpers reference
|
|
1032
|
+
|
|
1033
|
+
| Function | Returns |
|
|
1034
|
+
|----------|---------|
|
|
1035
|
+
| `getUserPosition(publicClient, vault, user)` | `UserPosition` — shares, asset value, share price, pending withdrawal |
|
|
1036
|
+
| `getUserPositionMultiChain(hubClient, vault, user)` | `MultiChainUserPosition` — shares across hub + all spokes |
|
|
1037
|
+
| `previewDeposit(publicClient, vault, assets)` | `bigint` — estimated shares |
|
|
1038
|
+
| `previewRedeem(publicClient, vault, shares)` | `bigint` — estimated assets |
|
|
1039
|
+
| `canDeposit(publicClient, vault, user)` | `DepositEligibility` — `{ allowed, reason }` |
|
|
1040
|
+
| `getVaultMetadata(publicClient, vault)` | `VaultMetadata` — name, symbol, decimals, underlying, TVL, capacity |
|
|
1041
|
+
| `getVaultStatus(publicClient, vault)` | `VaultStatus` — full config + mode + recommended flow |
|
|
1042
|
+
| `quoteLzFee(publicClient, vault)` | `bigint` — native fee for D4/D5/R5 |
|
|
1043
|
+
| `getAsyncRequestStatusLabel(publicClient, vault, guid)` | `AsyncRequestStatusInfo` |
|
|
1044
|
+
| `getUserBalances(publicClient, vault, user)` | `UserBalances` — shares + underlying in one call |
|
|
1045
|
+
| `getMaxWithdrawable(publicClient, vault, user)` | `MaxWithdrawable` — max assets given hub liquidity |
|
|
1046
|
+
| `getVaultSummary(publicClient, vault, user)` | `VaultSummary` — metadata + status + position combined |
|
|
1047
|
+
|
|
1048
|
+
---
|
|
1049
|
+
|
|
406
1050
|
## Repo structure
|
|
407
1051
|
|
|
408
1052
|
```
|
|
409
1053
|
more-vaults-sdk/
|
|
410
1054
|
├── src/
|
|
411
|
-
│ ├── viem/
|
|
412
|
-
│ ├── ethers/
|
|
413
|
-
│ └── react/
|
|
1055
|
+
│ ├── viem/ — viem/wagmi SDK
|
|
1056
|
+
│ ├── ethers/ — ethers.js v6 SDK
|
|
1057
|
+
│ └── react/ — React hooks (wagmi)
|
|
414
1058
|
├── docs/
|
|
415
|
-
│ ├── flows/
|
|
1059
|
+
│ ├── flows/ — per-flow detailed documentation
|
|
416
1060
|
│ ├── user-helpers.md
|
|
417
1061
|
│ └── testing.md
|
|
418
|
-
├── scripts/
|
|
419
|
-
└── tests/
|
|
1062
|
+
├── scripts/ — E2E test scripts (mainnet)
|
|
1063
|
+
└── tests/ — integration tests (require Foundry + Anvil)
|
|
420
1064
|
```
|
|
421
1065
|
|
|
422
|
-
Integration tests:
|
|
1066
|
+
Integration tests: `bash tests/run.sh` — runs the full test suite against a forked mainnet.
|