@oydual31/more-vaults-sdk 0.3.0 → 0.3.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/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-FgKCJQYa.d.cts → spokeRoutes-DK7cIW4z.d.cts} +4 -0
- package/dist/{spokeRoutes-FgKCJQYa.d.ts → spokeRoutes-DK7cIW4z.d.ts} +4 -0
- package/dist/viem/index.cjs +287 -5
- package/dist/viem/index.cjs.map +1 -1
- package/dist/viem/index.d.cts +269 -3
- package/dist/viem/index.d.ts +269 -3
- package/dist/viem/index.js +279 -7
- package/dist/viem/index.js.map +1 -1
- package/package.json +1 -1
- package/src/viem/abis.ts +51 -0
- package/src/viem/curatorMulticall.ts +299 -0
- package/src/viem/curatorStatus.ts +133 -2
- package/src/viem/index.ts +14 -0
- package/src/viem/types.ts +22 -0
- package/src/viem/userHelpers.ts +22 -6
package/package.json
CHANGED
package/src/viem/abis.ts
CHANGED
|
@@ -729,6 +729,57 @@ export const LZ_ADAPTER_ABI = [
|
|
|
729
729
|
},
|
|
730
730
|
] as const
|
|
731
731
|
|
|
732
|
+
/**
|
|
733
|
+
* ERC4626Facet ABI — synchronous deposit and redeem into whitelisted ERC-4626 vaults.
|
|
734
|
+
*/
|
|
735
|
+
export const ERC4626_FACET_ABI = [
|
|
736
|
+
{
|
|
737
|
+
type: 'function',
|
|
738
|
+
name: 'erc4626Deposit',
|
|
739
|
+
inputs: [
|
|
740
|
+
{ name: 'vault', type: 'address' },
|
|
741
|
+
{ name: 'assets', type: 'uint256' },
|
|
742
|
+
],
|
|
743
|
+
outputs: [{ name: 'shares', type: 'uint256' }],
|
|
744
|
+
stateMutability: 'nonpayable',
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
type: 'function',
|
|
748
|
+
name: 'erc4626Redeem',
|
|
749
|
+
inputs: [
|
|
750
|
+
{ name: 'vault', type: 'address' },
|
|
751
|
+
{ name: 'shares', type: 'uint256' },
|
|
752
|
+
],
|
|
753
|
+
outputs: [{ name: 'assets', type: 'uint256' }],
|
|
754
|
+
stateMutability: 'nonpayable',
|
|
755
|
+
},
|
|
756
|
+
] as const
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Vault analysis ABIs — per-vault whitelist and registry reads.
|
|
760
|
+
*/
|
|
761
|
+
export const VAULT_ANALYSIS_ABI = [
|
|
762
|
+
// Asset management reads
|
|
763
|
+
{ type: 'function', name: 'getAvailableAssets', inputs: [], outputs: [{ type: 'address[]' }], stateMutability: 'view' },
|
|
764
|
+
{ type: 'function', name: 'getDepositableAssets', inputs: [], outputs: [{ type: 'address[]' }], stateMutability: 'view' },
|
|
765
|
+
{ type: 'function', name: 'isAssetAvailable', inputs: [{ name: 'asset', type: 'address' }], outputs: [{ type: 'bool' }], stateMutability: 'view' },
|
|
766
|
+
{ type: 'function', name: 'isAssetDepositable', inputs: [{ name: 'asset', type: 'address' }], outputs: [{ type: 'bool' }], stateMutability: 'view' },
|
|
767
|
+
// Deposit whitelist
|
|
768
|
+
{ type: 'function', name: 'isDepositWhitelistEnabled', inputs: [], outputs: [{ type: 'bool' }], stateMutability: 'view' },
|
|
769
|
+
{ type: 'function', name: 'getAvailableToDeposit', inputs: [{ name: 'depositor', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
|
|
770
|
+
// Registry
|
|
771
|
+
{ type: 'function', name: 'moreVaultsRegistry', inputs: [], outputs: [{ type: 'address' }], stateMutability: 'view' },
|
|
772
|
+
] as const
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* MoreVaultsRegistry ABI — global protocol and bridge whitelist checks.
|
|
776
|
+
*/
|
|
777
|
+
export const REGISTRY_ABI = [
|
|
778
|
+
{ type: 'function', name: 'isWhitelisted', inputs: [{ name: 'protocol', type: 'address' }], outputs: [{ type: 'bool' }], stateMutability: 'view' },
|
|
779
|
+
{ type: 'function', name: 'isBridgeAllowed', inputs: [{ name: 'bridge', type: 'address' }], outputs: [{ type: 'bool' }], stateMutability: 'view' },
|
|
780
|
+
{ type: 'function', name: 'getAllowedFacets', inputs: [], outputs: [{ type: 'address[]' }], stateMutability: 'view' },
|
|
781
|
+
] as const
|
|
782
|
+
|
|
732
783
|
/**
|
|
733
784
|
* Minimal LZ Endpoint V2 ABI for compose queue management.
|
|
734
785
|
* Used by the Stargate 2-TX flow to check compose status and execute pending composes.
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curator MulticallFacet write operations for the MoreVaults 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
|
+
* All write functions use the simulate-then-write pattern:
|
|
8
|
+
* 1. `publicClient.simulateContract` — validates on-chain, catches reverts early
|
|
9
|
+
* 2. `walletClient.writeContract` — sends the actual transaction
|
|
10
|
+
*
|
|
11
|
+
* @module curatorMulticall
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
type Address,
|
|
16
|
+
type PublicClient,
|
|
17
|
+
type WalletClient,
|
|
18
|
+
encodeFunctionData,
|
|
19
|
+
getAddress,
|
|
20
|
+
} from 'viem'
|
|
21
|
+
import {
|
|
22
|
+
MULTICALL_ABI,
|
|
23
|
+
DEX_ABI,
|
|
24
|
+
ERC7540_FACET_ABI,
|
|
25
|
+
ERC4626_FACET_ABI,
|
|
26
|
+
} from './abis.js'
|
|
27
|
+
import type {
|
|
28
|
+
CuratorAction,
|
|
29
|
+
SubmitActionsResult,
|
|
30
|
+
} from './types.js'
|
|
31
|
+
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
// Encoding helpers
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Encode a single typed CuratorAction into raw calldata bytes suitable for
|
|
38
|
+
* passing into `submitActions(bytes[] actionsData)`.
|
|
39
|
+
*
|
|
40
|
+
* The encoded bytes are the full ABI-encoded function call (4-byte selector +
|
|
41
|
+
* arguments) targeting the vault diamond itself — the MulticallFacet will
|
|
42
|
+
* call `address(this).call(actionsData[i])` for each entry.
|
|
43
|
+
*
|
|
44
|
+
* @param action A discriminated-union CuratorAction describing what to do
|
|
45
|
+
* @returns ABI-encoded calldata bytes (`0x`-prefixed hex string)
|
|
46
|
+
*/
|
|
47
|
+
export function encodeCuratorAction(action: CuratorAction): `0x${string}` {
|
|
48
|
+
switch (action.type) {
|
|
49
|
+
case 'swap':
|
|
50
|
+
return encodeFunctionData({
|
|
51
|
+
abi: DEX_ABI,
|
|
52
|
+
functionName: 'executeSwap',
|
|
53
|
+
args: [
|
|
54
|
+
{
|
|
55
|
+
targetContract: getAddress(action.params.targetContract),
|
|
56
|
+
tokenIn: getAddress(action.params.tokenIn),
|
|
57
|
+
tokenOut: getAddress(action.params.tokenOut),
|
|
58
|
+
maxAmountIn: action.params.maxAmountIn,
|
|
59
|
+
minAmountOut: action.params.minAmountOut,
|
|
60
|
+
swapCallData: action.params.swapCallData,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
case 'batchSwap':
|
|
66
|
+
return encodeFunctionData({
|
|
67
|
+
abi: DEX_ABI,
|
|
68
|
+
functionName: 'executeBatchSwap',
|
|
69
|
+
args: [
|
|
70
|
+
{
|
|
71
|
+
swaps: action.params.swaps.map((s) => ({
|
|
72
|
+
targetContract: getAddress(s.targetContract),
|
|
73
|
+
tokenIn: getAddress(s.tokenIn),
|
|
74
|
+
tokenOut: getAddress(s.tokenOut),
|
|
75
|
+
maxAmountIn: s.maxAmountIn,
|
|
76
|
+
minAmountOut: s.minAmountOut,
|
|
77
|
+
swapCallData: s.swapCallData,
|
|
78
|
+
})),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
case 'erc4626Deposit':
|
|
84
|
+
return encodeFunctionData({
|
|
85
|
+
abi: ERC4626_FACET_ABI,
|
|
86
|
+
functionName: 'erc4626Deposit',
|
|
87
|
+
args: [getAddress(action.vault), action.assets],
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
case 'erc4626Redeem':
|
|
91
|
+
return encodeFunctionData({
|
|
92
|
+
abi: ERC4626_FACET_ABI,
|
|
93
|
+
functionName: 'erc4626Redeem',
|
|
94
|
+
args: [getAddress(action.vault), action.shares],
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
case 'erc7540RequestDeposit':
|
|
98
|
+
return encodeFunctionData({
|
|
99
|
+
abi: ERC7540_FACET_ABI,
|
|
100
|
+
functionName: 'erc7540RequestDeposit',
|
|
101
|
+
args: [getAddress(action.vault), action.assets],
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
case 'erc7540Deposit':
|
|
105
|
+
return encodeFunctionData({
|
|
106
|
+
abi: ERC7540_FACET_ABI,
|
|
107
|
+
functionName: 'erc7540Deposit',
|
|
108
|
+
args: [getAddress(action.vault), action.assets],
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
case 'erc7540RequestRedeem':
|
|
112
|
+
return encodeFunctionData({
|
|
113
|
+
abi: ERC7540_FACET_ABI,
|
|
114
|
+
functionName: 'erc7540RequestRedeem',
|
|
115
|
+
args: [getAddress(action.vault), action.shares],
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
case 'erc7540Redeem':
|
|
119
|
+
return encodeFunctionData({
|
|
120
|
+
abi: ERC7540_FACET_ABI,
|
|
121
|
+
functionName: 'erc7540Redeem',
|
|
122
|
+
args: [getAddress(action.vault), action.shares],
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
default: {
|
|
126
|
+
// TypeScript exhaustiveness check — this branch is never reached at runtime
|
|
127
|
+
const _exhaustive: never = action
|
|
128
|
+
throw new Error(`[MoreVaults] Unknown CuratorAction type: ${(_exhaustive as any).type}`)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Encode an array of CuratorActions into a calldata array ready for
|
|
135
|
+
* `submitActions`.
|
|
136
|
+
*
|
|
137
|
+
* @param actions Array of typed CuratorAction objects
|
|
138
|
+
* @returns Array of ABI-encoded calldata hex strings
|
|
139
|
+
*/
|
|
140
|
+
export function buildCuratorBatch(actions: CuratorAction[]): `0x${string}`[] {
|
|
141
|
+
return actions.map(encodeCuratorAction)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
// Write operations
|
|
146
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Submit a batch of curator actions to the vault's MulticallFacet.
|
|
150
|
+
*
|
|
151
|
+
* When `timeLockPeriod == 0` the contract immediately executes the actions
|
|
152
|
+
* inside `submitActions` itself. When a timelock is configured the actions
|
|
153
|
+
* are queued and must be executed later with `executeActions`.
|
|
154
|
+
*
|
|
155
|
+
* Uses the simulate-then-write pattern: simulation runs first so any on-chain
|
|
156
|
+
* revert (wrong curator, bad selector, slippage limit, etc.) surfaces before
|
|
157
|
+
* any gas is spent on a failing transaction.
|
|
158
|
+
*
|
|
159
|
+
* After the write succeeds, the function reads `getCurrentNonce` to determine
|
|
160
|
+
* which nonce was assigned to this batch (nonce - 1 after the submit increments it).
|
|
161
|
+
*
|
|
162
|
+
* @param walletClient Wallet client with curator account attached
|
|
163
|
+
* @param publicClient Public client for reads and simulation
|
|
164
|
+
* @param vault Vault address (diamond proxy)
|
|
165
|
+
* @param actions Array of raw calldata bytes — use `buildCuratorBatch` to build
|
|
166
|
+
* @returns Transaction hash and the nonce assigned to this batch
|
|
167
|
+
* @throws If the caller is not the curator, or any action selector is
|
|
168
|
+
* not allowed, or any action would revert
|
|
169
|
+
*/
|
|
170
|
+
export async function submitActions(
|
|
171
|
+
walletClient: WalletClient,
|
|
172
|
+
publicClient: PublicClient,
|
|
173
|
+
vault: Address,
|
|
174
|
+
actions: `0x${string}`[],
|
|
175
|
+
): Promise<SubmitActionsResult> {
|
|
176
|
+
const account = walletClient.account!
|
|
177
|
+
const v = getAddress(vault)
|
|
178
|
+
|
|
179
|
+
// Simulate first — catches permission errors and reverts before spending gas
|
|
180
|
+
await publicClient.simulateContract({
|
|
181
|
+
address: v,
|
|
182
|
+
abi: MULTICALL_ABI,
|
|
183
|
+
functionName: 'submitActions',
|
|
184
|
+
args: [actions],
|
|
185
|
+
account: account.address,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const txHash = await walletClient.writeContract({
|
|
189
|
+
address: v,
|
|
190
|
+
abi: MULTICALL_ABI,
|
|
191
|
+
functionName: 'submitActions',
|
|
192
|
+
args: [actions],
|
|
193
|
+
account,
|
|
194
|
+
chain: walletClient.chain,
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// Read the nonce that was assigned: the contract increments actionNonce after storing,
|
|
198
|
+
// so getCurrentNonce now returns (assignedNonce + 1). Subtract 1 to recover it.
|
|
199
|
+
const nextNonce = await publicClient.readContract({
|
|
200
|
+
address: v,
|
|
201
|
+
abi: MULTICALL_ABI,
|
|
202
|
+
functionName: 'getCurrentNonce',
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const nonce = nextNonce - 1n
|
|
206
|
+
|
|
207
|
+
return { txHash, nonce }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Execute pending actions after their timelock period has expired.
|
|
212
|
+
*
|
|
213
|
+
* Can only be called when `block.timestamp >= pendingUntil`. The contract
|
|
214
|
+
* reverts with `ActionsStillPending` if the timelock has not expired.
|
|
215
|
+
*
|
|
216
|
+
* Caller must be the curator (or any address when timeLockPeriod == 0, since
|
|
217
|
+
* in that case `submitActions` auto-executes and there is nothing to execute here).
|
|
218
|
+
*
|
|
219
|
+
* Uses simulate-then-write to surface on-chain reverts early.
|
|
220
|
+
*
|
|
221
|
+
* @param walletClient Wallet client with curator account attached
|
|
222
|
+
* @param publicClient Public client for reads and simulation
|
|
223
|
+
* @param vault Vault address (diamond proxy)
|
|
224
|
+
* @param nonce The action batch nonce to execute
|
|
225
|
+
* @returns Transaction hash
|
|
226
|
+
*/
|
|
227
|
+
export async function executeActions(
|
|
228
|
+
walletClient: WalletClient,
|
|
229
|
+
publicClient: PublicClient,
|
|
230
|
+
vault: Address,
|
|
231
|
+
nonce: bigint,
|
|
232
|
+
): Promise<{ txHash: `0x${string}` }> {
|
|
233
|
+
const account = walletClient.account!
|
|
234
|
+
const v = getAddress(vault)
|
|
235
|
+
|
|
236
|
+
// Simulate to surface reverts (NoSuchActions, ActionsStillPending, slippage)
|
|
237
|
+
await publicClient.simulateContract({
|
|
238
|
+
address: v,
|
|
239
|
+
abi: MULTICALL_ABI,
|
|
240
|
+
functionName: 'executeActions',
|
|
241
|
+
args: [nonce],
|
|
242
|
+
account: account.address,
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
const txHash = await walletClient.writeContract({
|
|
246
|
+
address: v,
|
|
247
|
+
abi: MULTICALL_ABI,
|
|
248
|
+
functionName: 'executeActions',
|
|
249
|
+
args: [nonce],
|
|
250
|
+
account,
|
|
251
|
+
chain: walletClient.chain,
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
return { txHash }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Guardian-only: cancel (veto) one or more pending action batches.
|
|
259
|
+
*
|
|
260
|
+
* Deletes the pending actions from storage, preventing them from ever being
|
|
261
|
+
* executed. Only the vault guardian can call this.
|
|
262
|
+
*
|
|
263
|
+
* Uses simulate-then-write to catch `NoSuchActions` and permission errors early.
|
|
264
|
+
*
|
|
265
|
+
* @param walletClient Wallet client with guardian account attached
|
|
266
|
+
* @param publicClient Public client for reads and simulation
|
|
267
|
+
* @param vault Vault address (diamond proxy)
|
|
268
|
+
* @param nonces Array of action nonces to cancel
|
|
269
|
+
* @returns Transaction hash
|
|
270
|
+
*/
|
|
271
|
+
export async function vetoActions(
|
|
272
|
+
walletClient: WalletClient,
|
|
273
|
+
publicClient: PublicClient,
|
|
274
|
+
vault: Address,
|
|
275
|
+
nonces: bigint[],
|
|
276
|
+
): Promise<{ txHash: `0x${string}` }> {
|
|
277
|
+
const account = walletClient.account!
|
|
278
|
+
const v = getAddress(vault)
|
|
279
|
+
|
|
280
|
+
// Simulate to catch NotGuardian, NoSuchActions, etc.
|
|
281
|
+
await publicClient.simulateContract({
|
|
282
|
+
address: v,
|
|
283
|
+
abi: MULTICALL_ABI,
|
|
284
|
+
functionName: 'vetoActions',
|
|
285
|
+
args: [nonces],
|
|
286
|
+
account: account.address,
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
const txHash = await walletClient.writeContract({
|
|
290
|
+
address: v,
|
|
291
|
+
abi: MULTICALL_ABI,
|
|
292
|
+
functionName: 'vetoActions',
|
|
293
|
+
args: [nonces],
|
|
294
|
+
account,
|
|
295
|
+
chain: walletClient.chain,
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
return { txHash }
|
|
299
|
+
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { type Address, type PublicClient, getAddress } from 'viem'
|
|
9
|
-
import { MULTICALL_ABI, CURATOR_CONFIG_ABI } from './abis.js'
|
|
10
|
-
import type { CuratorVaultStatus, PendingAction } from './types.js'
|
|
9
|
+
import { MULTICALL_ABI, CURATOR_CONFIG_ABI, VAULT_ANALYSIS_ABI, REGISTRY_ABI, METADATA_ABI } from './abis.js'
|
|
10
|
+
import type { CuratorVaultStatus, PendingAction, VaultAnalysis, AssetInfo } from './types.js'
|
|
11
11
|
|
|
12
12
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
13
|
|
|
@@ -122,3 +122,134 @@ export async function isCurator(
|
|
|
122
122
|
|
|
123
123
|
return getAddress(curatorAddress as Address) === getAddress(address)
|
|
124
124
|
}
|
|
125
|
+
|
|
126
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Full vault analysis — available assets with metadata, depositable assets, whitelist config.
|
|
130
|
+
* Useful for curator dashboards to understand what the vault can do.
|
|
131
|
+
*
|
|
132
|
+
* @param publicClient Viem public client (must be on the vault's chain)
|
|
133
|
+
* @param vault Vault address (diamond proxy)
|
|
134
|
+
* @returns VaultAnalysis snapshot
|
|
135
|
+
*/
|
|
136
|
+
export async function getVaultAnalysis(
|
|
137
|
+
publicClient: PublicClient,
|
|
138
|
+
vault: Address,
|
|
139
|
+
): Promise<VaultAnalysis> {
|
|
140
|
+
const v = getAddress(vault)
|
|
141
|
+
|
|
142
|
+
// Batch 1: fetch asset lists, whitelist flag, and registry address in parallel
|
|
143
|
+
const [availableRaw, depositableRaw, depositWhitelistEnabled, registryResult] =
|
|
144
|
+
await Promise.all([
|
|
145
|
+
publicClient.readContract({
|
|
146
|
+
address: v,
|
|
147
|
+
abi: VAULT_ANALYSIS_ABI,
|
|
148
|
+
functionName: 'getAvailableAssets',
|
|
149
|
+
}),
|
|
150
|
+
publicClient.readContract({
|
|
151
|
+
address: v,
|
|
152
|
+
abi: VAULT_ANALYSIS_ABI,
|
|
153
|
+
functionName: 'getDepositableAssets',
|
|
154
|
+
}),
|
|
155
|
+
publicClient.readContract({
|
|
156
|
+
address: v,
|
|
157
|
+
abi: VAULT_ANALYSIS_ABI,
|
|
158
|
+
functionName: 'isDepositWhitelistEnabled',
|
|
159
|
+
}),
|
|
160
|
+
publicClient.readContract({
|
|
161
|
+
address: v,
|
|
162
|
+
abi: VAULT_ANALYSIS_ABI,
|
|
163
|
+
functionName: 'moreVaultsRegistry',
|
|
164
|
+
}).catch(() => null),
|
|
165
|
+
])
|
|
166
|
+
|
|
167
|
+
const availableAddresses = (availableRaw as Address[]).map(getAddress)
|
|
168
|
+
const depositableAddresses = (depositableRaw as Address[]).map(getAddress)
|
|
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 metadataCalls = allAddresses.flatMap((addr) => [
|
|
175
|
+
{ address: addr, abi: METADATA_ABI, functionName: 'name' as const },
|
|
176
|
+
{ address: addr, abi: METADATA_ABI, functionName: 'symbol' as const },
|
|
177
|
+
{ address: addr, abi: METADATA_ABI, functionName: 'decimals' as const },
|
|
178
|
+
])
|
|
179
|
+
|
|
180
|
+
const metadataResults = allAddresses.length > 0
|
|
181
|
+
? await publicClient.multicall({ contracts: metadataCalls, allowFailure: true })
|
|
182
|
+
: []
|
|
183
|
+
|
|
184
|
+
// Map address → AssetInfo
|
|
185
|
+
const assetInfoMap = new Map<Address, AssetInfo>()
|
|
186
|
+
for (let i = 0; i < allAddresses.length; i++) {
|
|
187
|
+
const addr = allAddresses[i]
|
|
188
|
+
const nameResult = metadataResults[i * 3]
|
|
189
|
+
const symbolResult = metadataResults[i * 3 + 1]
|
|
190
|
+
const decimalsResult = metadataResults[i * 3 + 2]
|
|
191
|
+
|
|
192
|
+
assetInfoMap.set(addr, {
|
|
193
|
+
address: addr,
|
|
194
|
+
name: nameResult?.status === 'success' ? (nameResult.result as string) : '',
|
|
195
|
+
symbol: symbolResult?.status === 'success' ? (symbolResult.result as string) : '',
|
|
196
|
+
decimals: decimalsResult?.status === 'success' ? (decimalsResult.result as number) : 18,
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const registryAddress = registryResult ? getAddress(registryResult as Address) : null
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
availableAssets: availableAddresses.map((a) => assetInfoMap.get(a)!),
|
|
204
|
+
depositableAssets: depositableAddresses.map((a) => assetInfoMap.get(a)!),
|
|
205
|
+
depositWhitelistEnabled: depositWhitelistEnabled as boolean,
|
|
206
|
+
registryAddress,
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Check if specific protocol addresses are whitelisted in the global registry.
|
|
214
|
+
* Useful for curators to verify DEX routers before building swap calldata.
|
|
215
|
+
*
|
|
216
|
+
* @param publicClient Viem public client (must be on the vault's chain)
|
|
217
|
+
* @param vault Vault address (diamond proxy)
|
|
218
|
+
* @param protocols Protocol addresses to check
|
|
219
|
+
* @returns Record mapping address → whitelisted boolean
|
|
220
|
+
*/
|
|
221
|
+
export async function checkProtocolWhitelist(
|
|
222
|
+
publicClient: PublicClient,
|
|
223
|
+
vault: Address,
|
|
224
|
+
protocols: Address[],
|
|
225
|
+
): Promise<Record<string, boolean>> {
|
|
226
|
+
const v = getAddress(vault)
|
|
227
|
+
|
|
228
|
+
const registryRaw = await publicClient.readContract({
|
|
229
|
+
address: v,
|
|
230
|
+
abi: VAULT_ANALYSIS_ABI,
|
|
231
|
+
functionName: 'moreVaultsRegistry',
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const registry = getAddress(registryRaw as Address)
|
|
235
|
+
|
|
236
|
+
if (protocols.length === 0) return {}
|
|
237
|
+
|
|
238
|
+
const results = await publicClient.multicall({
|
|
239
|
+
contracts: protocols.map((protocol) => ({
|
|
240
|
+
address: registry,
|
|
241
|
+
abi: REGISTRY_ABI,
|
|
242
|
+
functionName: 'isWhitelisted' as const,
|
|
243
|
+
args: [getAddress(protocol)] as [Address],
|
|
244
|
+
})),
|
|
245
|
+
allowFailure: true,
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const out: Record<string, boolean> = {}
|
|
249
|
+
for (let i = 0; i < protocols.length; i++) {
|
|
250
|
+
const r = results[i]
|
|
251
|
+
out[getAddress(protocols[i])] = r?.status === 'success' ? (r.result as boolean) : false
|
|
252
|
+
}
|
|
253
|
+
return out
|
|
254
|
+
}
|
|
255
|
+
|
package/src/viem/index.ts
CHANGED
|
@@ -17,8 +17,11 @@ export {
|
|
|
17
17
|
DEX_ABI,
|
|
18
18
|
BRIDGE_FACET_ABI,
|
|
19
19
|
ERC7540_FACET_ABI,
|
|
20
|
+
ERC4626_FACET_ABI,
|
|
20
21
|
CURATOR_CONFIG_ABI,
|
|
21
22
|
LZ_ADAPTER_ABI,
|
|
23
|
+
VAULT_ANALYSIS_ABI,
|
|
24
|
+
REGISTRY_ABI,
|
|
22
25
|
} from './abis'
|
|
23
26
|
|
|
24
27
|
// --- Types ---
|
|
@@ -38,6 +41,8 @@ export type {
|
|
|
38
41
|
SubmitActionsResult,
|
|
39
42
|
CuratorAction,
|
|
40
43
|
CuratorVaultStatus,
|
|
44
|
+
AssetInfo,
|
|
45
|
+
VaultAnalysis,
|
|
41
46
|
} from './types'
|
|
42
47
|
export { ActionType } from './types'
|
|
43
48
|
|
|
@@ -155,7 +160,16 @@ export {
|
|
|
155
160
|
getCuratorVaultStatus,
|
|
156
161
|
getPendingActions,
|
|
157
162
|
isCurator,
|
|
163
|
+
getVaultAnalysis,
|
|
164
|
+
checkProtocolWhitelist,
|
|
158
165
|
} from './curatorStatus'
|
|
166
|
+
export {
|
|
167
|
+
encodeCuratorAction,
|
|
168
|
+
buildCuratorBatch,
|
|
169
|
+
submitActions,
|
|
170
|
+
executeActions,
|
|
171
|
+
vetoActions,
|
|
172
|
+
} from './curatorMulticall'
|
|
159
173
|
|
|
160
174
|
// --- wagmi compatibility ---
|
|
161
175
|
// Re-export viem's PublicClient type for wagmi compatibility.
|
package/src/viem/types.ts
CHANGED
|
@@ -155,3 +155,25 @@ export interface CuratorVaultStatus {
|
|
|
155
155
|
lzAdapter: Address
|
|
156
156
|
paused: boolean
|
|
157
157
|
}
|
|
158
|
+
|
|
159
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
160
|
+
// Vault Analysis Types
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
export interface AssetInfo {
|
|
164
|
+
address: Address
|
|
165
|
+
symbol: string
|
|
166
|
+
name: string
|
|
167
|
+
decimals: number
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface VaultAnalysis {
|
|
171
|
+
/** All tokens the vault can hold/swap (curator-managed) */
|
|
172
|
+
availableAssets: AssetInfo[]
|
|
173
|
+
/** Tokens users can deposit */
|
|
174
|
+
depositableAssets: AssetInfo[]
|
|
175
|
+
/** Whether deposit whitelist is enabled (restricts who can deposit) */
|
|
176
|
+
depositWhitelistEnabled: boolean
|
|
177
|
+
/** Registry address for global protocol whitelist checks */
|
|
178
|
+
registryAddress: Address | null
|
|
179
|
+
}
|
package/src/viem/userHelpers.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Address, type PublicClient, getAddress } from 'viem'
|
|
2
|
-
import { BRIDGE_ABI, CONFIG_ABI, ERC20_ABI, VAULT_ABI, METADATA_ABI, OFT_ABI } from './abis'
|
|
2
|
+
import { BRIDGE_ABI, CONFIG_ABI, ERC20_ABI, VAULT_ABI, METADATA_ABI, OFT_ABI, VAULT_ANALYSIS_ABI } from './abis'
|
|
3
3
|
import type { CrossChainRequestInfo } from './types'
|
|
4
4
|
import { getVaultStatus } from './utils'
|
|
5
5
|
import type { VaultStatus } from './utils'
|
|
@@ -140,6 +140,10 @@ export type DepositBlockReason = 'paused' | 'capacity-full' | 'not-whitelisted'
|
|
|
140
140
|
export interface DepositEligibility {
|
|
141
141
|
allowed: boolean
|
|
142
142
|
reason: DepositBlockReason
|
|
143
|
+
/** Max deposit amount for this user (0n if capacity full or not whitelisted) */
|
|
144
|
+
maxDeposit?: bigint
|
|
145
|
+
/** Whether the vault restricts deposits to whitelisted addresses */
|
|
146
|
+
whitelistEnabled?: boolean
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
/**
|
|
@@ -167,15 +171,27 @@ export async function canDeposit(
|
|
|
167
171
|
allowFailure: false,
|
|
168
172
|
})
|
|
169
173
|
|
|
174
|
+
// Read whitelist status (may revert on older vaults — default to false)
|
|
175
|
+
let whitelistEnabled = false
|
|
176
|
+
try {
|
|
177
|
+
whitelistEnabled = await publicClient.readContract({
|
|
178
|
+
address: v,
|
|
179
|
+
abi: VAULT_ANALYSIS_ABI,
|
|
180
|
+
functionName: 'isDepositWhitelistEnabled',
|
|
181
|
+
}) as boolean
|
|
182
|
+
} catch {
|
|
183
|
+
// Older vaults may not have this function
|
|
184
|
+
}
|
|
185
|
+
|
|
170
186
|
if (isPaused) {
|
|
171
|
-
return { allowed: false, reason: 'paused' }
|
|
187
|
+
return { allowed: false, reason: 'paused', whitelistEnabled }
|
|
172
188
|
}
|
|
173
189
|
|
|
174
190
|
// Cross-chain async hubs revert on maxDeposit — this is expected, not a whitelist block.
|
|
175
191
|
// The vault accepts deposits via initVaultActionRequest instead of the standard ERC-4626 path.
|
|
176
192
|
const isCrossChainAsync = isHub && !oraclesEnabled
|
|
177
193
|
if (isCrossChainAsync) {
|
|
178
|
-
return { allowed: true, reason: 'ok' }
|
|
194
|
+
return { allowed: true, reason: 'ok', whitelistEnabled }
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
// maxDeposit(user) can REVERT on vaults with whitelist/ACL
|
|
@@ -189,13 +205,13 @@ export async function canDeposit(
|
|
|
189
205
|
})
|
|
190
206
|
} catch {
|
|
191
207
|
// Revert means the vault has whitelist/ACL and this user is not approved
|
|
192
|
-
return { allowed: false, reason: 'not-whitelisted' }
|
|
208
|
+
return { allowed: false, reason: 'not-whitelisted', maxDeposit: 0n, whitelistEnabled }
|
|
193
209
|
}
|
|
194
210
|
|
|
195
211
|
if (maxDepositAmount === 0n) {
|
|
196
|
-
return { allowed: false, reason: 'capacity-full' }
|
|
212
|
+
return { allowed: false, reason: 'capacity-full', maxDeposit: 0n, whitelistEnabled }
|
|
197
213
|
}
|
|
198
|
-
return { allowed: true, reason: 'ok' }
|
|
214
|
+
return { allowed: true, reason: 'ok', maxDeposit: maxDepositAmount, whitelistEnabled }
|
|
199
215
|
}
|
|
200
216
|
|
|
201
217
|
// ─────────────────────────────────────────────────────────────────────────────
|