@oydual31/more-vaults-sdk 0.2.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oydual31/more-vaults-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "TypeScript SDK for MoreVaults protocol — viem/wagmi and ethers.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -37,3 +37,22 @@ export const CHAIN_ID_TO_EID: Record<number, number> = {
37
37
  [CHAIN_IDS.base]: LZ_EIDS.base,
38
38
  [CHAIN_IDS.ethereum]: LZ_EIDS.ethereum,
39
39
  };
40
+
41
+ /**
42
+ * Recommended timeouts for cross-chain operations (milliseconds).
43
+ * UIs should show a progress indicator and NOT timeout before these values.
44
+ */
45
+ export const LZ_TIMEOUTS = {
46
+ /** Poll interval between balance/event checks */
47
+ POLL_INTERVAL: 30_000,
48
+ /** Standard OFT bridge (shares or assets, non-Stargate) */
49
+ OFT_BRIDGE: 900_000, // 15 min
50
+ /** Stargate bridge (USDC, USDT, WETH) — slower due to pool mechanics */
51
+ STARGATE_BRIDGE: 1_800_000, // 30 min
52
+ /** LZ Read callback (async vault actions) */
53
+ LZ_READ_CALLBACK: 900_000, // 15 min
54
+ /** Compose delivery to hub (deposit from spoke) */
55
+ COMPOSE_DELIVERY: 2_700_000, // 45 min
56
+ /** Full spoke→hub→spoke redeem (all steps combined) */
57
+ FULL_SPOKE_REDEEM: 3_600_000, // 60 min
58
+ } as const;
@@ -2,7 +2,7 @@
2
2
  // Barrel export for all flows and utilities.
3
3
 
4
4
  // --- Chain constants ---
5
- export { CHAIN_IDS, LZ_EIDS, EID_TO_CHAIN_ID, CHAIN_ID_TO_EID } from "./chains";
5
+ export { CHAIN_IDS, LZ_EIDS, EID_TO_CHAIN_ID, CHAIN_ID_TO_EID, LZ_TIMEOUTS } from "./chains";
6
6
 
7
7
  // --- Types ---
8
8
  export type {
@@ -69,7 +69,9 @@ export {
69
69
  requestRedeem,
70
70
  getWithdrawalRequest,
71
71
  redeemAsync,
72
+ smartRedeem,
72
73
  bridgeSharesToHub,
74
+ bridgeAssetsToSpoke,
73
75
  } from "./redeemFlows";
74
76
 
75
77
  // --- Utilities ---
@@ -15,6 +15,7 @@ import type { ContractTransactionReceipt } from "ethers";
15
15
  import { preflightAsync, preflightRedeemLiquidity } from "./preflight";
16
16
  import { EscrowNotConfiguredError } from "./errors";
17
17
  import { validateWalletChain } from "./chainValidation";
18
+ import { getVaultStatus, quoteLzFee } from "./utils";
18
19
 
19
20
  /**
20
21
  * Ensure `spender` has at least `amount` allowance from `owner`.
@@ -298,3 +299,111 @@ export async function bridgeSharesToHub(
298
299
 
299
300
  return { receipt };
300
301
  }
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Smart redeem -- auto-detect vault type
305
+ // ---------------------------------------------------------------------------
306
+
307
+ /**
308
+ * Smart redeem — auto-selects the correct flow based on vault configuration.
309
+ *
310
+ * Detects the vault mode and dispatches to:
311
+ * - Sync vaults (local / cross-chain-oracle): `redeemShares`
312
+ * - Async vaults (cross-chain, oracle OFF): `redeemAsync` (quotes LZ fee automatically)
313
+ *
314
+ * @param signer Wallet signer with account attached
315
+ * @param addresses Vault address set (`escrow` required for async vaults)
316
+ * @param shares Amount of shares to redeem
317
+ * @param receiver Address that will receive the underlying assets
318
+ * @param owner Owner of the shares being redeemed
319
+ * @param extraOptions Optional LZ extra options (only used for async vaults)
320
+ * @returns RedeemResult or AsyncRequestResult depending on vault mode
321
+ */
322
+ export async function smartRedeem(
323
+ signer: Signer,
324
+ addresses: VaultAddresses,
325
+ shares: bigint,
326
+ receiver: string,
327
+ owner: string,
328
+ extraOptions: string = "0x"
329
+ ): Promise<RedeemResult | AsyncRequestResult> {
330
+ const provider = signer.provider!;
331
+ const vault = addresses.vault;
332
+ const status = await getVaultStatus(provider, vault);
333
+
334
+ if (status.mode === "paused") {
335
+ throw new Error(`[MoreVaults] Vault ${vault} is paused. Cannot redeem.`);
336
+ }
337
+
338
+ if (status.recommendedDepositFlow === "depositAsync") {
339
+ // Async vault — use redeemAsync
340
+ const lzFee = await quoteLzFee(provider, vault, extraOptions);
341
+ return redeemAsync(signer, addresses, shares, receiver, owner, lzFee, extraOptions);
342
+ }
343
+
344
+ // Sync vault — direct redeem
345
+ return redeemShares(signer, addresses, shares, receiver, owner);
346
+ }
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // R7 -- Bridge assets from hub back to spoke
350
+ // ---------------------------------------------------------------------------
351
+
352
+ /**
353
+ * Bridge underlying assets from hub back to spoke chain via OFT.
354
+ *
355
+ * Step 3 of the full spoke redeem flow:
356
+ * 1. bridgeSharesToHub() — shares spoke->hub
357
+ * 2. smartRedeem() — redeem on hub
358
+ * 3. bridgeAssetsToSpoke() — assets hub->spoke
359
+ *
360
+ * @param signer Wallet signer on the HUB chain
361
+ * @param assetOFT OFT address for the underlying asset on hub
362
+ * @param spokeChainEid LayerZero EID for the spoke (destination) chain
363
+ * @param amount Amount of underlying assets to bridge
364
+ * @param receiver Receiver address on the spoke chain
365
+ * @param lzFee OFT send fee (quote via OFT.quoteSend)
366
+ * @param isStargate Whether this is a Stargate OFT (uses TAXI mode)
367
+ * @returns Transaction receipt
368
+ */
369
+ export async function bridgeAssetsToSpoke(
370
+ signer: Signer,
371
+ assetOFT: string,
372
+ spokeChainEid: number,
373
+ amount: bigint,
374
+ receiver: string,
375
+ lzFee: bigint,
376
+ isStargate: boolean = true
377
+ ): Promise<{ receipt: ContractTransactionReceipt }> {
378
+ const oft = new Contract(assetOFT, OFT_ABI, signer);
379
+
380
+ // Read underlying token and approve
381
+ const token: string = await oft.token();
382
+ if (token.toLowerCase() !== assetOFT.toLowerCase()) {
383
+ await ensureAllowance(signer, token, assetOFT, amount);
384
+ } else {
385
+ await ensureAllowance(signer, assetOFT, assetOFT, amount);
386
+ }
387
+
388
+ const refundAddress = await signer.getAddress();
389
+ const toBytes32 = zeroPadValue(receiver, 32);
390
+
391
+ const sendParam = {
392
+ dstEid: spokeChainEid,
393
+ to: toBytes32,
394
+ amountLD: amount,
395
+ minAmountLD: amount * 99n / 100n, // 1% slippage for Stargate
396
+ extraOptions: "0x",
397
+ composeMsg: "0x",
398
+ oftCmd: isStargate ? "0x01" : "0x",
399
+ };
400
+
401
+ const msgFee = { nativeFee: lzFee, lzTokenFee: 0n };
402
+
403
+ const tx = await oft.send(sendParam, msgFee, refundAddress, {
404
+ value: lzFee,
405
+ });
406
+ const receipt = await tx.wait();
407
+
408
+ return { receipt };
409
+ }
@@ -30,6 +30,7 @@ export { useVaultDistribution } from './useVaultDistribution.js'
30
30
 
31
31
  // --- Smart (auto-routing) hooks ---
32
32
  export { useSmartDeposit } from './useSmartDeposit.js'
33
+ export { useSmartRedeem } from './useSmartRedeem.js'
33
34
 
34
35
  // --- Inbound Routes ---
35
36
  export { useInboundRoutes, getRouteTokenDecimals } from './useInboundRoutes.js'
@@ -0,0 +1,70 @@
1
+ import type { AsyncRequestStatusInfo } from '../viem/index.js'
2
+ import { useVaultStatus } from './useVaultStatus.js'
3
+ import { useOmniRedeem } from './useOmniRedeem.js'
4
+ import { useRedeemShares } from './useRedeemShares.js'
5
+
6
+ interface UseSmartRedeemReturn {
7
+ /**
8
+ * Execute redeem using the correct flow for this vault's mode.
9
+ * For async vaults: wraps redeemAsync (R5) — returns guid for tracking.
10
+ * For local/oracle vaults: wraps redeemShares (R1) — returns assets.
11
+ */
12
+ redeem: (sharesInWei: bigint, receiver: `0x${string}`, owner: `0x${string}`) => Promise<void>
13
+ isLoading: boolean
14
+ txHash: `0x${string}` | undefined
15
+ /** Assets received (available for R1 vaults after confirmation, undefined for R5). */
16
+ assets: bigint | undefined
17
+ /** GUID for cross-chain tracking (R5 vaults only). */
18
+ guid: `0x${string}` | undefined
19
+ /** Cross-chain request status (R5 vaults only). */
20
+ requestStatus: AsyncRequestStatusInfo | undefined
21
+ /** true when the wallet is connected to the wrong chain (R5 vaults only). */
22
+ wrongChain: boolean
23
+ /** Vault mode loaded from getVaultStatus. undefined while loading. */
24
+ vaultMode: 'local' | 'cross-chain-oracle' | 'cross-chain-async' | 'paused' | 'full' | undefined
25
+ error: Error | undefined
26
+ reset: () => void
27
+ }
28
+
29
+ /**
30
+ * Auto-selects the correct redeem flow based on vault mode.
31
+ * Best for frontends that support multiple vault types.
32
+ *
33
+ * Internally uses useVaultStatus to detect the mode, then delegates to:
34
+ * - useOmniRedeem (R5) for 'cross-chain-async' vaults
35
+ * - useRedeemShares (R1) for 'local' and 'cross-chain-oracle' vaults
36
+ *
37
+ * @example
38
+ * const { redeem, isLoading, guid, requestStatus, vaultMode } = useSmartRedeem('0xVAULT', 8453)
39
+ *
40
+ * if (vaultMode === 'paused') return <PausedBadge />
41
+ *
42
+ * await redeem(sharesInWei, userAddress, userAddress)
43
+ * // For async vaults: poll requestStatus until 'completed'
44
+ * // For sync vaults: txHash + assets are available immediately
45
+ */
46
+ export function useSmartRedeem(
47
+ vault: `0x${string}` | undefined,
48
+ hubChainId: number,
49
+ ): UseSmartRedeemReturn {
50
+ const { data: status } = useVaultStatus(vault, hubChainId)
51
+ const omni = useOmniRedeem(vault, hubChainId)
52
+ const simple = useRedeemShares(vault, hubChainId)
53
+
54
+ const isAsync = status?.mode === 'cross-chain-async'
55
+
56
+ const redeem = isAsync ? omni.redeem : simple.redeem
57
+
58
+ return {
59
+ redeem,
60
+ isLoading: isAsync ? omni.isLoading : simple.isLoading,
61
+ txHash: isAsync ? omni.txHash : simple.txHash,
62
+ assets: isAsync ? undefined : simple.assets,
63
+ guid: isAsync ? omni.guid : undefined,
64
+ requestStatus: isAsync ? omni.requestStatus : undefined,
65
+ wrongChain: isAsync ? omni.wrongChain : false,
66
+ vaultMode: status?.mode,
67
+ error: isAsync ? omni.error : simple.error,
68
+ reset: isAsync ? omni.reset : simple.reset,
69
+ }
70
+ }