@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.
Files changed (40) hide show
  1. package/README.md +163 -75
  2. package/dist/ethers/index.cjs +127 -4
  3. package/dist/ethers/index.cjs.map +1 -1
  4. package/dist/ethers/index.d.cts +104 -24
  5. package/dist/ethers/index.d.ts +104 -24
  6. package/dist/ethers/index.js +122 -5
  7. package/dist/ethers/index.js.map +1 -1
  8. package/dist/react/index.cjs +427 -271
  9. package/dist/react/index.cjs.map +1 -1
  10. package/dist/react/index.d.cts +43 -2
  11. package/dist/react/index.d.ts +43 -2
  12. package/dist/react/index.js +427 -272
  13. package/dist/react/index.js.map +1 -1
  14. package/dist/{spokeRoutes-BFIxGa1h.d.cts → spokeRoutes-BFI1m_zk.d.cts} +14 -5
  15. package/dist/{spokeRoutes-BFIxGa1h.d.ts → spokeRoutes-BFI1m_zk.d.ts} +14 -5
  16. package/dist/viem/index.cjs +1480 -636
  17. package/dist/viem/index.cjs.map +1 -1
  18. package/dist/viem/index.d.cts +473 -24
  19. package/dist/viem/index.d.ts +473 -24
  20. package/dist/viem/index.js +1471 -638
  21. package/dist/viem/index.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/ethers/abis.ts +5 -0
  24. package/src/ethers/chains.ts +19 -0
  25. package/src/ethers/crossChainFlows.ts +134 -29
  26. package/src/ethers/index.ts +6 -1
  27. package/src/ethers/redeemFlows.ts +109 -0
  28. package/src/react/index.ts +1 -0
  29. package/src/react/useSmartRedeem.ts +70 -0
  30. package/src/viem/abis.ts +93 -0
  31. package/src/viem/chains.ts +27 -0
  32. package/src/viem/crossChainFlows.ts +619 -24
  33. package/src/viem/depositFlows.ts +56 -18
  34. package/src/viem/index.ts +13 -2
  35. package/src/viem/preflight.ts +272 -1
  36. package/src/viem/redeemFlows.ts +327 -17
  37. package/src/viem/spokeRoutes.ts +58 -26
  38. package/src/viem/types.ts +38 -0
  39. package/src/viem/userHelpers.ts +15 -4
  40. 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 { getVaultStatus, smartDeposit, getUserBalances } from '@oydual31/more-vaults-sdk/viem'
11
+ import { smartDeposit, smartRedeem, getVaultStatus } from '@oydual31/more-vaults-sdk/viem'
12
12
 
13
13
  // ethers.js v6
14
- import { getVaultStatus, smartDeposit, getUserBalances } from '@oydual31/more-vaults-sdk/ethers'
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. Flow EVM is the current reference deployment, but any EVM chain can be 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 — they never interact with the spoke contracts directly.
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 Flow EVM is `0xABC...`, the corresponding escrow and spoke-side contracts are also at predictable, identical addresses across Arbitrum, Base, etc.
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: Address // MoreVaultsEscrow — same address as vault on each chain, required for D4, D5, R5
122
- shareOFT?: Address // OFTAdapter for vault shares — required for R6 (spoke redeem)
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 you also need `escrow` — get it from `getVaultStatus(publicClient, vault).escrow`. For cross-chain flows from a spoke you also need the OFT addresses for that specific spoke chain.
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
- | Flow EVM | 747 | 30332 |
127
+ | Ethereum | 1 | 30101 |
136
128
  | Arbitrum | 42161 | 30110 |
137
129
  | Base | 8453 | 30184 |
138
- | Ethereum | 1 | 30101 |
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 { getVaultStatus, depositSimple, getUserPosition } from '@oydual31/more-vaults-sdk/viem'
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: flowEvm, transport: http(RPC_URL) })
195
- const walletClient = createWalletClient({ account, chain: flowEvm, transport: http(RPC_URL) })
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 addresses = { vault: VAULT_ADDRESS, escrow: status.escrow }
189
+ const VAULT = '0x8f740aba022b3fcc934ab75c581c04b75e72aba6'
204
190
 
205
- // 2. Deposit
206
- const { txHash, shares } = await depositSimple(
207
- walletClient, publicClient, addresses,
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
- // 3. Read position
213
- const position = await getUserPosition(publicClient, VAULT_ADDRESS, account.address)
214
- console.log('Shares:', position.shares)
215
- console.log('Value:', position.estimatedAssets)
216
- ```
217
-
218
- ### ethers.js
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
- const { txHash, shares } = await depositSimple(
231
- signer, addresses,
232
- parseUnits('100', 6),
233
- await signer.getAddress(),
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
- ### Deposit
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
- | D1 | `depositSimple` | User on hub chain, oracle ON or local vault | [→](./docs/flows/D1-deposit-simple.md) |
246
- | D2 | `depositMultiAsset` | Deposit multiple tokens in one call | [](./docs/flows/D2-deposit-multi-asset.md) |
247
- | D3 | `depositCrossChainOracleOn` | Alias for D1 hub with oracle ON, explicit naming | [](./docs/flows/D3-deposit-oracle-on.md) |
248
- | D4 | `depositAsync` | Hub with oracle OFF async LZ Read, shares arrive in ~1–5 min | [](./docs/flows/D4-deposit-async.md) |
249
- | D5 | `mintAsync` | Same as D4 but user specifies exact share amount | [](./docs/flows/D5-mint-async.md) |
250
- | D6/D7 | `depositFromSpoke` | User on another chain tokens bridge via LZ OFT | [](./docs/flows/D6-D7-deposit-from-spoke.md) |
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 D1hub 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
- ### Redeem
255
+ ### Hub-chain redeem
253
256
 
254
257
  | ID | Function | When to use | Doc |
255
258
  |----|----------|-------------|-----|
256
- | R1 | `redeemShares` | Standard redeem, hub chain, no queue | [→](./docs/flows/R1-redeem-shares.md) |
257
- | R2 | `withdrawAssets` | Specify exact asset amount to receive | [](./docs/flows/R2-withdraw-assets.md) |
258
- | R3 | `requestRedeem` | Withdrawal queue enabled, no timelock | [](./docs/flows/R3-R4-request-redeem.md) |
259
- | R4 | `requestRedeem` | Withdrawal queue + mandatory wait period | [](./docs/flows/R3-R4-request-redeem.md) |
260
- | R5 | `redeemAsync` | Hub with oracle OFF async LZ Read | [](./docs/flows/R5-redeem-async.md) |
261
- | R6 | `bridgeSharesToHub` | User on spokestep 1: bridge shares to hub | [](./docs/flows/R6-bridge-shares-to-hub.md) |
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/ viem/wagmi SDK
286
- └── ethers/ ethers.js v6 SDK
372
+ │ ├── viem/ <- viem/wagmi SDK
373
+ ├── ethers/ <- ethers.js v6 SDK
374
+ │ └── react/ <- React hooks (wagmi)
287
375
  ├── docs/
288
- │ ├── flows/ one .md per flow with detailed examples
376
+ │ ├── flows/ <- one .md per flow with detailed examples
289
377
  │ ├── user-helpers.md
290
378
  │ └── testing.md
291
- ├── tests/ integration tests (require Foundry + Anvil)
292
- └── contracts/ protocol Solidity source + mocks (for running tests)
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.
@@ -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: receiverBytes32,
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: receiverBytes32,
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