@oydual31/more-vaults-sdk 0.3.2 → 0.4.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/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.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/{spokeRoutes-DK7cIW4z.d.cts → spokeRoutes-BIafSbQ3.d.cts} +13 -2
- package/dist/{spokeRoutes-DK7cIW4z.d.ts → spokeRoutes-BIafSbQ3.d.ts} +13 -2
- package/dist/viem/index.cjs +34 -28
- package/dist/viem/index.cjs.map +1 -1
- package/dist/viem/index.d.cts +1 -1
- package/dist/viem/index.d.ts +1 -1
- package/dist/viem/index.js +34 -29
- package/dist/viem/index.js.map +1 -1
- 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/viem/crossChainFlows.ts +12 -22
- package/src/viem/index.ts +1 -0
- package/src/viem/preflight.ts +3 -9
- package/src/viem/redeemFlows.ts +4 -5
- package/src/viem/utils.ts +40 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curator MulticallFacet write operations for the MoreVaults ethers.js v6 SDK.
|
|
3
|
+
*
|
|
4
|
+
* Provides typed helpers to submit, execute, and veto curator action batches
|
|
5
|
+
* on any MoreVaults diamond that has the MulticallFacet installed.
|
|
6
|
+
*
|
|
7
|
+
* @module curatorMulticall
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Contract, Interface } from "ethers";
|
|
11
|
+
import type { Signer, ContractTransactionReceipt } from "ethers";
|
|
12
|
+
import {
|
|
13
|
+
MULTICALL_ABI,
|
|
14
|
+
DEX_ABI,
|
|
15
|
+
ERC7540_FACET_ABI,
|
|
16
|
+
ERC4626_FACET_ABI,
|
|
17
|
+
} from "./abis";
|
|
18
|
+
import type { CuratorAction, SubmitActionsResult } from "./types";
|
|
19
|
+
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
// Encoding helpers
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Encode a single typed CuratorAction into raw calldata bytes suitable for
|
|
26
|
+
* passing into `submitActions(bytes[] actionsData)`.
|
|
27
|
+
*
|
|
28
|
+
* The encoded bytes are the full ABI-encoded function call (4-byte selector +
|
|
29
|
+
* arguments) targeting the vault diamond itself — the MulticallFacet will
|
|
30
|
+
* call `address(this).call(actionsData[i])` for each entry.
|
|
31
|
+
*
|
|
32
|
+
* @param action A discriminated-union CuratorAction describing what to do
|
|
33
|
+
* @returns ABI-encoded calldata hex string
|
|
34
|
+
*/
|
|
35
|
+
export function encodeCuratorAction(action: CuratorAction): string {
|
|
36
|
+
switch (action.type) {
|
|
37
|
+
case 'swap': {
|
|
38
|
+
const iface = new Interface(DEX_ABI as unknown as string[]);
|
|
39
|
+
return iface.encodeFunctionData("executeSwap", [
|
|
40
|
+
{
|
|
41
|
+
targetContract: action.params.targetContract,
|
|
42
|
+
tokenIn: action.params.tokenIn,
|
|
43
|
+
tokenOut: action.params.tokenOut,
|
|
44
|
+
maxAmountIn: action.params.maxAmountIn,
|
|
45
|
+
minAmountOut: action.params.minAmountOut,
|
|
46
|
+
swapCallData: action.params.swapCallData,
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case 'batchSwap': {
|
|
52
|
+
const iface = new Interface(DEX_ABI as unknown as string[]);
|
|
53
|
+
return iface.encodeFunctionData("executeBatchSwap", [
|
|
54
|
+
{
|
|
55
|
+
swaps: action.params.swaps.map((s) => ({
|
|
56
|
+
targetContract: s.targetContract,
|
|
57
|
+
tokenIn: s.tokenIn,
|
|
58
|
+
tokenOut: s.tokenOut,
|
|
59
|
+
maxAmountIn: s.maxAmountIn,
|
|
60
|
+
minAmountOut: s.minAmountOut,
|
|
61
|
+
swapCallData: s.swapCallData,
|
|
62
|
+
})),
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case 'erc4626Deposit': {
|
|
68
|
+
const iface = new Interface(ERC4626_FACET_ABI as unknown as string[]);
|
|
69
|
+
return iface.encodeFunctionData("erc4626Deposit", [action.vault, action.assets]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case 'erc4626Redeem': {
|
|
73
|
+
const iface = new Interface(ERC4626_FACET_ABI as unknown as string[]);
|
|
74
|
+
return iface.encodeFunctionData("erc4626Redeem", [action.vault, action.shares]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
case 'erc7540RequestDeposit': {
|
|
78
|
+
const iface = new Interface(ERC7540_FACET_ABI as unknown as string[]);
|
|
79
|
+
return iface.encodeFunctionData("erc7540RequestDeposit", [action.vault, action.assets]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'erc7540Deposit': {
|
|
83
|
+
const iface = new Interface(ERC7540_FACET_ABI as unknown as string[]);
|
|
84
|
+
return iface.encodeFunctionData("erc7540Deposit", [action.vault, action.assets]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 'erc7540RequestRedeem': {
|
|
88
|
+
const iface = new Interface(ERC7540_FACET_ABI as unknown as string[]);
|
|
89
|
+
return iface.encodeFunctionData("erc7540RequestRedeem", [action.vault, action.shares]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case 'erc7540Redeem': {
|
|
93
|
+
const iface = new Interface(ERC7540_FACET_ABI as unknown as string[]);
|
|
94
|
+
return iface.encodeFunctionData("erc7540Redeem", [action.vault, action.shares]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
default: {
|
|
98
|
+
// TypeScript exhaustiveness check — this branch is never reached at runtime
|
|
99
|
+
const _exhaustive: never = action;
|
|
100
|
+
throw new Error(`[MoreVaults] Unknown CuratorAction type: ${(_exhaustive as any).type}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Encode an array of CuratorActions into a calldata array ready for
|
|
107
|
+
* `submitActions`.
|
|
108
|
+
*
|
|
109
|
+
* @param actions Array of typed CuratorAction objects
|
|
110
|
+
* @returns Array of ABI-encoded calldata hex strings
|
|
111
|
+
*/
|
|
112
|
+
export function buildCuratorBatch(actions: CuratorAction[]): string[] {
|
|
113
|
+
return actions.map(encodeCuratorAction);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
117
|
+
// Write operations
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Submit a batch of curator actions to the vault's MulticallFacet.
|
|
122
|
+
*
|
|
123
|
+
* When `timeLockPeriod == 0` the contract immediately executes the actions
|
|
124
|
+
* inside `submitActions` itself. When a timelock is configured the actions
|
|
125
|
+
* are queued and must be executed later with `executeActions`.
|
|
126
|
+
*
|
|
127
|
+
* After the write succeeds, reads `getCurrentNonce` to determine which nonce
|
|
128
|
+
* was assigned (nonce - 1 after the submit increments it).
|
|
129
|
+
*
|
|
130
|
+
* @param signer Signer with curator account attached
|
|
131
|
+
* @param vault Vault address (diamond proxy)
|
|
132
|
+
* @param actions Array of raw calldata bytes — use `buildCuratorBatch` to build
|
|
133
|
+
* @returns Receipt and the nonce assigned to this batch
|
|
134
|
+
*/
|
|
135
|
+
export async function submitActions(
|
|
136
|
+
signer: Signer,
|
|
137
|
+
vault: string,
|
|
138
|
+
actions: string[]
|
|
139
|
+
): Promise<SubmitActionsResult> {
|
|
140
|
+
const multicallContract = new Contract(vault, MULTICALL_ABI, signer);
|
|
141
|
+
|
|
142
|
+
const tx = await multicallContract.submitActions(actions);
|
|
143
|
+
const receipt: ContractTransactionReceipt = await tx.wait();
|
|
144
|
+
|
|
145
|
+
// Read the nonce that was assigned: the contract increments actionNonce after storing,
|
|
146
|
+
// so getCurrentNonce now returns (assignedNonce + 1). Subtract 1 to recover it.
|
|
147
|
+
const nextNonce = (await multicallContract.getCurrentNonce()) as bigint;
|
|
148
|
+
const nonce = nextNonce - 1n;
|
|
149
|
+
|
|
150
|
+
return { receipt, nonce };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Execute pending actions after their timelock period has expired.
|
|
155
|
+
*
|
|
156
|
+
* Can only be called when `block.timestamp >= pendingUntil`. The contract
|
|
157
|
+
* reverts with `ActionsStillPending` if the timelock has not expired.
|
|
158
|
+
*
|
|
159
|
+
* @param signer Signer with curator account attached
|
|
160
|
+
* @param vault Vault address (diamond proxy)
|
|
161
|
+
* @param nonce The action batch nonce to execute
|
|
162
|
+
* @returns Transaction receipt
|
|
163
|
+
*/
|
|
164
|
+
export async function executeActions(
|
|
165
|
+
signer: Signer,
|
|
166
|
+
vault: string,
|
|
167
|
+
nonce: bigint
|
|
168
|
+
): Promise<ContractTransactionReceipt> {
|
|
169
|
+
const multicallContract = new Contract(vault, MULTICALL_ABI, signer);
|
|
170
|
+
|
|
171
|
+
const tx = await multicallContract.executeActions(nonce);
|
|
172
|
+
return tx.wait() as Promise<ContractTransactionReceipt>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Guardian-only: cancel (veto) one or more pending action batches.
|
|
177
|
+
*
|
|
178
|
+
* Deletes the pending actions from storage, preventing them from ever being
|
|
179
|
+
* executed. Only the vault guardian can call this.
|
|
180
|
+
*
|
|
181
|
+
* @param signer Signer with guardian account attached
|
|
182
|
+
* @param vault Vault address (diamond proxy)
|
|
183
|
+
* @param nonces Array of action nonces to cancel
|
|
184
|
+
* @returns Transaction receipt
|
|
185
|
+
*/
|
|
186
|
+
export async function vetoActions(
|
|
187
|
+
signer: Signer,
|
|
188
|
+
vault: string,
|
|
189
|
+
nonces: bigint[]
|
|
190
|
+
): Promise<ContractTransactionReceipt> {
|
|
191
|
+
const multicallContract = new Contract(vault, MULTICALL_ABI, signer);
|
|
192
|
+
|
|
193
|
+
const tx = await multicallContract.vetoActions(nonces);
|
|
194
|
+
return tx.wait() as Promise<ContractTransactionReceipt>;
|
|
195
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curator / vault-manager read helpers for the MoreVaults ethers.js v6 SDK.
|
|
3
|
+
*
|
|
4
|
+
* All functions are read-only (no wallet needed) and use Multicall3 for
|
|
5
|
+
* batched RPC efficiency.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Contract, Interface } from "ethers";
|
|
9
|
+
import type { Provider } from "ethers";
|
|
10
|
+
import {
|
|
11
|
+
MULTICALL_ABI,
|
|
12
|
+
CURATOR_CONFIG_ABI,
|
|
13
|
+
VAULT_ANALYSIS_ABI,
|
|
14
|
+
REGISTRY_ABI,
|
|
15
|
+
METADATA_ABI,
|
|
16
|
+
ERC20_ABI,
|
|
17
|
+
VAULT_ABI,
|
|
18
|
+
} from "./abis";
|
|
19
|
+
import type {
|
|
20
|
+
CuratorVaultStatus,
|
|
21
|
+
PendingAction,
|
|
22
|
+
VaultAnalysis,
|
|
23
|
+
AssetInfo,
|
|
24
|
+
AssetBalance,
|
|
25
|
+
VaultAssetBreakdown,
|
|
26
|
+
} from "./types";
|
|
27
|
+
|
|
28
|
+
// Multicall3 — deployed at the same address on every EVM chain
|
|
29
|
+
const MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
30
|
+
const MULTICALL3_ABI = [
|
|
31
|
+
"function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read a comprehensive status snapshot for the curator dashboard.
|
|
38
|
+
*
|
|
39
|
+
* Fetches in one multicall batch:
|
|
40
|
+
* curator, timeLockPeriod, getMaxSlippagePercent, getCurrentNonce,
|
|
41
|
+
* getAvailableAssets, getCrossChainAccountingManager, paused
|
|
42
|
+
*
|
|
43
|
+
* @param provider Read-only provider (must be on the vault's chain)
|
|
44
|
+
* @param vault Vault address (diamond proxy)
|
|
45
|
+
* @returns CuratorVaultStatus snapshot
|
|
46
|
+
*/
|
|
47
|
+
export async function getCuratorVaultStatus(
|
|
48
|
+
provider: Provider,
|
|
49
|
+
vault: string
|
|
50
|
+
): Promise<CuratorVaultStatus> {
|
|
51
|
+
const mc = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
52
|
+
const curatorConfigIface = new Interface(CURATOR_CONFIG_ABI as unknown as string[]);
|
|
53
|
+
const multicallIface = new Interface(MULTICALL_ABI as unknown as string[]);
|
|
54
|
+
|
|
55
|
+
const calls = [
|
|
56
|
+
{ target: vault, allowFailure: false, callData: curatorConfigIface.encodeFunctionData("curator") },
|
|
57
|
+
{ target: vault, allowFailure: false, callData: curatorConfigIface.encodeFunctionData("timeLockPeriod") },
|
|
58
|
+
{ target: vault, allowFailure: false, callData: curatorConfigIface.encodeFunctionData("getMaxSlippagePercent") },
|
|
59
|
+
{ target: vault, allowFailure: false, callData: multicallIface.encodeFunctionData("getCurrentNonce") },
|
|
60
|
+
{ target: vault, allowFailure: false, callData: curatorConfigIface.encodeFunctionData("getAvailableAssets") },
|
|
61
|
+
{ target: vault, allowFailure: false, callData: curatorConfigIface.encodeFunctionData("getCrossChainAccountingManager") },
|
|
62
|
+
{ target: vault, allowFailure: false, callData: curatorConfigIface.encodeFunctionData("paused") },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const results: { success: boolean; returnData: string }[] =
|
|
66
|
+
await mc.aggregate3.staticCall(calls);
|
|
67
|
+
|
|
68
|
+
const curator = curatorConfigIface.decodeFunctionResult("curator", results[0].returnData)[0] as string;
|
|
69
|
+
const timeLockPeriod = curatorConfigIface.decodeFunctionResult("timeLockPeriod", results[1].returnData)[0] as bigint;
|
|
70
|
+
const maxSlippagePercent = curatorConfigIface.decodeFunctionResult("getMaxSlippagePercent", results[2].returnData)[0] as bigint;
|
|
71
|
+
const currentNonce = multicallIface.decodeFunctionResult("getCurrentNonce", results[3].returnData)[0] as bigint;
|
|
72
|
+
const availableAssets = curatorConfigIface.decodeFunctionResult("getAvailableAssets", results[4].returnData)[0] as string[];
|
|
73
|
+
const lzAdapter = curatorConfigIface.decodeFunctionResult("getCrossChainAccountingManager", results[5].returnData)[0] as string;
|
|
74
|
+
const paused = curatorConfigIface.decodeFunctionResult("paused", results[6].returnData)[0] as boolean;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
curator,
|
|
78
|
+
timeLockPeriod,
|
|
79
|
+
maxSlippagePercent,
|
|
80
|
+
currentNonce,
|
|
81
|
+
availableAssets,
|
|
82
|
+
lzAdapter,
|
|
83
|
+
paused,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Fetch pending actions for a specific nonce and resolve whether they are
|
|
91
|
+
* executable (i.e. the timelock has expired).
|
|
92
|
+
*
|
|
93
|
+
* @param provider Read-only provider (must be on the vault's chain)
|
|
94
|
+
* @param vault Vault address (diamond proxy)
|
|
95
|
+
* @param nonce Action nonce to query
|
|
96
|
+
* @returns PendingAction with isExecutable flag set
|
|
97
|
+
*/
|
|
98
|
+
export async function getPendingActions(
|
|
99
|
+
provider: Provider,
|
|
100
|
+
vault: string,
|
|
101
|
+
nonce: bigint
|
|
102
|
+
): Promise<PendingAction> {
|
|
103
|
+
const multicallContract = new Contract(vault, MULTICALL_ABI, provider);
|
|
104
|
+
|
|
105
|
+
const [result, block] = await Promise.all([
|
|
106
|
+
multicallContract.getPendingActions(nonce) as Promise<[string[], bigint]>,
|
|
107
|
+
provider.getBlock("latest"),
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
const [actionsData, pendingUntil] = result;
|
|
111
|
+
const currentTimestamp = BigInt(block!.timestamp);
|
|
112
|
+
const isExecutable = pendingUntil > 0n && currentTimestamp >= pendingUntil;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
nonce,
|
|
116
|
+
actionsData,
|
|
117
|
+
pendingUntil,
|
|
118
|
+
isExecutable,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check whether a given address is the curator of the vault.
|
|
126
|
+
*
|
|
127
|
+
* @param provider Read-only provider (must be on the vault's chain)
|
|
128
|
+
* @param vault Vault address (diamond proxy)
|
|
129
|
+
* @param address Address to check
|
|
130
|
+
* @returns true if address is the current curator
|
|
131
|
+
*/
|
|
132
|
+
export async function isCurator(
|
|
133
|
+
provider: Provider,
|
|
134
|
+
vault: string,
|
|
135
|
+
address: string
|
|
136
|
+
): Promise<boolean> {
|
|
137
|
+
const config = new Contract(vault, CURATOR_CONFIG_ABI, provider);
|
|
138
|
+
const curatorAddress = (await config.curator()) as string;
|
|
139
|
+
return curatorAddress.toLowerCase() === address.toLowerCase();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Full vault analysis — available assets with metadata, depositable assets, whitelist config.
|
|
146
|
+
* Useful for curator dashboards to understand what the vault can do.
|
|
147
|
+
*
|
|
148
|
+
* @param provider Read-only provider (must be on the vault's chain)
|
|
149
|
+
* @param vault Vault address (diamond proxy)
|
|
150
|
+
* @returns VaultAnalysis snapshot
|
|
151
|
+
*/
|
|
152
|
+
export async function getVaultAnalysis(
|
|
153
|
+
provider: Provider,
|
|
154
|
+
vault: string
|
|
155
|
+
): Promise<VaultAnalysis> {
|
|
156
|
+
const analysisContract = new Contract(vault, VAULT_ANALYSIS_ABI, provider);
|
|
157
|
+
|
|
158
|
+
// Batch 1: fetch asset lists, whitelist flag, and registry address in parallel
|
|
159
|
+
const [availableRaw, depositableRaw, depositWhitelistEnabled, registryResult] =
|
|
160
|
+
await Promise.all([
|
|
161
|
+
analysisContract.getAvailableAssets() as Promise<string[]>,
|
|
162
|
+
analysisContract.getDepositableAssets() as Promise<string[]>,
|
|
163
|
+
analysisContract.isDepositWhitelistEnabled() as Promise<boolean>,
|
|
164
|
+
(analysisContract.moreVaultsRegistry() as Promise<string>).catch(() => null),
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
const availableAddresses = availableRaw as string[];
|
|
168
|
+
const depositableAddresses = depositableRaw as string[];
|
|
169
|
+
|
|
170
|
+
// Deduplicated set of all asset addresses we need metadata for
|
|
171
|
+
const allAddresses = Array.from(new Set([...availableAddresses, ...depositableAddresses]));
|
|
172
|
+
|
|
173
|
+
// Batch 2: multicall for name/symbol/decimals on all unique assets
|
|
174
|
+
const assetInfoMap = new Map<string, AssetInfo>();
|
|
175
|
+
|
|
176
|
+
if (allAddresses.length > 0) {
|
|
177
|
+
const mc = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
178
|
+
const metaIface = new Interface(METADATA_ABI as unknown as string[]);
|
|
179
|
+
|
|
180
|
+
const metadataCalls = allAddresses.flatMap((addr) => [
|
|
181
|
+
{ target: addr, allowFailure: true, callData: metaIface.encodeFunctionData("name") },
|
|
182
|
+
{ target: addr, allowFailure: true, callData: metaIface.encodeFunctionData("symbol") },
|
|
183
|
+
{ target: addr, allowFailure: true, callData: metaIface.encodeFunctionData("decimals") },
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
const metadataResults: { success: boolean; returnData: string }[] =
|
|
187
|
+
await mc.aggregate3.staticCall(metadataCalls);
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i < allAddresses.length; i++) {
|
|
190
|
+
const addr = allAddresses[i];
|
|
191
|
+
const nameRes = metadataResults[i * 3];
|
|
192
|
+
const symbolRes = metadataResults[i * 3 + 1];
|
|
193
|
+
const decimalsRes = metadataResults[i * 3 + 2];
|
|
194
|
+
|
|
195
|
+
const name = nameRes.success ? (metaIface.decodeFunctionResult("name", nameRes.returnData)[0] as string) : '';
|
|
196
|
+
const symbol = symbolRes.success ? (metaIface.decodeFunctionResult("symbol", symbolRes.returnData)[0] as string) : '';
|
|
197
|
+
const decimals = decimalsRes.success ? (Number(metaIface.decodeFunctionResult("decimals", decimalsRes.returnData)[0])) : 18;
|
|
198
|
+
|
|
199
|
+
assetInfoMap.set(addr.toLowerCase(), { address: addr, name, symbol, decimals });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const registryAddress = registryResult ? (registryResult as string) : null;
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
availableAssets: availableAddresses.map((a) => assetInfoMap.get(a.toLowerCase())!),
|
|
207
|
+
depositableAssets: depositableAddresses.map((a) => assetInfoMap.get(a.toLowerCase())!),
|
|
208
|
+
depositWhitelistEnabled: depositWhitelistEnabled as boolean,
|
|
209
|
+
registryAddress,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Check if specific protocol addresses are whitelisted in the global registry.
|
|
217
|
+
* Useful for curators to verify DEX routers before building swap calldata.
|
|
218
|
+
*
|
|
219
|
+
* @param provider Read-only provider (must be on the vault's chain)
|
|
220
|
+
* @param vault Vault address (diamond proxy)
|
|
221
|
+
* @param protocols Protocol addresses to check
|
|
222
|
+
* @returns Record mapping address → whitelisted boolean
|
|
223
|
+
*/
|
|
224
|
+
export async function checkProtocolWhitelist(
|
|
225
|
+
provider: Provider,
|
|
226
|
+
vault: string,
|
|
227
|
+
protocols: string[]
|
|
228
|
+
): Promise<Record<string, boolean>> {
|
|
229
|
+
const analysisContract = new Contract(vault, VAULT_ANALYSIS_ABI, provider);
|
|
230
|
+
const registry = (await analysisContract.moreVaultsRegistry()) as string;
|
|
231
|
+
|
|
232
|
+
if (protocols.length === 0) return {};
|
|
233
|
+
|
|
234
|
+
const mc = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
235
|
+
const registryIface = new Interface(REGISTRY_ABI as unknown as string[]);
|
|
236
|
+
|
|
237
|
+
const calls = protocols.map((protocol) => ({
|
|
238
|
+
target: registry,
|
|
239
|
+
allowFailure: true,
|
|
240
|
+
callData: registryIface.encodeFunctionData("isWhitelisted", [protocol]),
|
|
241
|
+
}));
|
|
242
|
+
|
|
243
|
+
const results: { success: boolean; returnData: string }[] =
|
|
244
|
+
await mc.aggregate3.staticCall(calls);
|
|
245
|
+
|
|
246
|
+
const out: Record<string, boolean> = {};
|
|
247
|
+
for (let i = 0; i < protocols.length; i++) {
|
|
248
|
+
const r = results[i];
|
|
249
|
+
const whitelisted = r.success
|
|
250
|
+
? (registryIface.decodeFunctionResult("isWhitelisted", r.returnData)[0] as boolean)
|
|
251
|
+
: false;
|
|
252
|
+
out[protocols[i].toLowerCase()] = whitelisted;
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get the vault's per-asset balance breakdown on the hub chain.
|
|
261
|
+
*
|
|
262
|
+
* Returns the balance of every available asset held by the vault, plus
|
|
263
|
+
* totalAssets and totalSupply for context. Useful for portfolio views
|
|
264
|
+
* that need to show individual holdings rather than a single USD-denominated total.
|
|
265
|
+
*
|
|
266
|
+
* @param provider Read-only provider (must be on the vault's hub chain)
|
|
267
|
+
* @param vault Vault address (diamond proxy)
|
|
268
|
+
* @returns VaultAssetBreakdown with per-asset balances
|
|
269
|
+
*/
|
|
270
|
+
export async function getVaultAssetBreakdown(
|
|
271
|
+
provider: Provider,
|
|
272
|
+
vault: string
|
|
273
|
+
): Promise<VaultAssetBreakdown> {
|
|
274
|
+
// Step 1: get available assets list
|
|
275
|
+
const analysisContract = new Contract(vault, VAULT_ANALYSIS_ABI, provider);
|
|
276
|
+
const availableRaw = (await analysisContract.getAvailableAssets()) as string[];
|
|
277
|
+
const addresses = availableRaw as string[];
|
|
278
|
+
|
|
279
|
+
// Step 2: multicall — balanceOf + metadata for each asset + totalAssets + totalSupply + vault decimals
|
|
280
|
+
const mc = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
281
|
+
const vaultIface = new Interface(VAULT_ABI as unknown as string[]);
|
|
282
|
+
const metaIface = new Interface(METADATA_ABI as unknown as string[]);
|
|
283
|
+
const erc20Iface = new Interface(ERC20_ABI as unknown as string[]);
|
|
284
|
+
|
|
285
|
+
const perAssetCalls = addresses.flatMap((addr) => [
|
|
286
|
+
{ target: addr, allowFailure: true, callData: erc20Iface.encodeFunctionData("balanceOf", [vault]) },
|
|
287
|
+
{ target: addr, allowFailure: true, callData: metaIface.encodeFunctionData("name") },
|
|
288
|
+
{ target: addr, allowFailure: true, callData: metaIface.encodeFunctionData("symbol") },
|
|
289
|
+
{ target: addr, allowFailure: true, callData: metaIface.encodeFunctionData("decimals") },
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
const totalCalls = [
|
|
293
|
+
...perAssetCalls,
|
|
294
|
+
{ target: vault, allowFailure: true, callData: vaultIface.encodeFunctionData("totalAssets") },
|
|
295
|
+
{ target: vault, allowFailure: true, callData: vaultIface.encodeFunctionData("totalSupply") },
|
|
296
|
+
{ target: vault, allowFailure: true, callData: metaIface.encodeFunctionData("decimals") },
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
const results: { success: boolean; returnData: string }[] =
|
|
300
|
+
await mc.aggregate3.staticCall(totalCalls);
|
|
301
|
+
|
|
302
|
+
const perAssetFields = 4; // balanceOf, name, symbol, decimals
|
|
303
|
+
const assets: AssetBalance[] = addresses.map((addr, i) => {
|
|
304
|
+
const base = i * perAssetFields;
|
|
305
|
+
const balance = results[base].success ? (erc20Iface.decodeFunctionResult("balanceOf", results[base].returnData)[0] as bigint) : 0n;
|
|
306
|
+
const name = results[base + 1].success ? (metaIface.decodeFunctionResult("name", results[base + 1].returnData)[0] as string) : '';
|
|
307
|
+
const symbol = results[base + 2].success ? (metaIface.decodeFunctionResult("symbol", results[base + 2].returnData)[0] as string) : '';
|
|
308
|
+
const decimals = results[base + 3].success ? (Number(metaIface.decodeFunctionResult("decimals", results[base + 3].returnData)[0])) : 18;
|
|
309
|
+
|
|
310
|
+
return { address: addr, name, symbol, decimals, balance };
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const totalsBase = addresses.length * perAssetFields;
|
|
314
|
+
const totalAssets = results[totalsBase].success ? (vaultIface.decodeFunctionResult("totalAssets", results[totalsBase].returnData)[0] as bigint) : 0n;
|
|
315
|
+
const totalSupply = results[totalsBase + 1].success ? (vaultIface.decodeFunctionResult("totalSupply", results[totalsBase + 1].returnData)[0] as bigint) : 0n;
|
|
316
|
+
const underlyingDecimals = results[totalsBase + 2].success ? (Number(metaIface.decodeFunctionResult("decimals", results[totalsBase + 2].returnData)[0])) : 6;
|
|
317
|
+
|
|
318
|
+
return { assets, totalAssets, totalSupply, underlyingDecimals };
|
|
319
|
+
}
|