@oydual31/more-vaults-sdk 0.1.0
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 +283 -0
- package/dist/ethers/index.cjs +973 -0
- package/dist/ethers/index.cjs.map +1 -0
- package/dist/ethers/index.d.cts +671 -0
- package/dist/ethers/index.d.ts +671 -0
- package/dist/ethers/index.js +924 -0
- package/dist/ethers/index.js.map +1 -0
- package/dist/viem/index.cjs +1426 -0
- package/dist/viem/index.cjs.map +1 -0
- package/dist/viem/index.d.cts +1291 -0
- package/dist/viem/index.d.ts +1291 -0
- package/dist/viem/index.js +1377 -0
- package/dist/viem/index.js.map +1 -0
- package/package.json +46 -0
- package/src/ethers/abis.ts +82 -0
- package/src/ethers/crossChainFlows.ts +206 -0
- package/src/ethers/depositFlows.ts +347 -0
- package/src/ethers/errors.ts +81 -0
- package/src/ethers/index.ts +103 -0
- package/src/ethers/preflight.ts +156 -0
- package/src/ethers/redeemFlows.ts +286 -0
- package/src/ethers/types.ts +67 -0
- package/src/ethers/userHelpers.ts +480 -0
- package/src/ethers/utils.ts +377 -0
- package/src/viem/abis.ts +392 -0
- package/src/viem/crossChainFlows.ts +220 -0
- package/src/viem/depositFlows.ts +331 -0
- package/src/viem/errors.ts +81 -0
- package/src/viem/index.ts +100 -0
- package/src/viem/preflight.ts +204 -0
- package/src/viem/redeemFlows.ts +337 -0
- package/src/viem/types.ts +56 -0
- package/src/viem/userHelpers.ts +489 -0
- package/src/viem/utils.ts +421 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// MoreVaults SDK -- ethers.js v6
|
|
2
|
+
// Barrel export for all flows and utilities.
|
|
3
|
+
|
|
4
|
+
// --- Types ---
|
|
5
|
+
export type {
|
|
6
|
+
VaultAddresses,
|
|
7
|
+
DepositResult,
|
|
8
|
+
RedeemResult,
|
|
9
|
+
AsyncRequestResult,
|
|
10
|
+
CrossChainRequestInfo,
|
|
11
|
+
ActionTypeValue,
|
|
12
|
+
Signer,
|
|
13
|
+
Provider,
|
|
14
|
+
ContractTransactionReceipt,
|
|
15
|
+
} from "./types";
|
|
16
|
+
export { ActionType } from "./types";
|
|
17
|
+
|
|
18
|
+
// --- ABIs ---
|
|
19
|
+
export {
|
|
20
|
+
VAULT_ABI,
|
|
21
|
+
BRIDGE_ABI,
|
|
22
|
+
CONFIG_ABI,
|
|
23
|
+
ERC20_ABI,
|
|
24
|
+
OFT_ABI,
|
|
25
|
+
METADATA_ABI,
|
|
26
|
+
} from "./abis";
|
|
27
|
+
|
|
28
|
+
// --- Errors ---
|
|
29
|
+
export {
|
|
30
|
+
MoreVaultsError,
|
|
31
|
+
VaultPausedError,
|
|
32
|
+
CapacityFullError,
|
|
33
|
+
NotWhitelistedError,
|
|
34
|
+
InsufficientLiquidityError,
|
|
35
|
+
CCManagerNotConfiguredError,
|
|
36
|
+
EscrowNotConfiguredError,
|
|
37
|
+
NotHubVaultError,
|
|
38
|
+
MissingEscrowAddressError,
|
|
39
|
+
} from "./errors";
|
|
40
|
+
|
|
41
|
+
// --- Deposit flows ---
|
|
42
|
+
export {
|
|
43
|
+
depositSimple,
|
|
44
|
+
depositMultiAsset,
|
|
45
|
+
depositCrossChainOracleOn,
|
|
46
|
+
depositAsync,
|
|
47
|
+
mintAsync,
|
|
48
|
+
smartDeposit,
|
|
49
|
+
} from "./depositFlows";
|
|
50
|
+
|
|
51
|
+
// --- Cross-chain flows ---
|
|
52
|
+
export {
|
|
53
|
+
depositFromSpoke,
|
|
54
|
+
depositFromSpokeAsync,
|
|
55
|
+
quoteDepositFromSpokeFee,
|
|
56
|
+
} from "./crossChainFlows";
|
|
57
|
+
|
|
58
|
+
// --- Redeem flows ---
|
|
59
|
+
export {
|
|
60
|
+
redeemShares,
|
|
61
|
+
withdrawAssets,
|
|
62
|
+
requestRedeem,
|
|
63
|
+
getWithdrawalRequest,
|
|
64
|
+
redeemAsync,
|
|
65
|
+
bridgeSharesToHub,
|
|
66
|
+
} from "./redeemFlows";
|
|
67
|
+
|
|
68
|
+
// --- Utilities ---
|
|
69
|
+
export {
|
|
70
|
+
ensureAllowance,
|
|
71
|
+
quoteLzFee,
|
|
72
|
+
isAsyncMode,
|
|
73
|
+
getAsyncRequestStatus,
|
|
74
|
+
getVaultStatus,
|
|
75
|
+
} from "./utils";
|
|
76
|
+
export type { VaultStatus, VaultMode } from "./utils";
|
|
77
|
+
|
|
78
|
+
// --- Pre-flight validation ---
|
|
79
|
+
export { preflightSync, preflightAsync, preflightRedeemLiquidity } from "./preflight";
|
|
80
|
+
|
|
81
|
+
// --- User Helpers ---
|
|
82
|
+
export {
|
|
83
|
+
getUserPosition,
|
|
84
|
+
previewDeposit,
|
|
85
|
+
previewRedeem,
|
|
86
|
+
canDeposit,
|
|
87
|
+
getVaultMetadata,
|
|
88
|
+
getAsyncRequestStatusLabel,
|
|
89
|
+
getUserBalances,
|
|
90
|
+
getMaxWithdrawable,
|
|
91
|
+
getVaultSummary,
|
|
92
|
+
} from "./userHelpers";
|
|
93
|
+
export type {
|
|
94
|
+
UserPosition,
|
|
95
|
+
DepositEligibility,
|
|
96
|
+
DepositBlockReason,
|
|
97
|
+
VaultMetadata,
|
|
98
|
+
AsyncRequestStatus,
|
|
99
|
+
AsyncRequestStatusInfo,
|
|
100
|
+
UserBalances,
|
|
101
|
+
MaxWithdrawable,
|
|
102
|
+
VaultSummary,
|
|
103
|
+
} from "./userHelpers";
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight validation helpers for MoreVaults ethers.js v6 SDK flows.
|
|
3
|
+
*
|
|
4
|
+
* Each function reads on-chain state and throws a descriptive error BEFORE
|
|
5
|
+
* the actual contract call, so developers see a clear, actionable message
|
|
6
|
+
* instead of a raw VM revert.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Contract, ZeroAddress } from "ethers";
|
|
10
|
+
import type { Provider } from "ethers";
|
|
11
|
+
import { CONFIG_ABI, BRIDGE_ABI, VAULT_ABI, ERC20_ABI } from "./abis";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pre-flight checks for async cross-chain flows (D4 / D5 / R5).
|
|
15
|
+
*
|
|
16
|
+
* Validates that:
|
|
17
|
+
* 1. The CCManager is configured on the vault.
|
|
18
|
+
* 2. An escrow is registered in the vault's registry.
|
|
19
|
+
* 3. The vault is a hub (required for async flows).
|
|
20
|
+
* 4. The vault does NOT have oracle-based cross-chain accounting enabled
|
|
21
|
+
* (oracle-on vaults should use depositSimple / depositCrossChainOracleOn).
|
|
22
|
+
* 5. The vault is not paused.
|
|
23
|
+
*
|
|
24
|
+
* All reads that are independent of each other are executed in parallel via
|
|
25
|
+
* Promise.all to minimise latency.
|
|
26
|
+
*
|
|
27
|
+
* @param provider Read-only provider for contract reads
|
|
28
|
+
* @param vault Vault address (diamond proxy)
|
|
29
|
+
* @param escrow Escrow address from VaultAddresses
|
|
30
|
+
*/
|
|
31
|
+
export async function preflightAsync(
|
|
32
|
+
provider: Provider,
|
|
33
|
+
vault: string,
|
|
34
|
+
escrow: string
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
const config = new Contract(vault, CONFIG_ABI, provider);
|
|
37
|
+
const bridge = new Contract(vault, BRIDGE_ABI, provider);
|
|
38
|
+
|
|
39
|
+
// Parallel read: ccManager, escrow, isHub, oraclesCrossChainAccounting, paused
|
|
40
|
+
const [ccManager, registeredEscrow, isHub, oraclesEnabled, isPaused] =
|
|
41
|
+
await Promise.all([
|
|
42
|
+
config.getCrossChainAccountingManager() as Promise<string>,
|
|
43
|
+
config.getEscrow() as Promise<string>,
|
|
44
|
+
config.isHub() as Promise<boolean>,
|
|
45
|
+
bridge.oraclesCrossChainAccounting() as Promise<boolean>,
|
|
46
|
+
config.paused() as Promise<boolean>,
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
if (ccManager === ZeroAddress) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`[MoreVaults] CCManager not configured on vault ${vault}. Call setCrossChainAccountingManager(ccManagerAddress) as vault owner first.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (registeredEscrow === ZeroAddress) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`[MoreVaults] Escrow not configured for vault ${vault}. The registry must have an escrow set for this vault.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!isHub) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`[MoreVaults] Vault ${vault} is not a hub vault. Async flows (D4/D5/R5) only work on hub vaults.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (oraclesEnabled) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`[MoreVaults] Vault ${vault} has oracle-based cross-chain accounting enabled. Use depositSimple/depositCrossChainOracleOn instead of async flows.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isPaused) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`[MoreVaults] Vault ${vault} is paused. Cannot perform any actions.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Pre-flight liquidity check for async redeem (R5).
|
|
82
|
+
*
|
|
83
|
+
* Reads the hub's liquid balance of the underlying token and compares it
|
|
84
|
+
* against the assets the user expects to receive. If the hub does not hold
|
|
85
|
+
* enough liquid assets the redeem will be auto-refunded after the LZ round-trip,
|
|
86
|
+
* wasting the LayerZero fee.
|
|
87
|
+
*
|
|
88
|
+
* @param provider Read-only provider for contract reads
|
|
89
|
+
* @param vault Vault address (diamond proxy)
|
|
90
|
+
* @param shares Shares the user intends to redeem
|
|
91
|
+
*/
|
|
92
|
+
export async function preflightRedeemLiquidity(
|
|
93
|
+
provider: Provider,
|
|
94
|
+
vault: string,
|
|
95
|
+
shares: bigint
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
const vaultContract = new Contract(vault, VAULT_ABI, provider);
|
|
98
|
+
|
|
99
|
+
const underlying: string = await vaultContract.asset();
|
|
100
|
+
|
|
101
|
+
const underlyingContract = new Contract(underlying, ERC20_ABI, provider);
|
|
102
|
+
const [hubLiquid, assetsNeeded]: [bigint, bigint] = await Promise.all([
|
|
103
|
+
underlyingContract.balanceOf(vault),
|
|
104
|
+
vaultContract.convertToAssets(shares),
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
if (hubLiquid < assetsNeeded) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`[MoreVaults] Insufficient hub liquidity for redeem.\n` +
|
|
110
|
+
` Hub liquid balance : ${hubLiquid}\n` +
|
|
111
|
+
` Estimated required : ${assetsNeeded}\n` +
|
|
112
|
+
`Submitting this redeem will waste the LayerZero fee — the request will be auto-refunded.\n` +
|
|
113
|
+
`Ask the vault curator to repatriate liquidity from spoke chains first.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Pre-flight checks for synchronous deposit flows (D1 / D3).
|
|
120
|
+
*
|
|
121
|
+
* Validates that:
|
|
122
|
+
* 1. The vault is not paused.
|
|
123
|
+
* 2. The vault still has deposit capacity (maxDeposit > 0).
|
|
124
|
+
*
|
|
125
|
+
* Both reads are executed in parallel.
|
|
126
|
+
*
|
|
127
|
+
* @param provider Read-only provider for contract reads
|
|
128
|
+
* @param vault Vault address (diamond proxy)
|
|
129
|
+
*/
|
|
130
|
+
export async function preflightSync(
|
|
131
|
+
provider: Provider,
|
|
132
|
+
vault: string
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
const config = new Contract(vault, CONFIG_ABI, provider);
|
|
135
|
+
|
|
136
|
+
// Run paused and maxDeposit in parallel.
|
|
137
|
+
// maxDeposit(ZeroAddress) may REVERT on whitelisted vaults — catch separately.
|
|
138
|
+
const [isPaused, depositCapResult] = await Promise.all([
|
|
139
|
+
config.paused() as Promise<boolean>,
|
|
140
|
+
(config.maxDeposit(ZeroAddress) as Promise<bigint>).catch(() => null as null),
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
if (isPaused) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`[MoreVaults] Vault ${vault} is paused. Cannot perform any actions.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// null means maxDeposit reverted → whitelist vault — skip capacity check
|
|
150
|
+
// (the user may still be whitelisted; canDeposit will do user-specific check)
|
|
151
|
+
if (depositCapResult !== null && depositCapResult === 0n) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`[MoreVaults] Vault ${vault} has reached deposit capacity. No more deposits accepted.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { Contract, AbiCoder, zeroPadValue, Signer, Provider } from "ethers";
|
|
2
|
+
import {
|
|
3
|
+
VAULT_ABI,
|
|
4
|
+
BRIDGE_ABI,
|
|
5
|
+
ERC20_ABI,
|
|
6
|
+
OFT_ABI,
|
|
7
|
+
} from "./abis";
|
|
8
|
+
import {
|
|
9
|
+
VaultAddresses,
|
|
10
|
+
RedeemResult,
|
|
11
|
+
AsyncRequestResult,
|
|
12
|
+
ActionType,
|
|
13
|
+
} from "./types";
|
|
14
|
+
import type { ContractTransactionReceipt } from "ethers";
|
|
15
|
+
import { preflightAsync, preflightRedeemLiquidity } from "./preflight";
|
|
16
|
+
import { MissingEscrowAddressError } from "./errors";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ensure `spender` has at least `amount` allowance from `owner`.
|
|
20
|
+
*/
|
|
21
|
+
async function ensureAllowance(
|
|
22
|
+
signer: Signer,
|
|
23
|
+
token: string,
|
|
24
|
+
spender: string,
|
|
25
|
+
amount: bigint
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
const owner = await signer.getAddress();
|
|
28
|
+
const erc20 = new Contract(token, ERC20_ABI, signer);
|
|
29
|
+
const current: bigint = await erc20.allowance(owner, spender);
|
|
30
|
+
if (current < amount) {
|
|
31
|
+
const tx = await erc20.approve(spender, amount);
|
|
32
|
+
await tx.wait();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// R1 -- Simple redeem
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Redeem `shares` for underlying assets.
|
|
42
|
+
*
|
|
43
|
+
* TXs: 1 redeem.
|
|
44
|
+
* Pre-condition: if withdrawal queue is enabled, `requestRedeem` must have
|
|
45
|
+
* been called and the timelock must have elapsed.
|
|
46
|
+
*
|
|
47
|
+
* @param signer - Wallet holding the shares.
|
|
48
|
+
* @param addresses - Vault addresses. Only `vault` is used.
|
|
49
|
+
* @param shares - Number of shares to redeem.
|
|
50
|
+
* @param receiver - Address that will receive the underlying assets.
|
|
51
|
+
* @param owner - Address that owns the shares.
|
|
52
|
+
* @returns Assets received and the transaction receipt.
|
|
53
|
+
*/
|
|
54
|
+
export async function redeemShares(
|
|
55
|
+
signer: Signer,
|
|
56
|
+
addresses: VaultAddresses,
|
|
57
|
+
shares: bigint,
|
|
58
|
+
receiver: string,
|
|
59
|
+
owner: string
|
|
60
|
+
): Promise<RedeemResult> {
|
|
61
|
+
const vault = new Contract(addresses.vault, VAULT_ABI, signer);
|
|
62
|
+
|
|
63
|
+
// Static call to get the return value (assets) before broadcasting
|
|
64
|
+
const assets: bigint = await vault.redeem.staticCall(shares, receiver, owner);
|
|
65
|
+
|
|
66
|
+
const tx = await vault.redeem(shares, receiver, owner);
|
|
67
|
+
const receipt = await tx.wait();
|
|
68
|
+
|
|
69
|
+
return { receipt, assets };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// R2 -- Simple withdraw
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Withdraw exact `assets` amount of underlying tokens by burning shares.
|
|
78
|
+
*
|
|
79
|
+
* TXs: 1 withdraw.
|
|
80
|
+
*
|
|
81
|
+
* @param signer - Wallet holding the shares.
|
|
82
|
+
* @param addresses - Vault addresses. Only `vault` is used.
|
|
83
|
+
* @param assets - Exact amount of underlying tokens to withdraw.
|
|
84
|
+
* @param receiver - Address that will receive the tokens.
|
|
85
|
+
* @param owner - Address that owns the shares.
|
|
86
|
+
* @returns Assets received and the transaction receipt.
|
|
87
|
+
*/
|
|
88
|
+
export async function withdrawAssets(
|
|
89
|
+
signer: Signer,
|
|
90
|
+
addresses: VaultAddresses,
|
|
91
|
+
assets: bigint,
|
|
92
|
+
receiver: string,
|
|
93
|
+
owner: string
|
|
94
|
+
): Promise<RedeemResult> {
|
|
95
|
+
const vault = new Contract(addresses.vault, VAULT_ABI, signer);
|
|
96
|
+
const tx = await vault.withdraw(assets, receiver, owner);
|
|
97
|
+
const receipt = await tx.wait();
|
|
98
|
+
|
|
99
|
+
return { receipt, assets };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// R3 -- Queue redeem, no timelock (2 TXs)
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Submit a withdrawal queue request for `shares`.
|
|
108
|
+
*
|
|
109
|
+
* TXs: 1 requestRedeem.
|
|
110
|
+
* After this call and once any timelock elapses, call `redeemShares()` to
|
|
111
|
+
* complete the withdrawal.
|
|
112
|
+
*
|
|
113
|
+
* @param signer - Wallet holding the shares.
|
|
114
|
+
* @param addresses - Vault addresses. Only `vault` is used.
|
|
115
|
+
* @param shares - Number of shares to queue for redemption.
|
|
116
|
+
* @param owner - Address on whose behalf to request.
|
|
117
|
+
* @returns Transaction receipt.
|
|
118
|
+
*/
|
|
119
|
+
export async function requestRedeem(
|
|
120
|
+
signer: Signer,
|
|
121
|
+
addresses: VaultAddresses,
|
|
122
|
+
shares: bigint,
|
|
123
|
+
owner: string
|
|
124
|
+
): Promise<{ receipt: ContractTransactionReceipt }> {
|
|
125
|
+
const vault = new Contract(addresses.vault, VAULT_ABI, signer);
|
|
126
|
+
const tx = await vault.requestRedeem(shares, owner);
|
|
127
|
+
const receipt = await tx.wait();
|
|
128
|
+
return { receipt };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// R4 -- Queue redeem with timelock (helper to check status)
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get the current withdrawal request for an owner.
|
|
137
|
+
* Returns null if no pending request exists.
|
|
138
|
+
*
|
|
139
|
+
* @param provider - JSON-RPC provider.
|
|
140
|
+
* @param vault - Vault (diamond) address.
|
|
141
|
+
* @param owner - Address of the shares owner.
|
|
142
|
+
* @returns The request details or null.
|
|
143
|
+
*/
|
|
144
|
+
export async function getWithdrawalRequest(
|
|
145
|
+
provider: Provider,
|
|
146
|
+
vault: string,
|
|
147
|
+
owner: string
|
|
148
|
+
): Promise<{ shares: bigint; timelockEndsAt: bigint } | null> {
|
|
149
|
+
const vaultContract = new Contract(vault, VAULT_ABI, provider);
|
|
150
|
+
const [shares, timelockEndsAt]: [bigint, bigint] =
|
|
151
|
+
await vaultContract.getWithdrawalRequest(owner);
|
|
152
|
+
|
|
153
|
+
if (shares === 0n) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { shares, timelockEndsAt };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// R5 -- Cross-chain oracle OFF, async redeem
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Initiate an asynchronous cross-chain redeem request.
|
|
166
|
+
*
|
|
167
|
+
* CRITICAL: shares are approved to the **escrow** address (vault share token).
|
|
168
|
+
* amountLimit MUST be 0 for redeems.
|
|
169
|
+
*
|
|
170
|
+
* TXs: 1 approve to escrow for shares (if needed) + 1 initVaultActionRequest.
|
|
171
|
+
* Wait: LayerZero cross-chain fulfillment.
|
|
172
|
+
*
|
|
173
|
+
* @param signer - Wallet holding the shares.
|
|
174
|
+
* @param addresses - Must include `vault` and `escrow`.
|
|
175
|
+
* @param shares - Number of shares to redeem.
|
|
176
|
+
* @param receiver - Address that will receive the underlying assets.
|
|
177
|
+
* @param owner - Address that owns the shares.
|
|
178
|
+
* @param lzFee - Native fee for LayerZero message.
|
|
179
|
+
* @param extraOptions - Optional LZ adapter parameters (bytes).
|
|
180
|
+
* @returns The request GUID for tracking and the transaction receipt.
|
|
181
|
+
*/
|
|
182
|
+
export async function redeemAsync(
|
|
183
|
+
signer: Signer,
|
|
184
|
+
addresses: VaultAddresses,
|
|
185
|
+
shares: bigint,
|
|
186
|
+
receiver: string,
|
|
187
|
+
owner: string,
|
|
188
|
+
lzFee: bigint,
|
|
189
|
+
extraOptions: string = "0x"
|
|
190
|
+
): Promise<AsyncRequestResult> {
|
|
191
|
+
const provider = signer.provider!;
|
|
192
|
+
if (!addresses.escrow) throw new MissingEscrowAddressError();
|
|
193
|
+
const escrow = addresses.escrow;
|
|
194
|
+
|
|
195
|
+
// Pre-flight: validate async cross-chain setup before sending any transaction
|
|
196
|
+
await preflightAsync(provider, addresses.vault, escrow);
|
|
197
|
+
|
|
198
|
+
// Pre-flight: check hub has enough liquid assets — avoids wasting LZ fee on a guaranteed refund
|
|
199
|
+
await preflightRedeemLiquidity(provider, addresses.vault, shares);
|
|
200
|
+
|
|
201
|
+
// CRITICAL: approve ESCROW for shares (the vault token itself)
|
|
202
|
+
await ensureAllowance(signer, addresses.vault, escrow, shares);
|
|
203
|
+
|
|
204
|
+
const coder = AbiCoder.defaultAbiCoder();
|
|
205
|
+
const actionCallData = coder.encode(
|
|
206
|
+
["uint256", "address", "address"],
|
|
207
|
+
[shares, receiver, owner]
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const bridge = new Contract(addresses.vault, BRIDGE_ABI, signer);
|
|
211
|
+
|
|
212
|
+
// Static call first to capture the return value (guid) before broadcasting
|
|
213
|
+
const guid: string = await bridge.initVaultActionRequest.staticCall(
|
|
214
|
+
ActionType.REDEEM,
|
|
215
|
+
actionCallData,
|
|
216
|
+
0,
|
|
217
|
+
extraOptions,
|
|
218
|
+
{ value: lzFee }
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const tx = await bridge.initVaultActionRequest(
|
|
222
|
+
ActionType.REDEEM,
|
|
223
|
+
actionCallData,
|
|
224
|
+
0, // amountLimit MUST be 0 for redeems
|
|
225
|
+
extraOptions,
|
|
226
|
+
{ value: lzFee }
|
|
227
|
+
);
|
|
228
|
+
const receipt = await tx.wait();
|
|
229
|
+
|
|
230
|
+
return { receipt, guid };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// R6 -- Spoke -> Hub redeem (step 1: bridge shares to hub)
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Bridge shares from a spoke chain to the hub chain (step 1 of 2).
|
|
239
|
+
*
|
|
240
|
+
* After the shares arrive on the hub chain, call `redeemShares()` on the
|
|
241
|
+
* hub to complete the redemption.
|
|
242
|
+
*
|
|
243
|
+
* TXs: 1 approve (if needed) + 1 OFT.send().
|
|
244
|
+
* Wait: LayerZero delivery (typically 1-5 minutes).
|
|
245
|
+
*
|
|
246
|
+
* @param signer - Wallet on the spoke chain holding shares.
|
|
247
|
+
* @param shareOFT - OFTAdapter address for the share token on spoke.
|
|
248
|
+
* @param hubChainEid - LayerZero endpoint ID for the hub chain (e.g. 30332 for Flow EVM).
|
|
249
|
+
* @param shares - Number of shares to bridge.
|
|
250
|
+
* @param receiver - Address on hub chain that will receive the shares.
|
|
251
|
+
* @param lzFee - Native fee for LayerZero message.
|
|
252
|
+
* @returns Transaction receipt.
|
|
253
|
+
*/
|
|
254
|
+
export async function bridgeSharesToHub(
|
|
255
|
+
signer: Signer,
|
|
256
|
+
shareOFT: string,
|
|
257
|
+
hubChainEid: number,
|
|
258
|
+
shares: bigint,
|
|
259
|
+
receiver: string,
|
|
260
|
+
lzFee: bigint
|
|
261
|
+
): Promise<{ receipt: ContractTransactionReceipt }> {
|
|
262
|
+
await ensureAllowance(signer, shareOFT, shareOFT, shares);
|
|
263
|
+
|
|
264
|
+
const oft = new Contract(shareOFT, OFT_ABI, signer);
|
|
265
|
+
const refundAddress = await signer.getAddress();
|
|
266
|
+
const toBytes32 = zeroPadValue(receiver, 32);
|
|
267
|
+
|
|
268
|
+
const sendParam = {
|
|
269
|
+
dstEid: hubChainEid,
|
|
270
|
+
to: toBytes32,
|
|
271
|
+
amountLD: shares,
|
|
272
|
+
minAmountLD: shares, // no slippage on share bridging
|
|
273
|
+
extraOptions: "0x",
|
|
274
|
+
composeMsg: "0x",
|
|
275
|
+
oftCmd: "0x",
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const msgFee = { nativeFee: lzFee, lzTokenFee: 0n };
|
|
279
|
+
|
|
280
|
+
const tx = await oft.send(sendParam, msgFee, refundAddress, {
|
|
281
|
+
value: lzFee,
|
|
282
|
+
});
|
|
283
|
+
const receipt = await tx.wait();
|
|
284
|
+
|
|
285
|
+
return { receipt };
|
|
286
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Signer, Provider, ContractTransactionReceipt } from "ethers";
|
|
2
|
+
|
|
3
|
+
/** Addresses involved in vault operations. */
|
|
4
|
+
export interface VaultAddresses {
|
|
5
|
+
/** Hub vault (diamond proxy) address. */
|
|
6
|
+
vault: string;
|
|
7
|
+
/** MoreVaultsEscrow address for cross-chain locking. */
|
|
8
|
+
escrow?: string;
|
|
9
|
+
/** OFTAdapter address for share token bridging (cross-chain only). */
|
|
10
|
+
shareOFT?: string;
|
|
11
|
+
/** OFT address for USDC bridging (cross-chain only). */
|
|
12
|
+
usdcOFT?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Result of a synchronous deposit or mint. */
|
|
16
|
+
export interface DepositResult {
|
|
17
|
+
receipt: ContractTransactionReceipt;
|
|
18
|
+
/** Number of vault shares minted. */
|
|
19
|
+
shares: bigint;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Result of a synchronous redeem or withdraw. */
|
|
23
|
+
export interface RedeemResult {
|
|
24
|
+
receipt: ContractTransactionReceipt;
|
|
25
|
+
/** Number of underlying assets returned. */
|
|
26
|
+
assets: bigint;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Result of an asynchronous cross-chain request. */
|
|
30
|
+
export interface AsyncRequestResult {
|
|
31
|
+
receipt: ContractTransactionReceipt;
|
|
32
|
+
/** bytes32 request GUID for tracking fulfillment. */
|
|
33
|
+
guid: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* ActionType enum matching MoreVaultsLib.ActionType on-chain values.
|
|
38
|
+
*
|
|
39
|
+
* DEPOSIT = 0, MINT = 1, WITHDRAW = 2, REDEEM = 3,
|
|
40
|
+
* MULTI_ASSETS_DEPOSIT = 4, ACCRUE_FEES = 5
|
|
41
|
+
*/
|
|
42
|
+
export const ActionType = {
|
|
43
|
+
DEPOSIT: 0,
|
|
44
|
+
MINT: 1,
|
|
45
|
+
WITHDRAW: 2,
|
|
46
|
+
REDEEM: 3,
|
|
47
|
+
MULTI_ASSETS_DEPOSIT: 4,
|
|
48
|
+
ACCRUE_FEES: 5,
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
51
|
+
export type ActionTypeValue = (typeof ActionType)[keyof typeof ActionType];
|
|
52
|
+
|
|
53
|
+
/** Cross-chain request info returned by getRequestInfo. */
|
|
54
|
+
export interface CrossChainRequestInfo {
|
|
55
|
+
initiator: string;
|
|
56
|
+
timestamp: bigint;
|
|
57
|
+
actionType: number;
|
|
58
|
+
actionCallData: string;
|
|
59
|
+
fulfilled: boolean;
|
|
60
|
+
finalized: boolean;
|
|
61
|
+
refunded: boolean;
|
|
62
|
+
totalAssets: bigint;
|
|
63
|
+
finalizationResult: bigint;
|
|
64
|
+
amountLimit: bigint;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type { Signer, Provider, ContractTransactionReceipt };
|