@oydual31/more-vaults-sdk 0.2.9 → 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.
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Curator / vault-manager read helpers for the MoreVaults SDK.
3
+ *
4
+ * All functions are read-only (no wallet needed) and use multicall for
5
+ * batched RPC efficiency.
6
+ */
7
+
8
+ import { type Address, type PublicClient, getAddress } from 'viem'
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
+
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+
14
+ /**
15
+ * Read a comprehensive status snapshot for the curator dashboard.
16
+ *
17
+ * Fetches in two batches (multicall) to minimise round trips:
18
+ * Batch 1: curator, timeLockPeriod, getMaxSlippagePercent, getCurrentNonce,
19
+ * getAvailableAssets, getCrossChainAccountingManager, paused
20
+ *
21
+ * @param publicClient Viem public client (must be on the vault's chain)
22
+ * @param vault Vault address (diamond proxy)
23
+ * @returns CuratorVaultStatus snapshot
24
+ */
25
+ export async function getCuratorVaultStatus(
26
+ publicClient: PublicClient,
27
+ vault: Address,
28
+ ): Promise<CuratorVaultStatus> {
29
+ const v = getAddress(vault)
30
+
31
+ const [
32
+ curator,
33
+ timeLockPeriod,
34
+ maxSlippagePercent,
35
+ currentNonce,
36
+ availableAssets,
37
+ lzAdapter,
38
+ paused,
39
+ ] = await publicClient.multicall({
40
+ contracts: [
41
+ { address: v, abi: CURATOR_CONFIG_ABI, functionName: 'curator' },
42
+ { address: v, abi: CURATOR_CONFIG_ABI, functionName: 'timeLockPeriod' },
43
+ { address: v, abi: CURATOR_CONFIG_ABI, functionName: 'getMaxSlippagePercent' },
44
+ { address: v, abi: MULTICALL_ABI, functionName: 'getCurrentNonce' },
45
+ { address: v, abi: CURATOR_CONFIG_ABI, functionName: 'getAvailableAssets' },
46
+ { address: v, abi: CURATOR_CONFIG_ABI, functionName: 'getCrossChainAccountingManager' },
47
+ { address: v, abi: CURATOR_CONFIG_ABI, functionName: 'paused' },
48
+ ],
49
+ allowFailure: false,
50
+ })
51
+
52
+ return {
53
+ curator: getAddress(curator as Address),
54
+ timeLockPeriod: timeLockPeriod as bigint,
55
+ maxSlippagePercent: maxSlippagePercent as bigint,
56
+ currentNonce: currentNonce as bigint,
57
+ availableAssets: (availableAssets as Address[]).map(getAddress),
58
+ lzAdapter: getAddress(lzAdapter as Address),
59
+ paused: paused as boolean,
60
+ }
61
+ }
62
+
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+
65
+ /**
66
+ * Fetch pending actions for a specific nonce and resolve whether they are
67
+ * executable (i.e. the timelock has expired).
68
+ *
69
+ * @param publicClient Viem public client (must be on the vault's chain)
70
+ * @param vault Vault address (diamond proxy)
71
+ * @param nonce Action nonce to query
72
+ * @returns PendingAction with isExecutable flag set
73
+ */
74
+ export async function getPendingActions(
75
+ publicClient: PublicClient,
76
+ vault: Address,
77
+ nonce: bigint,
78
+ ): Promise<PendingAction> {
79
+ const v = getAddress(vault)
80
+
81
+ const [actionsResult, block] = await Promise.all([
82
+ publicClient.readContract({
83
+ address: v,
84
+ abi: MULTICALL_ABI,
85
+ functionName: 'getPendingActions',
86
+ args: [nonce],
87
+ }),
88
+ publicClient.getBlock(),
89
+ ])
90
+
91
+ const [actionsData, pendingUntil] = actionsResult as [`0x${string}`[], bigint]
92
+ const isExecutable = pendingUntil > 0n && block.timestamp >= pendingUntil
93
+
94
+ return {
95
+ nonce,
96
+ actionsData,
97
+ pendingUntil,
98
+ isExecutable,
99
+ }
100
+ }
101
+
102
+ // ─────────────────────────────────────────────────────────────────────────────
103
+
104
+ /**
105
+ * Check whether a given address is the curator of the vault.
106
+ *
107
+ * @param publicClient Viem public client (must be on the vault's chain)
108
+ * @param vault Vault address (diamond proxy)
109
+ * @param address Address to check
110
+ * @returns true if address is the current curator
111
+ */
112
+ export async function isCurator(
113
+ publicClient: PublicClient,
114
+ vault: Address,
115
+ address: Address,
116
+ ): Promise<boolean> {
117
+ const curatorAddress = await publicClient.readContract({
118
+ address: getAddress(vault),
119
+ abi: CURATOR_CONFIG_ABI,
120
+ functionName: 'curator',
121
+ })
122
+
123
+ return getAddress(curatorAddress as Address) === getAddress(address)
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
@@ -13,6 +13,15 @@ export {
13
13
  OFT_ABI,
14
14
  METADATA_ABI,
15
15
  LZ_ENDPOINT_ABI,
16
+ MULTICALL_ABI,
17
+ DEX_ABI,
18
+ BRIDGE_FACET_ABI,
19
+ ERC7540_FACET_ABI,
20
+ ERC4626_FACET_ABI,
21
+ CURATOR_CONFIG_ABI,
22
+ LZ_ADAPTER_ABI,
23
+ VAULT_ANALYSIS_ABI,
24
+ REGISTRY_ABI,
16
25
  } from './abis'
17
26
 
18
27
  // --- Types ---
@@ -25,6 +34,15 @@ export type {
25
34
  ActionTypeValue,
26
35
  ComposeData,
27
36
  SpokeDepositResult,
37
+ SwapParams,
38
+ BatchSwapParams,
39
+ BridgeParams,
40
+ PendingAction,
41
+ SubmitActionsResult,
42
+ CuratorAction,
43
+ CuratorVaultStatus,
44
+ AssetInfo,
45
+ VaultAnalysis,
28
46
  } from './types'
29
47
  export { ActionType } from './types'
30
48
 
@@ -137,6 +155,22 @@ export type { VaultDistribution, SpokeBalance } from './distribution'
137
155
  export { getInboundRoutes, getUserBalancesForRoutes, getOutboundRoutes, quoteRouteDepositFee, NATIVE_SYMBOL } from './spokeRoutes'
138
156
  export type { InboundRoute, InboundRouteWithBalance, OutboundRoute } from './spokeRoutes'
139
157
 
158
+ // --- Curator Operations ---
159
+ export {
160
+ getCuratorVaultStatus,
161
+ getPendingActions,
162
+ isCurator,
163
+ getVaultAnalysis,
164
+ checkProtocolWhitelist,
165
+ } from './curatorStatus'
166
+ export {
167
+ encodeCuratorAction,
168
+ buildCuratorBatch,
169
+ submitActions,
170
+ executeActions,
171
+ vetoActions,
172
+ } from './curatorMulticall'
173
+
140
174
  // --- wagmi compatibility ---
141
175
  // Re-export viem's PublicClient type for wagmi compatibility.
142
176
  // wagmi's usePublicClient() returns a type that is structurally compatible
package/src/viem/types.ts CHANGED
@@ -98,3 +98,82 @@ export interface CrossChainRequestInfo {
98
98
  finalizationResult: bigint
99
99
  amountLimit: bigint
100
100
  }
101
+
102
+ // ─────────────────────────────────────────────────────────────────────────────
103
+ // Curator Operations Types
104
+ // ─────────────────────────────────────────────────────────────────────────────
105
+
106
+ export interface SwapParams {
107
+ targetContract: Address
108
+ tokenIn: Address
109
+ tokenOut: Address
110
+ maxAmountIn: bigint
111
+ minAmountOut: bigint
112
+ swapCallData: `0x${string}`
113
+ }
114
+
115
+ export interface BatchSwapParams {
116
+ swaps: SwapParams[]
117
+ }
118
+
119
+ export interface BridgeParams {
120
+ oftToken: Address
121
+ dstEid: number
122
+ amount: bigint
123
+ dstVault: Address
124
+ refundAddress: Address
125
+ }
126
+
127
+ export interface PendingAction {
128
+ nonce: bigint
129
+ actionsData: `0x${string}`[]
130
+ pendingUntil: bigint
131
+ isExecutable: boolean
132
+ }
133
+
134
+ export interface SubmitActionsResult {
135
+ txHash: `0x${string}`
136
+ nonce: bigint
137
+ }
138
+
139
+ export type CuratorAction =
140
+ | { type: 'swap'; params: SwapParams }
141
+ | { type: 'batchSwap'; params: BatchSwapParams }
142
+ | { type: 'erc4626Deposit'; vault: Address; assets: bigint }
143
+ | { type: 'erc4626Redeem'; vault: Address; shares: bigint }
144
+ | { type: 'erc7540RequestDeposit'; vault: Address; assets: bigint }
145
+ | { type: 'erc7540Deposit'; vault: Address; assets: bigint }
146
+ | { type: 'erc7540RequestRedeem'; vault: Address; shares: bigint }
147
+ | { type: 'erc7540Redeem'; vault: Address; shares: bigint }
148
+
149
+ export interface CuratorVaultStatus {
150
+ curator: Address
151
+ timeLockPeriod: bigint
152
+ maxSlippagePercent: bigint
153
+ currentNonce: bigint
154
+ availableAssets: Address[]
155
+ lzAdapter: Address
156
+ paused: boolean
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
+ }
@@ -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
  // ─────────────────────────────────────────────────────────────────────────────