@oydual31/more-vaults-sdk 0.3.3 → 0.4.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/dist/ethers/index.cjs +1794 -315
- package/dist/ethers/index.cjs.map +1 -1
- package/dist/ethers/index.d.cts +1147 -1
- package/dist/ethers/index.d.ts +1147 -1
- package/dist/ethers/index.js +1752 -317
- package/dist/ethers/index.js.map +1 -1
- package/dist/react/index.cjs +644 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +111 -2
- package/dist/react/index.d.ts +111 -2
- package/dist/react/index.js +638 -3
- package/dist/react/index.js.map +1 -1
- package/dist/{spokeRoutes-BIafSbQ3.d.cts → spokeRoutes-B8Lnk-t4.d.cts} +191 -2
- package/dist/{spokeRoutes-BIafSbQ3.d.ts → spokeRoutes-B8Lnk-t4.d.ts} +191 -2
- package/dist/viem/index.d.cts +4 -192
- package/dist/viem/index.d.ts +4 -192
- package/package.json +1 -1
- package/src/ethers/abis.ts +92 -0
- package/src/ethers/chains.ts +191 -0
- package/src/ethers/crossChainFlows.ts +208 -0
- package/src/ethers/curatorMulticall.ts +195 -0
- package/src/ethers/curatorStatus.ts +319 -0
- package/src/ethers/curatorSwaps.ts +192 -0
- package/src/ethers/distribution.ts +156 -0
- package/src/ethers/index.ts +96 -1
- package/src/ethers/preflight.ts +225 -1
- package/src/ethers/redeemFlows.ts +160 -1
- package/src/ethers/spokeRoutes.ts +361 -0
- package/src/ethers/topology.ts +240 -0
- package/src/ethers/types.ts +95 -0
- package/src/ethers/userHelpers.ts +193 -0
- package/src/ethers/utils.ts +28 -0
- package/src/react/index.ts +25 -0
- package/src/react/useCuratorVaultStatus.ts +32 -0
- package/src/react/useExecuteActions.ts +23 -0
- package/src/react/useIsCurator.ts +30 -0
- package/src/react/usePendingActions.ts +33 -0
- package/src/react/useProtocolWhitelist.ts +30 -0
- package/src/react/useSubmitActions.ts +27 -0
- package/src/react/useVaultAnalysis.ts +32 -0
- package/src/react/useVaultAssetBreakdown.ts +32 -0
- package/src/react/useVetoActions.ts +23 -0
package/src/ethers/index.ts
CHANGED
|
@@ -15,6 +15,18 @@ export type {
|
|
|
15
15
|
Signer,
|
|
16
16
|
Provider,
|
|
17
17
|
ContractTransactionReceipt,
|
|
18
|
+
// Curator types
|
|
19
|
+
SwapParams,
|
|
20
|
+
BatchSwapParams,
|
|
21
|
+
BridgeParams,
|
|
22
|
+
PendingAction,
|
|
23
|
+
SubmitActionsResult,
|
|
24
|
+
CuratorAction,
|
|
25
|
+
CuratorVaultStatus,
|
|
26
|
+
AssetInfo,
|
|
27
|
+
VaultAnalysis,
|
|
28
|
+
AssetBalance,
|
|
29
|
+
VaultAssetBreakdown,
|
|
18
30
|
} from "./types";
|
|
19
31
|
export { ActionType } from "./types";
|
|
20
32
|
|
|
@@ -27,6 +39,16 @@ export {
|
|
|
27
39
|
OFT_ABI,
|
|
28
40
|
METADATA_ABI,
|
|
29
41
|
LZ_ENDPOINT_ABI,
|
|
42
|
+
// Curator ABIs
|
|
43
|
+
MULTICALL_ABI,
|
|
44
|
+
DEX_ABI,
|
|
45
|
+
BRIDGE_FACET_ABI,
|
|
46
|
+
ERC7540_FACET_ABI,
|
|
47
|
+
ERC4626_FACET_ABI,
|
|
48
|
+
CURATOR_CONFIG_ABI,
|
|
49
|
+
LZ_ADAPTER_ABI,
|
|
50
|
+
VAULT_ANALYSIS_ABI,
|
|
51
|
+
REGISTRY_ABI,
|
|
30
52
|
} from "./abis";
|
|
31
53
|
|
|
32
54
|
// --- Errors ---
|
|
@@ -60,6 +82,7 @@ export {
|
|
|
60
82
|
quoteDepositFromSpokeFee,
|
|
61
83
|
quoteComposeFee,
|
|
62
84
|
executeCompose,
|
|
85
|
+
waitForCompose,
|
|
63
86
|
} from "./crossChainFlows";
|
|
64
87
|
|
|
65
88
|
// --- Redeem flows ---
|
|
@@ -72,7 +95,10 @@ export {
|
|
|
72
95
|
smartRedeem,
|
|
73
96
|
bridgeSharesToHub,
|
|
74
97
|
bridgeAssetsToSpoke,
|
|
98
|
+
resolveRedeemAddresses,
|
|
99
|
+
quoteShareBridgeFee,
|
|
75
100
|
} from "./redeemFlows";
|
|
101
|
+
export type { SpokeRedeemRoute } from "./redeemFlows";
|
|
76
102
|
|
|
77
103
|
// --- Utilities ---
|
|
78
104
|
export {
|
|
@@ -81,11 +107,18 @@ export {
|
|
|
81
107
|
isAsyncMode,
|
|
82
108
|
getAsyncRequestStatus,
|
|
83
109
|
getVaultStatus,
|
|
110
|
+
detectStargateOft,
|
|
84
111
|
} from "./utils";
|
|
85
112
|
export type { VaultStatus, VaultMode } from "./utils";
|
|
86
113
|
|
|
87
114
|
// --- Pre-flight validation ---
|
|
88
|
-
export {
|
|
115
|
+
export {
|
|
116
|
+
preflightSync,
|
|
117
|
+
preflightAsync,
|
|
118
|
+
preflightRedeemLiquidity,
|
|
119
|
+
preflightSpokeDeposit,
|
|
120
|
+
preflightSpokeRedeem,
|
|
121
|
+
} from "./preflight";
|
|
89
122
|
|
|
90
123
|
// --- User Helpers ---
|
|
91
124
|
export {
|
|
@@ -98,6 +131,7 @@ export {
|
|
|
98
131
|
getUserBalances,
|
|
99
132
|
getMaxWithdrawable,
|
|
100
133
|
getVaultSummary,
|
|
134
|
+
getUserPositionMultiChain,
|
|
101
135
|
} from "./userHelpers";
|
|
102
136
|
export type {
|
|
103
137
|
UserPosition,
|
|
@@ -109,7 +143,68 @@ export type {
|
|
|
109
143
|
UserBalances,
|
|
110
144
|
MaxWithdrawable,
|
|
111
145
|
VaultSummary,
|
|
146
|
+
MultiChainUserPosition,
|
|
112
147
|
} from "./userHelpers";
|
|
113
148
|
|
|
149
|
+
// --- Curator status reads ---
|
|
150
|
+
export {
|
|
151
|
+
getCuratorVaultStatus,
|
|
152
|
+
getPendingActions,
|
|
153
|
+
isCurator,
|
|
154
|
+
getVaultAnalysis,
|
|
155
|
+
checkProtocolWhitelist,
|
|
156
|
+
getVaultAssetBreakdown,
|
|
157
|
+
} from "./curatorStatus";
|
|
158
|
+
|
|
159
|
+
// --- Curator multicall writes ---
|
|
160
|
+
export {
|
|
161
|
+
encodeCuratorAction,
|
|
162
|
+
buildCuratorBatch,
|
|
163
|
+
submitActions,
|
|
164
|
+
executeActions,
|
|
165
|
+
vetoActions,
|
|
166
|
+
} from "./curatorMulticall";
|
|
167
|
+
|
|
168
|
+
// --- Curator swap helpers ---
|
|
169
|
+
export {
|
|
170
|
+
buildUniswapV3Swap,
|
|
171
|
+
encodeUniswapV3SwapCalldata,
|
|
172
|
+
} from "./curatorSwaps";
|
|
173
|
+
|
|
174
|
+
// --- Topology ---
|
|
175
|
+
export {
|
|
176
|
+
getVaultTopology,
|
|
177
|
+
getFullVaultTopology,
|
|
178
|
+
discoverVaultTopology,
|
|
179
|
+
isOnHubChain,
|
|
180
|
+
getAllVaultChainIds,
|
|
181
|
+
OMNI_FACTORY_ADDRESS,
|
|
182
|
+
} from "./topology";
|
|
183
|
+
export type { VaultTopology } from "./topology";
|
|
184
|
+
|
|
185
|
+
// --- Distribution ---
|
|
186
|
+
export {
|
|
187
|
+
getVaultDistribution,
|
|
188
|
+
getVaultDistributionWithTopology,
|
|
189
|
+
} from "./distribution";
|
|
190
|
+
export type { VaultDistribution, SpokeBalance } from "./distribution";
|
|
191
|
+
|
|
192
|
+
// --- Spoke routes ---
|
|
193
|
+
export {
|
|
194
|
+
getInboundRoutes,
|
|
195
|
+
getUserBalancesForRoutes,
|
|
196
|
+
getOutboundRoutes,
|
|
197
|
+
quoteRouteDepositFee,
|
|
198
|
+
NATIVE_SYMBOL,
|
|
199
|
+
} from "./spokeRoutes";
|
|
200
|
+
export type {
|
|
201
|
+
InboundRoute,
|
|
202
|
+
InboundRouteWithBalance,
|
|
203
|
+
OutboundRoute,
|
|
204
|
+
} from "./spokeRoutes";
|
|
205
|
+
|
|
206
|
+
// --- Chains ---
|
|
207
|
+
export { UNISWAP_V3_ROUTERS, OFT_ROUTES } from "./chains";
|
|
208
|
+
|
|
114
209
|
// --- wagmi / ethers adapter compatibility ---
|
|
115
210
|
export { asSdkSigner } from "./wagmiCompat";
|
package/src/ethers/preflight.ts
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
import { Contract, ZeroAddress } from "ethers";
|
|
10
10
|
import type { Provider } from "ethers";
|
|
11
|
-
import { CONFIG_ABI, BRIDGE_ABI, VAULT_ABI, ERC20_ABI } from "./abis";
|
|
11
|
+
import { CONFIG_ABI, BRIDGE_ABI, VAULT_ABI, ERC20_ABI, OFT_ABI } from "./abis";
|
|
12
12
|
import { InsufficientLiquidityError } from "./errors";
|
|
13
|
+
import { detectStargateOft } from "./utils";
|
|
14
|
+
import { EID_TO_CHAIN_ID, createChainProvider } from "./chains";
|
|
15
|
+
import { quoteComposeFee } from "./crossChainFlows";
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Pre-flight checks for async cross-chain flows (D4 / D5 / R5).
|
|
@@ -164,3 +167,224 @@ export async function preflightSync(
|
|
|
164
167
|
);
|
|
165
168
|
}
|
|
166
169
|
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Pre-flight checks for spoke-to-hub deposits (D6 / D7 via OFT Compose).
|
|
173
|
+
*
|
|
174
|
+
* Validates that:
|
|
175
|
+
* 1. User has enough tokens on the spoke chain to deposit.
|
|
176
|
+
* 2. User has enough native gas on the spoke chain for TX1 (OFT.send).
|
|
177
|
+
* 3. For Stargate OFTs (2-TX flow): user has enough ETH on the hub chain for TX2.
|
|
178
|
+
*
|
|
179
|
+
* @param spokeProvider Read-only provider on the SPOKE chain
|
|
180
|
+
* @param vault Vault address
|
|
181
|
+
* @param spokeOFT OFT contract address on the spoke chain
|
|
182
|
+
* @param hubEid LZ EID for the hub chain
|
|
183
|
+
* @param spokeEid LZ EID for the spoke chain
|
|
184
|
+
* @param amount Amount of tokens to deposit
|
|
185
|
+
* @param userAddress User's wallet address
|
|
186
|
+
* @param lzFee LZ fee for TX1 (from quoteDepositFromSpokeFee)
|
|
187
|
+
* @returns Object with validated balances for UI display
|
|
188
|
+
*/
|
|
189
|
+
export async function preflightSpokeDeposit(
|
|
190
|
+
spokeProvider: Provider,
|
|
191
|
+
vault: string,
|
|
192
|
+
spokeOFT: string,
|
|
193
|
+
hubEid: number,
|
|
194
|
+
spokeEid: number,
|
|
195
|
+
amount: bigint,
|
|
196
|
+
userAddress: string,
|
|
197
|
+
lzFee: bigint,
|
|
198
|
+
): Promise<{
|
|
199
|
+
spokeTokenBalance: bigint
|
|
200
|
+
spokeNativeBalance: bigint
|
|
201
|
+
hubNativeBalance: bigint
|
|
202
|
+
estimatedComposeFee: bigint
|
|
203
|
+
isStargate: boolean
|
|
204
|
+
}> {
|
|
205
|
+
// Read the underlying token address from the OFT
|
|
206
|
+
const OFT_TOKEN_ABI = ["function token() view returns (address)"] as const;
|
|
207
|
+
const oftContract = new Contract(spokeOFT, OFT_TOKEN_ABI, spokeProvider);
|
|
208
|
+
const spokeToken: string = await oftContract.token();
|
|
209
|
+
|
|
210
|
+
// Check token balance + native balance on spoke in parallel
|
|
211
|
+
const tokenContract = new Contract(spokeToken, ERC20_ABI, spokeProvider);
|
|
212
|
+
const [spokeTokenBalance, spokeNativeBalance]: [bigint, bigint] = await Promise.all([
|
|
213
|
+
tokenContract.balanceOf(userAddress),
|
|
214
|
+
spokeProvider.getBalance(userAddress),
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
// 1. Check token balance
|
|
218
|
+
if (spokeTokenBalance < amount) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`[MoreVaults] Insufficient token balance on spoke chain.\n` +
|
|
221
|
+
` Need: ${amount}\n` +
|
|
222
|
+
` Have: ${spokeTokenBalance}\n` +
|
|
223
|
+
` Token: ${spokeToken}`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 2. Check native gas for TX1 (lzFee + gas buffer)
|
|
228
|
+
const gasBuffer = 500_000_000_000_000n; // 0.0005 ETH
|
|
229
|
+
if (spokeNativeBalance < lzFee + gasBuffer) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`[MoreVaults] Insufficient native gas on spoke chain for TX1.\n` +
|
|
232
|
+
` Need: ~${lzFee + gasBuffer} wei (LZ fee + gas)\n` +
|
|
233
|
+
` Have: ${spokeNativeBalance} wei`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 3. For Stargate OFTs: check ETH on hub for TX2 (compose retry)
|
|
238
|
+
const isStargate = await detectStargateOft(spokeProvider, spokeOFT);
|
|
239
|
+
|
|
240
|
+
let hubNativeBalance = 0n;
|
|
241
|
+
let estimatedComposeFee = 0n;
|
|
242
|
+
|
|
243
|
+
if (isStargate) {
|
|
244
|
+
const hubChainId = EID_TO_CHAIN_ID[hubEid];
|
|
245
|
+
const hubProvider = createChainProvider(hubChainId);
|
|
246
|
+
if (hubProvider) {
|
|
247
|
+
[hubNativeBalance, estimatedComposeFee] = await Promise.all([
|
|
248
|
+
hubProvider.getBalance(userAddress),
|
|
249
|
+
quoteComposeFee(hubProvider, vault, spokeEid, userAddress),
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
const hubGasBuffer = 300_000_000_000_000n; // 0.0003 ETH
|
|
253
|
+
const totalNeeded = estimatedComposeFee + hubGasBuffer;
|
|
254
|
+
|
|
255
|
+
if (hubNativeBalance < totalNeeded) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`[MoreVaults] Insufficient ETH on hub chain for TX2 (compose retry).\n` +
|
|
258
|
+
` This is a Stargate 2-TX flow — TX2 requires ETH on the hub chain.\n` +
|
|
259
|
+
` Need: ~${totalNeeded} wei (compose fee ${estimatedComposeFee} + gas)\n` +
|
|
260
|
+
` Have: ${hubNativeBalance} wei\n` +
|
|
261
|
+
` Short: ${totalNeeded - hubNativeBalance} wei\n` +
|
|
262
|
+
` Send ETH to ${userAddress} on chainId ${hubChainId} before depositing.`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
spokeTokenBalance,
|
|
270
|
+
spokeNativeBalance,
|
|
271
|
+
hubNativeBalance,
|
|
272
|
+
estimatedComposeFee,
|
|
273
|
+
isStargate,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Pre-flight checks for spoke→hub→spoke redeem (R6 + R1 + R7).
|
|
279
|
+
*
|
|
280
|
+
* Validates that:
|
|
281
|
+
* 1. User has shares on the spoke chain.
|
|
282
|
+
* 2. User has enough native gas on the spoke for TX1.
|
|
283
|
+
* 3. User has enough native gas on the hub for TX2 (redeem) + TX3 (asset bridge).
|
|
284
|
+
*
|
|
285
|
+
* @param route SpokeRedeemRoute from resolveRedeemAddresses
|
|
286
|
+
* @param shares Shares the user intends to redeem
|
|
287
|
+
* @param userAddress User's wallet address
|
|
288
|
+
* @param shareBridgeFee LZ fee for share bridge (TX1)
|
|
289
|
+
* @returns Validated balances for UI display
|
|
290
|
+
*/
|
|
291
|
+
export async function preflightSpokeRedeem(
|
|
292
|
+
route: {
|
|
293
|
+
hubChainId: number
|
|
294
|
+
spokeChainId: number
|
|
295
|
+
hubEid: number
|
|
296
|
+
spokeEid: number
|
|
297
|
+
hubAsset: string
|
|
298
|
+
spokeShareOft: string
|
|
299
|
+
hubAssetOft: string
|
|
300
|
+
spokeAsset: string
|
|
301
|
+
isStargate: boolean
|
|
302
|
+
},
|
|
303
|
+
shares: bigint,
|
|
304
|
+
userAddress: string,
|
|
305
|
+
shareBridgeFee: bigint,
|
|
306
|
+
): Promise<{
|
|
307
|
+
sharesOnSpoke: bigint
|
|
308
|
+
spokeNativeBalance: bigint
|
|
309
|
+
hubNativeBalance: bigint
|
|
310
|
+
estimatedAssetBridgeFee: bigint
|
|
311
|
+
estimatedAssetsOut: bigint
|
|
312
|
+
hubLiquidBalance: bigint
|
|
313
|
+
}> {
|
|
314
|
+
const spokeProvider = createChainProvider(route.spokeChainId);
|
|
315
|
+
const hubProvider = createChainProvider(route.hubChainId);
|
|
316
|
+
if (!spokeProvider) throw new Error(`No public RPC for spoke chainId ${route.spokeChainId}`);
|
|
317
|
+
if (!hubProvider) throw new Error(`No public RPC for hub chainId ${route.hubChainId}`);
|
|
318
|
+
|
|
319
|
+
// Parallel reads: shares on spoke, native balances
|
|
320
|
+
const spokeShareContract = new Contract(route.spokeShareOft, ERC20_ABI, spokeProvider);
|
|
321
|
+
const [sharesOnSpoke, spokeNativeBalance, hubNativeBalance]: [bigint, bigint, bigint] =
|
|
322
|
+
await Promise.all([
|
|
323
|
+
spokeShareContract.balanceOf(userAddress),
|
|
324
|
+
spokeProvider.getBalance(userAddress),
|
|
325
|
+
hubProvider.getBalance(userAddress),
|
|
326
|
+
]);
|
|
327
|
+
|
|
328
|
+
// 1. Check shares
|
|
329
|
+
if (sharesOnSpoke < shares) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
`[MoreVaults] Insufficient shares on spoke chain.\n` +
|
|
332
|
+
` Need: ${shares}\n` +
|
|
333
|
+
` Have: ${sharesOnSpoke}\n` +
|
|
334
|
+
` Token: ${route.spokeShareOft}`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 2. Check spoke gas for TX1
|
|
339
|
+
const spokeGasBuffer = 500_000_000_000_000n; // 0.0005 ETH
|
|
340
|
+
if (spokeNativeBalance < shareBridgeFee + spokeGasBuffer) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`[MoreVaults] Insufficient native gas on spoke for TX1 (share bridge).\n` +
|
|
343
|
+
` Need: ~${shareBridgeFee + spokeGasBuffer} wei (LZ fee + gas)\n` +
|
|
344
|
+
` Have: ${spokeNativeBalance} wei`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 3. Estimate asset bridge fee (TX3) and check hub gas
|
|
349
|
+
let estimatedAssetBridgeFee = 0n;
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const toBytes32 = `0x${userAddress.replace(/^0x/, '').padStart(64, '0')}`;
|
|
353
|
+
const dummyAmount = 1_000_000n; // 1 USDC for fee estimation
|
|
354
|
+
const hubOft = new Contract(route.hubAssetOft, OFT_ABI, hubProvider);
|
|
355
|
+
const feeResult = await hubOft.quoteSend({
|
|
356
|
+
dstEid: route.spokeEid,
|
|
357
|
+
to: toBytes32,
|
|
358
|
+
amountLD: dummyAmount,
|
|
359
|
+
minAmountLD: dummyAmount * 99n / 100n,
|
|
360
|
+
extraOptions: "0x",
|
|
361
|
+
composeMsg: "0x",
|
|
362
|
+
oftCmd: route.isStargate ? "0x01" : "0x",
|
|
363
|
+
}, false);
|
|
364
|
+
estimatedAssetBridgeFee = feeResult.nativeFee as bigint;
|
|
365
|
+
} catch {
|
|
366
|
+
estimatedAssetBridgeFee = 300_000_000_000_000n; // 0.0003 ETH fallback
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const hubGasBuffer = 300_000_000_000_000n; // 0.0003 ETH for gas (TX2 + TX3)
|
|
370
|
+
const totalHubNeeded = estimatedAssetBridgeFee + hubGasBuffer;
|
|
371
|
+
|
|
372
|
+
if (hubNativeBalance < totalHubNeeded) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`[MoreVaults] Insufficient ETH on hub chain for TX2 (redeem) + TX3 (asset bridge).\n` +
|
|
375
|
+
` Need: ~${totalHubNeeded} wei (LZ fee ${estimatedAssetBridgeFee} + gas)\n` +
|
|
376
|
+
` Have: ${hubNativeBalance} wei\n` +
|
|
377
|
+
` Short: ${totalHubNeeded - hubNativeBalance} wei\n` +
|
|
378
|
+
` Send ETH to ${userAddress} on chainId ${route.hubChainId} before redeeming.`,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
sharesOnSpoke,
|
|
384
|
+
spokeNativeBalance,
|
|
385
|
+
hubNativeBalance,
|
|
386
|
+
estimatedAssetBridgeFee,
|
|
387
|
+
estimatedAssetsOut: 0n,
|
|
388
|
+
hubLiquidBalance: 0n,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
@@ -15,7 +15,9 @@ 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
|
+
import { getVaultStatus, quoteLzFee, detectStargateOft } from "./utils";
|
|
19
|
+
import { CHAIN_ID_TO_EID, OFT_ROUTES, createChainProvider } from "./chains";
|
|
20
|
+
import { OMNI_FACTORY_ADDRESS } from "./topology";
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Ensure `spender` has at least `amount` allowance from `owner`.
|
|
@@ -407,3 +409,160 @@ export async function bridgeAssetsToSpoke(
|
|
|
407
409
|
|
|
408
410
|
return { receipt };
|
|
409
411
|
}
|
|
412
|
+
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
414
|
+
// Spoke redeem helpers
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
|
|
417
|
+
/** Minimal ABIs used only within redeemFlows */
|
|
418
|
+
const FACTORY_COMPOSER_ABI_RF = [
|
|
419
|
+
"function vaultComposer(address _vault) view returns (address)",
|
|
420
|
+
] as const;
|
|
421
|
+
|
|
422
|
+
const REDEEM_COMPOSER_ABI = [
|
|
423
|
+
"function SHARE_OFT() view returns (address)",
|
|
424
|
+
] as const;
|
|
425
|
+
|
|
426
|
+
const OFT_PEERS_ABI_RF = [
|
|
427
|
+
"function peers(uint32 eid) view returns (bytes32)",
|
|
428
|
+
] as const;
|
|
429
|
+
|
|
430
|
+
export interface SpokeRedeemRoute {
|
|
431
|
+
/** Hub chain ID */
|
|
432
|
+
hubChainId: number
|
|
433
|
+
/** Spoke chain ID */
|
|
434
|
+
spokeChainId: number
|
|
435
|
+
/** LZ EID for the hub */
|
|
436
|
+
hubEid: number
|
|
437
|
+
/** LZ EID for the spoke */
|
|
438
|
+
spokeEid: number
|
|
439
|
+
/** Vault underlying asset address on hub */
|
|
440
|
+
hubAsset: string
|
|
441
|
+
/** SHARE_OFT on spoke chain */
|
|
442
|
+
spokeShareOft: string
|
|
443
|
+
/** Asset OFT on hub for bridging back */
|
|
444
|
+
hubAssetOft: string
|
|
445
|
+
/** Underlying asset address on spoke chain */
|
|
446
|
+
spokeAsset: string
|
|
447
|
+
/** Whether the asset OFT is a Stargate pool */
|
|
448
|
+
isStargate: boolean
|
|
449
|
+
/** OFT route symbol (e.g. 'stgUSDC') */
|
|
450
|
+
symbol: string
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Quote the LZ fee for bridging shares from spoke to hub via SHARE_OFT.
|
|
455
|
+
*
|
|
456
|
+
* IMPORTANT: `amountLD` must be in SHARE_OFT native decimals (e.g. 18),
|
|
457
|
+
* NOT vault decimals (e.g. 8). Use the raw `SHARE_OFT.balanceOf(user)` value.
|
|
458
|
+
*
|
|
459
|
+
* @param spokeProvider Read-only provider on the SPOKE chain
|
|
460
|
+
* @param shareOFT SHARE_OFT address on the spoke chain
|
|
461
|
+
* @param hubChainEid LayerZero Endpoint ID for the hub chain
|
|
462
|
+
* @param amountLD Shares in SHARE_OFT native decimals (raw balanceOf)
|
|
463
|
+
* @param receiver Receiver address on the hub chain
|
|
464
|
+
* @returns LZ native fee in wei
|
|
465
|
+
*/
|
|
466
|
+
export async function quoteShareBridgeFee(
|
|
467
|
+
spokeProvider: Provider,
|
|
468
|
+
shareOFT: string,
|
|
469
|
+
hubChainEid: number,
|
|
470
|
+
amountLD: bigint,
|
|
471
|
+
receiver: string,
|
|
472
|
+
): Promise<bigint> {
|
|
473
|
+
const toBytes32 = zeroPadValue(receiver, 32);
|
|
474
|
+
const sendParam = {
|
|
475
|
+
dstEid: hubChainEid,
|
|
476
|
+
to: toBytes32,
|
|
477
|
+
amountLD,
|
|
478
|
+
minAmountLD: amountLD,
|
|
479
|
+
extraOptions: "0x",
|
|
480
|
+
composeMsg: "0x",
|
|
481
|
+
oftCmd: "0x",
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const oft = new Contract(shareOFT, OFT_ABI, spokeProvider);
|
|
485
|
+
const fee = await oft.quoteSend(sendParam, false);
|
|
486
|
+
return fee.nativeFee as bigint;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Resolve all addresses needed for a full spoke→hub→spoke redeem flow.
|
|
491
|
+
*
|
|
492
|
+
* @param hubProvider Read-only provider on the HUB chain
|
|
493
|
+
* @param vault Vault address
|
|
494
|
+
* @param hubChainId Hub chain ID
|
|
495
|
+
* @param spokeChainId Spoke chain ID where user has shares
|
|
496
|
+
* @returns All addresses needed for bridgeSharesToHub + redeemShares + bridgeAssetsToSpoke
|
|
497
|
+
*/
|
|
498
|
+
export async function resolveRedeemAddresses(
|
|
499
|
+
hubProvider: Provider,
|
|
500
|
+
vault: string,
|
|
501
|
+
hubChainId: number,
|
|
502
|
+
spokeChainId: number,
|
|
503
|
+
): Promise<SpokeRedeemRoute> {
|
|
504
|
+
const hubEid = CHAIN_ID_TO_EID[hubChainId];
|
|
505
|
+
const spokeEid = CHAIN_ID_TO_EID[spokeChainId];
|
|
506
|
+
if (!hubEid || !spokeEid) {
|
|
507
|
+
throw new Error(`No LZ EID for chainId ${!hubEid ? hubChainId : spokeChainId}`);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const vaultContract = new Contract(vault, VAULT_ABI, hubProvider);
|
|
511
|
+
const factory = new Contract(OMNI_FACTORY_ADDRESS, FACTORY_COMPOSER_ABI_RF, hubProvider);
|
|
512
|
+
const [hubAsset, composerAddress]: [string, string] = await Promise.all([
|
|
513
|
+
vaultContract.asset(),
|
|
514
|
+
factory.vaultComposer(vault),
|
|
515
|
+
]);
|
|
516
|
+
|
|
517
|
+
if (composerAddress === ZeroAddress) {
|
|
518
|
+
throw new Error(`[MoreVaults] No composer registered for vault ${vault} on hub chain ${hubChainId}`);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const composer = new Contract(composerAddress, REDEEM_COMPOSER_ABI, hubProvider);
|
|
522
|
+
const hubShareOft: string = await composer.SHARE_OFT();
|
|
523
|
+
|
|
524
|
+
const hubShareOftContract = new Contract(hubShareOft, OFT_PEERS_ABI_RF, hubProvider);
|
|
525
|
+
const spokeShareOftBytes32: string = await hubShareOftContract.peers(spokeEid);
|
|
526
|
+
|
|
527
|
+
// Convert bytes32 to address (last 20 bytes = last 40 hex chars)
|
|
528
|
+
const spokeShareOft = `0x${spokeShareOftBytes32.slice(-40)}`;
|
|
529
|
+
|
|
530
|
+
let hubAssetOft: string | null = null;
|
|
531
|
+
let spokeAsset: string | null = null;
|
|
532
|
+
let symbol = '';
|
|
533
|
+
|
|
534
|
+
for (const [sym, chainMap] of Object.entries(OFT_ROUTES)) {
|
|
535
|
+
const hubEntry = (chainMap as Record<number, { oft: string; token: string }>)[hubChainId];
|
|
536
|
+
const spokeEntry = (chainMap as Record<number, { oft: string; token: string }>)[spokeChainId];
|
|
537
|
+
if (!hubEntry || !spokeEntry) continue;
|
|
538
|
+
|
|
539
|
+
if (hubEntry.token.toLowerCase() === hubAsset.toLowerCase()) {
|
|
540
|
+
hubAssetOft = hubEntry.oft;
|
|
541
|
+
spokeAsset = spokeEntry.token;
|
|
542
|
+
symbol = sym;
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (!hubAssetOft || !spokeAsset) {
|
|
548
|
+
throw new Error(
|
|
549
|
+
`[MoreVaults] No OFT route found for vault asset ${hubAsset} ` +
|
|
550
|
+
`between hub chain ${hubChainId} and spoke chain ${spokeChainId}`,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const isStargate = await detectStargateOft(hubProvider, hubAssetOft);
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
hubChainId,
|
|
558
|
+
spokeChainId,
|
|
559
|
+
hubEid,
|
|
560
|
+
spokeEid,
|
|
561
|
+
hubAsset,
|
|
562
|
+
spokeShareOft,
|
|
563
|
+
hubAssetOft,
|
|
564
|
+
spokeAsset,
|
|
565
|
+
isStargate,
|
|
566
|
+
symbol,
|
|
567
|
+
};
|
|
568
|
+
}
|