@oydual31/more-vaults-sdk 0.1.16 → 0.2.1
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 +163 -75
- package/dist/ethers/index.cjs +127 -4
- package/dist/ethers/index.cjs.map +1 -1
- package/dist/ethers/index.d.cts +104 -24
- package/dist/ethers/index.d.ts +104 -24
- package/dist/ethers/index.js +122 -5
- package/dist/ethers/index.js.map +1 -1
- package/dist/react/index.cjs +427 -271
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +43 -2
- package/dist/react/index.d.ts +43 -2
- package/dist/react/index.js +427 -272
- package/dist/react/index.js.map +1 -1
- package/dist/{spokeRoutes-BFIxGa1h.d.cts → spokeRoutes-BFI1m_zk.d.cts} +14 -5
- package/dist/{spokeRoutes-BFIxGa1h.d.ts → spokeRoutes-BFI1m_zk.d.ts} +14 -5
- package/dist/viem/index.cjs +1480 -636
- package/dist/viem/index.cjs.map +1 -1
- package/dist/viem/index.d.cts +473 -24
- package/dist/viem/index.d.ts +473 -24
- package/dist/viem/index.js +1471 -638
- package/dist/viem/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ethers/abis.ts +5 -0
- package/src/ethers/chains.ts +19 -0
- package/src/ethers/crossChainFlows.ts +134 -29
- package/src/ethers/index.ts +6 -1
- package/src/ethers/redeemFlows.ts +109 -0
- package/src/react/index.ts +1 -0
- package/src/react/useSmartRedeem.ts +70 -0
- package/src/viem/abis.ts +93 -0
- package/src/viem/chains.ts +27 -0
- package/src/viem/crossChainFlows.ts +619 -24
- package/src/viem/depositFlows.ts +56 -18
- package/src/viem/index.ts +13 -2
- package/src/viem/preflight.ts +272 -1
- package/src/viem/redeemFlows.ts +327 -17
- package/src/viem/spokeRoutes.ts +58 -26
- package/src/viem/types.ts +38 -0
- package/src/viem/userHelpers.ts +15 -4
- package/src/viem/utils.ts +36 -1
package/README.md
CHANGED
|
@@ -8,10 +8,10 @@ npm install @oydual31/more-vaults-sdk
|
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
// viem / wagmi
|
|
11
|
-
import {
|
|
11
|
+
import { smartDeposit, smartRedeem, getVaultStatus } from '@oydual31/more-vaults-sdk/viem'
|
|
12
12
|
|
|
13
13
|
// ethers.js v6
|
|
14
|
-
import { getVaultStatus,
|
|
14
|
+
import { getVaultStatus, depositSimple } from '@oydual31/more-vaults-sdk/ethers'
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
---
|
|
@@ -44,8 +44,8 @@ Redeem 100 shares → receive 105 USDC
|
|
|
44
44
|
|
|
45
45
|
MoreVaults uses a **hub-and-spoke** model for cross-chain yield:
|
|
46
46
|
|
|
47
|
-
- **Hub** (`isHub = true`): the chain where the vault does its accounting — mints/burns shares, accepts deposits and redemptions. All SDK flows target the hub.
|
|
48
|
-
- **Spoke**: a position on another chain (Arbitrum, Base, etc.) where the vault has deployed funds for yield. Users on spoke chains bridge tokens to the hub via LayerZero OFT
|
|
47
|
+
- **Hub** (`isHub = true`): the chain where the vault does its accounting — mints/burns shares, accepts deposits and redemptions. All SDK flows target the hub.
|
|
48
|
+
- **Spoke**: a position on another chain (Arbitrum, Base, Ethereum, etc.) where the vault has deployed funds for yield. Users on spoke chains bridge tokens to the hub via LayerZero OFT.
|
|
49
49
|
|
|
50
50
|
If a vault has `isHub = false`, it is a single-chain vault — no cross-chain flows apply, use D1/R1.
|
|
51
51
|
|
|
@@ -79,11 +79,6 @@ This means:
|
|
|
79
79
|
|
|
80
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.
|
|
81
81
|
|
|
82
|
-
If a redeem fails because the hub is under-funded:
|
|
83
|
-
1. The user's shares are returned automatically (or the tx reverts before any state change for R1).
|
|
84
|
-
2. The vault curator must repatriate liquidity from the spokes.
|
|
85
|
-
3. The user retries the redeem once sufficient liquidity is available on the hub.
|
|
86
|
-
|
|
87
82
|
### Withdrawal queue and timelock
|
|
88
83
|
|
|
89
84
|
Some vaults require shares to be "queued" before redemption:
|
|
@@ -107,9 +102,7 @@ const escrow = status.escrow // address(0) if not configured
|
|
|
107
102
|
|
|
108
103
|
### Same address on every chain (CREATE3)
|
|
109
104
|
|
|
110
|
-
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
|
|
111
|
-
|
|
112
|
-
This simplifies the frontend significantly — you don't need a separate address map per chain. One address identifies the vault everywhere.
|
|
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.
|
|
113
106
|
|
|
114
107
|
### VaultAddresses
|
|
115
108
|
|
|
@@ -118,13 +111,12 @@ Every flow function takes a `VaultAddresses` object:
|
|
|
118
111
|
```ts
|
|
119
112
|
interface VaultAddresses {
|
|
120
113
|
vault: Address // Vault address — same on every chain (CREATE3)
|
|
121
|
-
escrow
|
|
122
|
-
|
|
123
|
-
usdcOFT?: Address // OFT for the underlying token on the spoke — required for D6/D7
|
|
114
|
+
escrow?: Address // MoreVaultsEscrow — required for D4, D5, R5 (auto-resolved if omitted)
|
|
115
|
+
hubChainId?: number // Optional chain validation
|
|
124
116
|
}
|
|
125
117
|
```
|
|
126
118
|
|
|
127
|
-
For simple hub flows (D1, R1) you only need `vault`. For async flows
|
|
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.
|
|
128
120
|
|
|
129
121
|
### LayerZero EID
|
|
130
122
|
|
|
@@ -132,10 +124,10 @@ LayerZero identifies chains by an **Endpoint ID (EID)** — different from the c
|
|
|
132
124
|
|
|
133
125
|
| Chain | Chain ID | LayerZero EID |
|
|
134
126
|
|-------|----------|---------------|
|
|
135
|
-
|
|
|
127
|
+
| Ethereum | 1 | 30101 |
|
|
136
128
|
| Arbitrum | 42161 | 30110 |
|
|
137
129
|
| Base | 8453 | 30184 |
|
|
138
|
-
|
|
|
130
|
+
| Flow EVM | 747 | 30332 |
|
|
139
131
|
|
|
140
132
|
### GUID (async request ID)
|
|
141
133
|
|
|
@@ -147,7 +139,6 @@ const { guid } = await depositAsync(...)
|
|
|
147
139
|
// Poll status
|
|
148
140
|
const info = await getAsyncRequestStatusLabel(publicClient, vault, guid)
|
|
149
141
|
// info.status: 'pending' | 'ready-to-execute' | 'completed' | 'refunded'
|
|
150
|
-
// info.result: shares minted or assets received once completed
|
|
151
142
|
```
|
|
152
143
|
|
|
153
144
|
---
|
|
@@ -177,88 +168,126 @@ const { data: walletClient } = useWalletClient()
|
|
|
177
168
|
| `new BrowserProvider(window.ethereum).getSigner()` | Browser — MetaMask or any injected wallet |
|
|
178
169
|
| `new Wallet(PRIVATE_KEY, new JsonRpcProvider(RPC_URL))` | Node.js — scripts, bots, backends |
|
|
179
170
|
|
|
180
|
-
Read-only helpers (`getUserPosition`, `previewDeposit`, etc.) accept a bare `Provider` in the ethers version — no signer needed.
|
|
181
|
-
|
|
182
171
|
> The client's chain must match the chain where the vault lives. Hub flows → the hub chain. Spoke deposit/redeem (D6/D7/R6) → the spoke chain.
|
|
183
172
|
|
|
184
173
|
---
|
|
185
174
|
|
|
186
|
-
## Quick start
|
|
175
|
+
## Quick start — Smart flows (recommended)
|
|
176
|
+
|
|
177
|
+
The simplest way to use the SDK. `smartDeposit` and `smartRedeem` auto-detect the vault type and use the correct flow.
|
|
187
178
|
|
|
188
179
|
### viem / wagmi
|
|
189
180
|
|
|
190
181
|
```ts
|
|
191
|
-
import {
|
|
182
|
+
import { smartDeposit, smartRedeem, getVaultStatus, LZ_TIMEOUTS } from '@oydual31/more-vaults-sdk/viem'
|
|
192
183
|
import { createPublicClient, createWalletClient, http, parseUnits } from 'viem'
|
|
184
|
+
import { base } from 'viem/chains'
|
|
193
185
|
|
|
194
|
-
const publicClient = createPublicClient({ chain:
|
|
195
|
-
const walletClient = createWalletClient({ account, chain:
|
|
196
|
-
|
|
197
|
-
// 1. Check vault status to know which flow to use
|
|
198
|
-
const status = await getVaultStatus(publicClient, VAULT_ADDRESS)
|
|
199
|
-
// status.mode → 'local' | 'cross-chain-oracle' | 'cross-chain-async' | 'paused' | 'full'
|
|
200
|
-
// status.recommendedDepositFlow → 'depositSimple' | 'depositAsync' | 'none'
|
|
201
|
-
// status.escrow → escrow address (needed for async flows)
|
|
186
|
+
const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) })
|
|
187
|
+
const walletClient = createWalletClient({ account, chain: base, transport: http(RPC_URL) })
|
|
202
188
|
|
|
203
|
-
const
|
|
189
|
+
const VAULT = '0x8f740aba022b3fcc934ab75c581c04b75e72aba6'
|
|
204
190
|
|
|
205
|
-
//
|
|
206
|
-
const
|
|
207
|
-
walletClient, publicClient,
|
|
191
|
+
// --- Deposit ---
|
|
192
|
+
const depositResult = await smartDeposit(
|
|
193
|
+
walletClient, publicClient,
|
|
194
|
+
{ vault: VAULT },
|
|
208
195
|
parseUnits('100', 6), // 100 USDC
|
|
209
196
|
account.address,
|
|
210
197
|
)
|
|
211
198
|
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
console.log('
|
|
215
|
-
console.log('
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
```ts
|
|
221
|
-
import { getVaultStatus, depositSimple, getUserPosition } from '@oydual31/more-vaults-sdk/ethers'
|
|
222
|
-
import { BrowserProvider, parseUnits } from 'ethers'
|
|
223
|
-
|
|
224
|
-
const provider = new BrowserProvider(window.ethereum)
|
|
225
|
-
const signer = await provider.getSigner()
|
|
226
|
-
|
|
227
|
-
const status = await getVaultStatus(provider, VAULT_ADDRESS)
|
|
228
|
-
const addresses = { vault: VAULT_ADDRESS, escrow: status.escrow }
|
|
199
|
+
// Check if async (LZ Read callback needed)
|
|
200
|
+
if ('guid' in depositResult) {
|
|
201
|
+
console.log('Async deposit — waiting for LZ callback (~5 min)')
|
|
202
|
+
console.log('GUID:', depositResult.guid)
|
|
203
|
+
// Poll for shares using LZ_TIMEOUTS.LZ_READ_CALLBACK as timeout
|
|
204
|
+
} else {
|
|
205
|
+
console.log('Sync deposit — shares:', depositResult.shares)
|
|
206
|
+
}
|
|
229
207
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
208
|
+
// --- Redeem ---
|
|
209
|
+
const redeemResult = await smartRedeem(
|
|
210
|
+
walletClient, publicClient,
|
|
211
|
+
{ vault: VAULT },
|
|
212
|
+
shares,
|
|
213
|
+
account.address,
|
|
214
|
+
account.address,
|
|
234
215
|
)
|
|
216
|
+
|
|
217
|
+
if ('guid' in redeemResult) {
|
|
218
|
+
console.log('Async redeem — waiting for LZ callback (~5 min)')
|
|
219
|
+
// Poll for USDC balance increase
|
|
220
|
+
} else {
|
|
221
|
+
console.log('Sync redeem — assets:', redeemResult.assets)
|
|
222
|
+
}
|
|
235
223
|
```
|
|
236
224
|
|
|
237
225
|
---
|
|
238
226
|
|
|
239
227
|
## Flows
|
|
240
228
|
|
|
241
|
-
###
|
|
229
|
+
### Smart flows (auto-detection)
|
|
230
|
+
|
|
231
|
+
| Function | Description |
|
|
232
|
+
|----------|-------------|
|
|
233
|
+
| `smartDeposit` | Auto-detects vault mode → `depositSimple` (sync) or `depositAsync` (async) |
|
|
234
|
+
| `smartRedeem` | Auto-detects vault mode → `redeemShares` (sync) or `redeemAsync` (async) |
|
|
235
|
+
|
|
236
|
+
### Hub-chain deposit
|
|
242
237
|
|
|
243
238
|
| ID | Function | When to use | Doc |
|
|
244
239
|
|----|----------|-------------|-----|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
|
|
|
240
|
+
| — | `smartDeposit` | **Recommended.** Auto-detects vault type. | — |
|
|
241
|
+
| D1 | `depositSimple` | User on hub chain, oracle ON or local vault | [->](./docs/flows/D1-deposit-simple.md) |
|
|
242
|
+
| D2 | `depositMultiAsset` | Deposit multiple tokens in one call | [->](./docs/flows/D2-deposit-multi-asset.md) |
|
|
243
|
+
| D3 | `depositCrossChainOracleOn` | Alias for D1 — hub with oracle ON | [->](./docs/flows/D3-deposit-oracle-on.md) |
|
|
244
|
+
| D4 | `depositAsync` | Hub with oracle OFF — async LZ Read | [->](./docs/flows/D4-deposit-async.md) |
|
|
245
|
+
| D5 | `mintAsync` | Same as D4 but user specifies exact share amount | [->](./docs/flows/D5-mint-async.md) |
|
|
246
|
+
|
|
247
|
+
### Cross-chain deposit (spoke -> hub)
|
|
248
|
+
|
|
249
|
+
| ID | Function | When to use | Doc |
|
|
250
|
+
|----|----------|-------------|-----|
|
|
251
|
+
| D6/D7 | `depositFromSpoke` | User on spoke chain — tokens bridge via LZ OFT | [->](./docs/flows/D6-D7-deposit-from-spoke.md) |
|
|
252
|
+
|
|
253
|
+
For Stargate OFTs (stgUSDC, USDT, WETH), `depositFromSpoke` returns `composeData` — the user must execute a 2nd TX on the hub via `waitForCompose` + `executeCompose`. For standard OFTs, compose auto-executes in 1 TX.
|
|
251
254
|
|
|
252
|
-
###
|
|
255
|
+
### Hub-chain redeem
|
|
253
256
|
|
|
254
257
|
| ID | Function | When to use | Doc |
|
|
255
258
|
|----|----------|-------------|-----|
|
|
256
|
-
|
|
|
257
|
-
|
|
|
258
|
-
|
|
|
259
|
-
|
|
|
260
|
-
|
|
|
261
|
-
|
|
|
259
|
+
| — | `smartRedeem` | **Recommended.** Auto-detects vault type. | — |
|
|
260
|
+
| R1 | `redeemShares` | Standard redeem, hub chain, no queue | [->](./docs/flows/R1-redeem-shares.md) |
|
|
261
|
+
| R2 | `withdrawAssets` | Specify exact asset amount to receive | [->](./docs/flows/R2-withdraw-assets.md) |
|
|
262
|
+
| R3 | `requestRedeem` | Withdrawal queue enabled, no timelock | [->](./docs/flows/R3-R4-request-redeem.md) |
|
|
263
|
+
| R4 | `requestRedeem` | Withdrawal queue + mandatory wait period | [->](./docs/flows/R3-R4-request-redeem.md) |
|
|
264
|
+
| R5 | `redeemAsync` | Hub with oracle OFF — async LZ Read | [->](./docs/flows/R5-redeem-async.md) |
|
|
265
|
+
|
|
266
|
+
### Cross-chain redeem (spoke -> hub -> spoke)
|
|
267
|
+
|
|
268
|
+
Full spoke redeem is a 3-step flow across two chains:
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
Step 1 (Spoke): bridgeSharesToHub() — shares spoke->hub via SHARE_OFT (~7 min)
|
|
272
|
+
Step 2 (Hub): smartRedeem() — redeem on hub (auto-detects async, ~5 min callback)
|
|
273
|
+
Step 3 (Hub): bridgeAssetsToSpoke() — assets hub->spoke via Stargate/OFT (~13 min)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
| Function | Step | Doc |
|
|
277
|
+
|----------|------|-----|
|
|
278
|
+
| `resolveRedeemAddresses` | Pre-step: discover all addresses dynamically | — |
|
|
279
|
+
| `preflightSpokeRedeem` | Pre-step: validate balances + gas on both chains | — |
|
|
280
|
+
| `bridgeSharesToHub` | Step 1: bridge shares spoke->hub | [->](./docs/flows/R6-bridge-shares-to-hub.md) |
|
|
281
|
+
| `smartRedeem` | Step 2: redeem on hub | — |
|
|
282
|
+
| `bridgeAssetsToSpoke` | Step 3: bridge assets hub->spoke | [->](./docs/flows/R7-bridge-assets-to-spoke.md) |
|
|
283
|
+
|
|
284
|
+
### Compose helpers (Stargate 2-TX flow)
|
|
285
|
+
|
|
286
|
+
| Function | Description |
|
|
287
|
+
|----------|-------------|
|
|
288
|
+
| `waitForCompose` | Wait for pending compose in LZ Endpoint's composeQueue |
|
|
289
|
+
| `quoteComposeFee` | Quote ETH needed for `executeCompose` (readFee + shareSendFee) |
|
|
290
|
+
| `executeCompose` | Execute pending compose on hub chain |
|
|
262
291
|
|
|
263
292
|
### User helpers (read-only, no gas)
|
|
264
293
|
|
|
@@ -274,6 +303,64 @@ Full reference: [docs/user-helpers.md](./docs/user-helpers.md)
|
|
|
274
303
|
| `getVaultStatus` | full config snapshot + recommended flow + issues list |
|
|
275
304
|
| `quoteLzFee` | native fee required for D4, D5, R5 |
|
|
276
305
|
| `getAsyncRequestStatusLabel` | pending / ready-to-execute / completed / refunded |
|
|
306
|
+
| `getUserBalances` | shares + underlying balance in one call |
|
|
307
|
+
| `getMaxWithdrawable` | max assets redeemable given hub liquidity |
|
|
308
|
+
| `getVaultSummary` | metadata + status + user position combined |
|
|
309
|
+
|
|
310
|
+
### Spoke route discovery
|
|
311
|
+
|
|
312
|
+
| Function | Description |
|
|
313
|
+
|----------|-------------|
|
|
314
|
+
| `getInboundRoutes` | All routes a user can deposit from (which chains/tokens) |
|
|
315
|
+
| `getUserBalancesForRoutes` | User balances for all inbound routes |
|
|
316
|
+
| `getOutboundRoutes` | All routes for spoke redeem (which chains to bridge back to) |
|
|
317
|
+
| `quoteRouteDepositFee` | LZ fee for a specific inbound route |
|
|
318
|
+
| `resolveRedeemAddresses` | Discover SHARE_OFT, asset OFT, spoke asset for a redeem route |
|
|
319
|
+
|
|
320
|
+
### Topology & distribution
|
|
321
|
+
|
|
322
|
+
| Function | Description |
|
|
323
|
+
|----------|-------------|
|
|
324
|
+
| `getVaultTopology` | Hub/spoke chain IDs, OFT routes, composer addresses |
|
|
325
|
+
| `getFullVaultTopology` | Topology + all on-chain config |
|
|
326
|
+
| `getVaultDistribution` | TVL breakdown across hub + all spokes |
|
|
327
|
+
| `isOnHubChain` | Check if user is on the hub chain |
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## LZ Timeouts (for UI integration)
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
import { LZ_TIMEOUTS } from '@oydual31/more-vaults-sdk/viem'
|
|
335
|
+
|
|
336
|
+
LZ_TIMEOUTS.POLL_INTERVAL // 30s — interval between balance polls
|
|
337
|
+
LZ_TIMEOUTS.OFT_BRIDGE // 15 min — standard OFT bridge (shares or assets)
|
|
338
|
+
LZ_TIMEOUTS.STARGATE_BRIDGE // 30 min — Stargate bridge (slower, pool mechanics)
|
|
339
|
+
LZ_TIMEOUTS.LZ_READ_CALLBACK // 15 min — async deposit/redeem LZ Read callback
|
|
340
|
+
LZ_TIMEOUTS.COMPOSE_DELIVERY // 45 min — compose delivery to hub (spoke deposit)
|
|
341
|
+
LZ_TIMEOUTS.FULL_SPOKE_REDEEM // 60 min — full spoke->hub->spoke redeem
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Use these as timeout values in UI progress indicators. Do NOT timeout before these values — cross-chain operations can legitimately take this long.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Pre-flight validation
|
|
349
|
+
|
|
350
|
+
Run pre-flight checks before submitting transactions to catch issues early:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
import { preflightSpokeDeposit, preflightSpokeRedeem } from '@oydual31/more-vaults-sdk/viem'
|
|
354
|
+
|
|
355
|
+
// Before spoke deposit
|
|
356
|
+
const check = await preflightSpokeDeposit(...)
|
|
357
|
+
// Validates: spoke balance, gas, hub composer setup
|
|
358
|
+
|
|
359
|
+
// Before spoke redeem
|
|
360
|
+
const check = await preflightSpokeRedeem(route, shares, userAddress, shareBridgeFee)
|
|
361
|
+
// Validates: shares on spoke, spoke gas (LZ fee + buffer), hub gas (asset bridge fee)
|
|
362
|
+
// Returns: estimatedAssetBridgeFee, hubLiquidBalance, etc.
|
|
363
|
+
```
|
|
277
364
|
|
|
278
365
|
---
|
|
279
366
|
|
|
@@ -282,14 +369,15 @@ Full reference: [docs/user-helpers.md](./docs/user-helpers.md)
|
|
|
282
369
|
```
|
|
283
370
|
more-vaults-sdk/
|
|
284
371
|
├── src/
|
|
285
|
-
│ ├── viem/
|
|
286
|
-
│
|
|
372
|
+
│ ├── viem/ <- viem/wagmi SDK
|
|
373
|
+
│ ├── ethers/ <- ethers.js v6 SDK
|
|
374
|
+
│ └── react/ <- React hooks (wagmi)
|
|
287
375
|
├── docs/
|
|
288
|
-
│ ├── flows/
|
|
376
|
+
│ ├── flows/ <- one .md per flow with detailed examples
|
|
289
377
|
│ ├── user-helpers.md
|
|
290
378
|
│ └── testing.md
|
|
291
|
-
├──
|
|
292
|
-
└──
|
|
379
|
+
├── scripts/ <- E2E test scripts (mainnet)
|
|
380
|
+
└── tests/ <- integration tests (require Foundry + Anvil)
|
|
293
381
|
```
|
|
294
382
|
|
|
295
383
|
Integration tests: [docs/testing.md](./docs/testing.md) — `bash tests/run.sh` runs all 43 tests.
|
package/dist/ethers/index.cjs
CHANGED
|
@@ -31,6 +31,25 @@ var CHAIN_ID_TO_EID = {
|
|
|
31
31
|
[CHAIN_IDS.base]: LZ_EIDS.base,
|
|
32
32
|
[CHAIN_IDS.ethereum]: LZ_EIDS.ethereum
|
|
33
33
|
};
|
|
34
|
+
var LZ_TIMEOUTS = {
|
|
35
|
+
/** Poll interval between balance/event checks */
|
|
36
|
+
POLL_INTERVAL: 3e4,
|
|
37
|
+
/** Standard OFT bridge (shares or assets, non-Stargate) */
|
|
38
|
+
OFT_BRIDGE: 9e5,
|
|
39
|
+
// 15 min
|
|
40
|
+
/** Stargate bridge (USDC, USDT, WETH) — slower due to pool mechanics */
|
|
41
|
+
STARGATE_BRIDGE: 18e5,
|
|
42
|
+
// 30 min
|
|
43
|
+
/** LZ Read callback (async vault actions) */
|
|
44
|
+
LZ_READ_CALLBACK: 9e5,
|
|
45
|
+
// 15 min
|
|
46
|
+
/** Compose delivery to hub (deposit from spoke) */
|
|
47
|
+
COMPOSE_DELIVERY: 27e5,
|
|
48
|
+
// 45 min
|
|
49
|
+
/** Full spoke→hub→spoke redeem (all steps combined) */
|
|
50
|
+
FULL_SPOKE_REDEEM: 36e5
|
|
51
|
+
// 60 min
|
|
52
|
+
};
|
|
34
53
|
|
|
35
54
|
// src/ethers/types.ts
|
|
36
55
|
var ActionType = {
|
|
@@ -111,6 +130,10 @@ var OFT_ABI = [
|
|
|
111
130
|
"function send(tuple(uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam, tuple(uint256 nativeFee, uint256 lzTokenFee) fee, address refundAddress) payable returns (tuple(bytes32 guid, uint64 nonce, uint256 amountSentLD, uint256 amountReceivedLD) receipt, tuple(uint256 nativeFee, uint256 lzTokenFee) fee)",
|
|
112
131
|
"function quoteSend(tuple(uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam, bool payInLzToken) view returns (tuple(uint256 nativeFee, uint256 lzTokenFee))"
|
|
113
132
|
];
|
|
133
|
+
var LZ_ENDPOINT_ABI = [
|
|
134
|
+
"function composeQueue(address from, address to, bytes32 guid, uint16 index) view returns (bytes32 messageHash)",
|
|
135
|
+
"function lzCompose(address _from, address _to, bytes32 _guid, uint16 _index, bytes _message, bytes _extraData) payable"
|
|
136
|
+
];
|
|
114
137
|
|
|
115
138
|
// src/ethers/errors.ts
|
|
116
139
|
var MoreVaultsError = class extends Error {
|
|
@@ -588,6 +611,9 @@ async function smartDeposit(signer, provider, addresses, assets, receiver, extra
|
|
|
588
611
|
}
|
|
589
612
|
return depositSimple(signer, addresses, assets, receiver);
|
|
590
613
|
}
|
|
614
|
+
var LZ_ENDPOINT = "0x1a44076050125825900e736c501f859c50fe728c";
|
|
615
|
+
var EMPTY_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
616
|
+
var RECEIVED_HASH = "0x0000000000000000000000000000000000000000000000000000000000000001";
|
|
591
617
|
async function ensureAllowance3(signer, token, spender, amount) {
|
|
592
618
|
const owner = await signer.getAddress();
|
|
593
619
|
const erc20 = new ethers.Contract(token, ERC20_ABI, signer);
|
|
@@ -597,11 +623,12 @@ async function ensureAllowance3(signer, token, spender, amount) {
|
|
|
597
623
|
await tx.wait();
|
|
598
624
|
}
|
|
599
625
|
}
|
|
600
|
-
async function depositFromSpoke(signer, spokeOFT, hubEid, spokeEid, amount, receiver, lzFee, minMsgValue = 0n, minSharesOut = 0n, minAmountLD, extraOptions = "0x") {
|
|
626
|
+
async function depositFromSpoke(signer, spokeOFT, composer, hubEid, spokeEid, amount, receiver, lzFee, minMsgValue = 0n, minSharesOut = 0n, minAmountLD, extraOptions = "0x") {
|
|
601
627
|
await ensureAllowance3(signer, spokeOFT, spokeOFT, amount);
|
|
602
628
|
const oft = new ethers.Contract(spokeOFT, OFT_ABI, signer);
|
|
603
629
|
const refundAddress = await signer.getAddress();
|
|
604
630
|
const receiverBytes32 = ethers.zeroPadValue(receiver, 32);
|
|
631
|
+
const composerBytes32 = ethers.zeroPadValue(composer, 32);
|
|
605
632
|
const hopSendParam = {
|
|
606
633
|
dstEid: spokeEid,
|
|
607
634
|
to: receiverBytes32,
|
|
@@ -621,7 +648,7 @@ async function depositFromSpoke(signer, spokeOFT, hubEid, spokeEid, amount, rece
|
|
|
621
648
|
);
|
|
622
649
|
const sendParam = {
|
|
623
650
|
dstEid: hubEid,
|
|
624
|
-
to:
|
|
651
|
+
to: composerBytes32,
|
|
625
652
|
amountLD: amount,
|
|
626
653
|
minAmountLD: minAmountLD ?? amount,
|
|
627
654
|
extraOptions,
|
|
@@ -636,8 +663,9 @@ async function depositFromSpoke(signer, spokeOFT, hubEid, spokeEid, amount, rece
|
|
|
636
663
|
return { receipt };
|
|
637
664
|
}
|
|
638
665
|
var depositFromSpokeAsync = depositFromSpoke;
|
|
639
|
-
async function quoteDepositFromSpokeFee(provider, spokeOFT, hubEid, spokeEid, amount, receiver, minMsgValue = 0n, minSharesOut = 0n, minAmountLD, extraOptions = "0x") {
|
|
666
|
+
async function quoteDepositFromSpokeFee(provider, spokeOFT, composer, hubEid, spokeEid, amount, receiver, minMsgValue = 0n, minSharesOut = 0n, minAmountLD, extraOptions = "0x") {
|
|
640
667
|
const receiverBytes32 = ethers.zeroPadValue(receiver, 32);
|
|
668
|
+
const composerBytes32 = ethers.zeroPadValue(composer, 32);
|
|
641
669
|
const hopSendParam = {
|
|
642
670
|
dstEid: spokeEid,
|
|
643
671
|
to: receiverBytes32,
|
|
@@ -665,7 +693,7 @@ async function quoteDepositFromSpokeFee(provider, spokeOFT, hubEid, spokeEid, am
|
|
|
665
693
|
);
|
|
666
694
|
const sendParam = {
|
|
667
695
|
dstEid: hubEid,
|
|
668
|
-
to:
|
|
696
|
+
to: composerBytes32,
|
|
669
697
|
amountLD: amount,
|
|
670
698
|
minAmountLD: minAmountLD ?? amount,
|
|
671
699
|
extraOptions,
|
|
@@ -676,6 +704,55 @@ async function quoteDepositFromSpokeFee(provider, spokeOFT, hubEid, spokeEid, am
|
|
|
676
704
|
const fee = await oft.quoteSend(sendParam, false);
|
|
677
705
|
return fee.nativeFee;
|
|
678
706
|
}
|
|
707
|
+
async function quoteComposeFee(provider, vault, spokeEid, receiver) {
|
|
708
|
+
try {
|
|
709
|
+
const vaultContract = new ethers.Contract(vault, BRIDGE_ABI, provider);
|
|
710
|
+
const readFee = await vaultContract.quoteAccountingFee("0x");
|
|
711
|
+
let shareSendFee = 0n;
|
|
712
|
+
if (spokeEid && receiver) {
|
|
713
|
+
try {
|
|
714
|
+
const COMPOSER_ABI = ["function SHARE_OFT() view returns (address)"];
|
|
715
|
+
const FACTORY_ABI = ["function vaultComposer(address _vault) view returns (address)"];
|
|
716
|
+
const factory = new ethers.Contract("0x7bDB8B17604b03125eFAED33cA0c55FBf856BB0C", FACTORY_ABI, provider);
|
|
717
|
+
const composerAddr = await factory.vaultComposer(vault);
|
|
718
|
+
const composer = new ethers.Contract(composerAddr, COMPOSER_ABI, provider);
|
|
719
|
+
const shareOftAddr = await composer.SHARE_OFT();
|
|
720
|
+
const shareOft = new ethers.Contract(shareOftAddr, OFT_ABI, provider);
|
|
721
|
+
const receiverBytes32 = ethers.zeroPadValue(receiver, 32);
|
|
722
|
+
const fee = await shareOft.quoteSend({
|
|
723
|
+
dstEid: spokeEid,
|
|
724
|
+
to: receiverBytes32,
|
|
725
|
+
amountLD: 1000000n,
|
|
726
|
+
minAmountLD: 0n,
|
|
727
|
+
extraOptions: "0x",
|
|
728
|
+
composeMsg: "0x",
|
|
729
|
+
oftCmd: "0x"
|
|
730
|
+
}, false);
|
|
731
|
+
shareSendFee = fee.nativeFee;
|
|
732
|
+
} catch {
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return (readFee + shareSendFee) * 110n / 100n;
|
|
736
|
+
} catch {
|
|
737
|
+
return 500000000000000n;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async function executeCompose(signer, from, to, guid, message, fee, index = 0) {
|
|
741
|
+
const endpoint = new ethers.Contract(LZ_ENDPOINT, LZ_ENDPOINT_ABI, signer);
|
|
742
|
+
const hash = await endpoint.composeQueue(from, to, guid, index);
|
|
743
|
+
if (hash === EMPTY_HASH) {
|
|
744
|
+
throw new Error("Compose not found in queue (hash = 0). Never sent or wrong parameters.");
|
|
745
|
+
}
|
|
746
|
+
if (hash === RECEIVED_HASH) {
|
|
747
|
+
throw new Error("Compose already delivered \u2014 no action needed.");
|
|
748
|
+
}
|
|
749
|
+
const tx = await endpoint.lzCompose(from, to, guid, index, message, "0x", {
|
|
750
|
+
value: fee,
|
|
751
|
+
gasLimit: 5000000n
|
|
752
|
+
});
|
|
753
|
+
const receipt = await tx.wait();
|
|
754
|
+
return { receipt };
|
|
755
|
+
}
|
|
679
756
|
async function ensureAllowance4(signer, token, spender, amount) {
|
|
680
757
|
const owner = await signer.getAddress();
|
|
681
758
|
const erc20 = new ethers.Contract(token, ERC20_ABI, signer);
|
|
@@ -769,6 +846,46 @@ async function bridgeSharesToHub(signer, shareOFT, hubChainEid, shares, receiver
|
|
|
769
846
|
const receipt = await tx.wait();
|
|
770
847
|
return { receipt };
|
|
771
848
|
}
|
|
849
|
+
async function smartRedeem(signer, addresses, shares, receiver, owner, extraOptions = "0x") {
|
|
850
|
+
const provider = signer.provider;
|
|
851
|
+
const vault = addresses.vault;
|
|
852
|
+
const status = await getVaultStatus(provider, vault);
|
|
853
|
+
if (status.mode === "paused") {
|
|
854
|
+
throw new Error(`[MoreVaults] Vault ${vault} is paused. Cannot redeem.`);
|
|
855
|
+
}
|
|
856
|
+
if (status.recommendedDepositFlow === "depositAsync") {
|
|
857
|
+
const lzFee = await quoteLzFee(provider, vault, extraOptions);
|
|
858
|
+
return redeemAsync(signer, addresses, shares, receiver, owner, lzFee, extraOptions);
|
|
859
|
+
}
|
|
860
|
+
return redeemShares(signer, addresses, shares, receiver, owner);
|
|
861
|
+
}
|
|
862
|
+
async function bridgeAssetsToSpoke(signer, assetOFT, spokeChainEid, amount, receiver, lzFee, isStargate = true) {
|
|
863
|
+
const oft = new ethers.Contract(assetOFT, OFT_ABI, signer);
|
|
864
|
+
const token = await oft.token();
|
|
865
|
+
if (token.toLowerCase() !== assetOFT.toLowerCase()) {
|
|
866
|
+
await ensureAllowance4(signer, token, assetOFT, amount);
|
|
867
|
+
} else {
|
|
868
|
+
await ensureAllowance4(signer, assetOFT, assetOFT, amount);
|
|
869
|
+
}
|
|
870
|
+
const refundAddress = await signer.getAddress();
|
|
871
|
+
const toBytes32 = ethers.zeroPadValue(receiver, 32);
|
|
872
|
+
const sendParam = {
|
|
873
|
+
dstEid: spokeChainEid,
|
|
874
|
+
to: toBytes32,
|
|
875
|
+
amountLD: amount,
|
|
876
|
+
minAmountLD: amount * 99n / 100n,
|
|
877
|
+
// 1% slippage for Stargate
|
|
878
|
+
extraOptions: "0x",
|
|
879
|
+
composeMsg: "0x",
|
|
880
|
+
oftCmd: isStargate ? "0x01" : "0x"
|
|
881
|
+
};
|
|
882
|
+
const msgFee = { nativeFee: lzFee, lzTokenFee: 0n };
|
|
883
|
+
const tx = await oft.send(sendParam, msgFee, refundAddress, {
|
|
884
|
+
value: lzFee
|
|
885
|
+
});
|
|
886
|
+
const receipt = await tx.wait();
|
|
887
|
+
return { receipt };
|
|
888
|
+
}
|
|
772
889
|
var MULTICALL3_ADDRESS2 = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
773
890
|
var MULTICALL3_ABI2 = [
|
|
774
891
|
"function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)"
|
|
@@ -998,6 +1115,8 @@ exports.ERC20_ABI = ERC20_ABI;
|
|
|
998
1115
|
exports.EscrowNotConfiguredError = EscrowNotConfiguredError;
|
|
999
1116
|
exports.InsufficientLiquidityError = InsufficientLiquidityError;
|
|
1000
1117
|
exports.LZ_EIDS = LZ_EIDS;
|
|
1118
|
+
exports.LZ_ENDPOINT_ABI = LZ_ENDPOINT_ABI;
|
|
1119
|
+
exports.LZ_TIMEOUTS = LZ_TIMEOUTS;
|
|
1001
1120
|
exports.METADATA_ABI = METADATA_ABI;
|
|
1002
1121
|
exports.MissingEscrowAddressError = MissingEscrowAddressError;
|
|
1003
1122
|
exports.MoreVaultsError = MoreVaultsError;
|
|
@@ -1008,6 +1127,7 @@ exports.VAULT_ABI = VAULT_ABI;
|
|
|
1008
1127
|
exports.VaultPausedError = VaultPausedError;
|
|
1009
1128
|
exports.WrongChainError = WrongChainError;
|
|
1010
1129
|
exports.asSdkSigner = asSdkSigner;
|
|
1130
|
+
exports.bridgeAssetsToSpoke = bridgeAssetsToSpoke;
|
|
1011
1131
|
exports.bridgeSharesToHub = bridgeSharesToHub;
|
|
1012
1132
|
exports.canDeposit = canDeposit;
|
|
1013
1133
|
exports.depositAsync = depositAsync;
|
|
@@ -1017,6 +1137,7 @@ exports.depositFromSpokeAsync = depositFromSpokeAsync;
|
|
|
1017
1137
|
exports.depositMultiAsset = depositMultiAsset;
|
|
1018
1138
|
exports.depositSimple = depositSimple;
|
|
1019
1139
|
exports.ensureAllowance = ensureAllowance;
|
|
1140
|
+
exports.executeCompose = executeCompose;
|
|
1020
1141
|
exports.getAsyncRequestStatus = getAsyncRequestStatus;
|
|
1021
1142
|
exports.getAsyncRequestStatusLabel = getAsyncRequestStatusLabel;
|
|
1022
1143
|
exports.getMaxWithdrawable = getMaxWithdrawable;
|
|
@@ -1033,12 +1154,14 @@ exports.preflightRedeemLiquidity = preflightRedeemLiquidity;
|
|
|
1033
1154
|
exports.preflightSync = preflightSync;
|
|
1034
1155
|
exports.previewDeposit = previewDeposit;
|
|
1035
1156
|
exports.previewRedeem = previewRedeem;
|
|
1157
|
+
exports.quoteComposeFee = quoteComposeFee;
|
|
1036
1158
|
exports.quoteDepositFromSpokeFee = quoteDepositFromSpokeFee;
|
|
1037
1159
|
exports.quoteLzFee = quoteLzFee;
|
|
1038
1160
|
exports.redeemAsync = redeemAsync;
|
|
1039
1161
|
exports.redeemShares = redeemShares;
|
|
1040
1162
|
exports.requestRedeem = requestRedeem;
|
|
1041
1163
|
exports.smartDeposit = smartDeposit;
|
|
1164
|
+
exports.smartRedeem = smartRedeem;
|
|
1042
1165
|
exports.withdrawAssets = withdrawAssets;
|
|
1043
1166
|
//# sourceMappingURL=index.cjs.map
|
|
1044
1167
|
//# sourceMappingURL=index.cjs.map
|