@oydual31/more-vaults-sdk 0.3.0 → 0.3.2

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,239 @@
1
+ /**
2
+ * Curator swap helpers for Uniswap V3-compatible DEXes.
3
+ *
4
+ * Provides typed helpers to build CuratorAction objects and raw calldata for
5
+ * Uniswap V3 exactInputSingle swaps, automatically resolving the correct router
6
+ * and ABI variant (SwapRouter vs SwapRouter02) per chain.
7
+ *
8
+ * Supported chains and routers:
9
+ * - Base (8453): SwapRouter02 0x2626... — NO deadline field
10
+ * - Ethereum (1): SwapRouter 0xE592... — HAS deadline field
11
+ * - Arbitrum (42161): SwapRouter 0xE592... — HAS deadline field
12
+ * - Optimism (10): SwapRouter 0xE592... — HAS deadline field
13
+ * - Flow EVM (747): FlowSwap V3 0xeEDC... — HAS deadline field
14
+ *
15
+ * @module curatorSwaps
16
+ */
17
+
18
+ import { type Address, encodeFunctionData } from 'viem'
19
+ import { UNISWAP_V3_ROUTERS } from './chains.js'
20
+ import type { CuratorAction } from './types.js'
21
+
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // ABI constants
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+
26
+ /**
27
+ * Uniswap V3 SwapRouter exactInputSingle ABI.
28
+ * Used for: Ethereum (1), Arbitrum (42161), Optimism (10), Flow EVM (747).
29
+ * Struct includes `deadline` field.
30
+ */
31
+ const UNISWAP_V3_SWAP_ROUTER_ABI = [
32
+ {
33
+ type: 'function',
34
+ name: 'exactInputSingle',
35
+ inputs: [
36
+ {
37
+ type: 'tuple',
38
+ name: 'params',
39
+ components: [
40
+ { name: 'tokenIn', type: 'address' },
41
+ { name: 'tokenOut', type: 'address' },
42
+ { name: 'fee', type: 'uint24' },
43
+ { name: 'recipient', type: 'address' },
44
+ { name: 'deadline', type: 'uint256' },
45
+ { name: 'amountIn', type: 'uint256' },
46
+ { name: 'amountOutMinimum', type: 'uint256' },
47
+ { name: 'sqrtPriceLimitX96', type: 'uint160' },
48
+ ],
49
+ },
50
+ ],
51
+ outputs: [{ name: 'amountOut', type: 'uint256' }],
52
+ stateMutability: 'payable',
53
+ },
54
+ ] as const
55
+
56
+ /**
57
+ * Uniswap V3 SwapRouter02 exactInputSingle ABI.
58
+ * Used for: Base (8453).
59
+ * Struct does NOT include `deadline` field — SwapRouter02 removed it.
60
+ */
61
+ const UNISWAP_V3_SWAP_ROUTER02_ABI = [
62
+ {
63
+ type: 'function',
64
+ name: 'exactInputSingle',
65
+ inputs: [
66
+ {
67
+ type: 'tuple',
68
+ name: 'params',
69
+ components: [
70
+ { name: 'tokenIn', type: 'address' },
71
+ { name: 'tokenOut', type: 'address' },
72
+ { name: 'fee', type: 'uint24' },
73
+ { name: 'recipient', type: 'address' },
74
+ { name: 'amountIn', type: 'uint256' },
75
+ { name: 'amountOutMinimum', type: 'uint256' },
76
+ { name: 'sqrtPriceLimitX96', type: 'uint160' },
77
+ ],
78
+ },
79
+ ],
80
+ outputs: [{ name: 'amountOut', type: 'uint256' }],
81
+ stateMutability: 'payable',
82
+ },
83
+ ] as const
84
+
85
+ // ─────────────────────────────────────────────────────────────────────────────
86
+ // Chain variant detection
87
+ // ─────────────────────────────────────────────────────────────────────────────
88
+
89
+ /**
90
+ * Chains that use SwapRouter02 (no deadline in struct).
91
+ * All other chains in UNISWAP_V3_ROUTERS use the original SwapRouter.
92
+ */
93
+ const SWAP_ROUTER02_CHAINS = new Set([8453])
94
+
95
+ // ─────────────────────────────────────────────────────────────────────────────
96
+ // Calldata encoding
97
+ // ─────────────────────────────────────────────────────────────────────────────
98
+
99
+ /**
100
+ * Encode Uniswap V3 exactInputSingle calldata directly.
101
+ * For curators who want raw calldata without the CuratorAction wrapper.
102
+ *
103
+ * Automatically selects the correct ABI variant (SwapRouter vs SwapRouter02)
104
+ * based on the chainId. The deadline (for SwapRouter chains) is set to
105
+ * `now + 20 minutes` to prevent stale transactions from executing.
106
+ *
107
+ * @param params.chainId EVM chain ID — must be present in UNISWAP_V3_ROUTERS
108
+ * @param params.tokenIn Input token address
109
+ * @param params.tokenOut Output token address
110
+ * @param params.fee Pool fee tier: 100, 500, 3000, or 10000
111
+ * @param params.amountIn Exact input amount (in tokenIn units)
112
+ * @param params.minAmountOut Minimum acceptable output (slippage protection)
113
+ * @param params.recipient Address to receive the output tokens (usually the vault)
114
+ * @returns The router contract address and ABI-encoded calldata
115
+ * @throws If no router is configured for the given chainId
116
+ */
117
+ export function encodeUniswapV3SwapCalldata(params: {
118
+ chainId: number
119
+ tokenIn: Address
120
+ tokenOut: Address
121
+ fee: number
122
+ amountIn: bigint
123
+ minAmountOut: bigint
124
+ recipient: Address
125
+ }): { targetContract: Address; swapCallData: `0x${string}` } {
126
+ const { chainId, tokenIn, tokenOut, fee, amountIn, minAmountOut, recipient } = params
127
+
128
+ const router = UNISWAP_V3_ROUTERS[chainId]
129
+ if (!router) {
130
+ throw new Error(
131
+ `[MoreVaults] No Uniswap V3 router configured for chainId ${chainId}. ` +
132
+ `Supported chains: ${Object.keys(UNISWAP_V3_ROUTERS).join(', ')}`
133
+ )
134
+ }
135
+
136
+ let swapCallData: `0x${string}`
137
+
138
+ if (SWAP_ROUTER02_CHAINS.has(chainId)) {
139
+ // SwapRouter02 (Base) — no deadline field
140
+ swapCallData = encodeFunctionData({
141
+ abi: UNISWAP_V3_SWAP_ROUTER02_ABI,
142
+ functionName: 'exactInputSingle',
143
+ args: [
144
+ {
145
+ tokenIn,
146
+ tokenOut,
147
+ fee,
148
+ recipient,
149
+ amountIn,
150
+ amountOutMinimum: minAmountOut,
151
+ sqrtPriceLimitX96: 0n,
152
+ },
153
+ ],
154
+ })
155
+ } else {
156
+ // Original SwapRouter (Eth/Arb/Op/Flow EVM) — has deadline field
157
+ const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200) // now + 20 minutes
158
+ swapCallData = encodeFunctionData({
159
+ abi: UNISWAP_V3_SWAP_ROUTER_ABI,
160
+ functionName: 'exactInputSingle',
161
+ args: [
162
+ {
163
+ tokenIn,
164
+ tokenOut,
165
+ fee,
166
+ recipient,
167
+ deadline,
168
+ amountIn,
169
+ amountOutMinimum: minAmountOut,
170
+ sqrtPriceLimitX96: 0n,
171
+ },
172
+ ],
173
+ })
174
+ }
175
+
176
+ return { targetContract: router, swapCallData }
177
+ }
178
+
179
+ // ─────────────────────────────────────────────────────────────────────────────
180
+ // CuratorAction builder
181
+ // ─────────────────────────────────────────────────────────────────────────────
182
+
183
+ /**
184
+ * Build a CuratorAction for a Uniswap V3 exactInputSingle swap.
185
+ *
186
+ * Automatically resolves the router address from UNISWAP_V3_ROUTERS and
187
+ * selects the correct ABI struct (with or without deadline) based on chainId.
188
+ *
189
+ * The returned action is a `swap` variant ready to be passed to
190
+ * `buildCuratorBatch` and then `submitActions`.
191
+ *
192
+ * @param params.chainId EVM chain ID — must be present in UNISWAP_V3_ROUTERS
193
+ * @param params.tokenIn Input token address
194
+ * @param params.tokenOut Output token address
195
+ * @param params.fee Pool fee tier: 100, 500, 3000, or 10000
196
+ * @param params.amountIn Exact input amount (in tokenIn units)
197
+ * @param params.minAmountOut Minimum acceptable output (slippage protection)
198
+ * @param params.recipient Address to receive output tokens (usually the vault)
199
+ * @returns A typed CuratorAction ready for buildCuratorBatch
200
+ * @throws If no router is configured for the given chainId
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * const action = buildUniswapV3Swap({
205
+ * chainId: 8453,
206
+ * tokenIn: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
207
+ * tokenOut: '0x4200000000000000000000000000000000000006', // WETH
208
+ * fee: 500, // 0.05% pool
209
+ * amountIn: 150_000n, // 0.15 USDC (6 decimals)
210
+ * minAmountOut: 1n, // accept any amount (set properly in production)
211
+ * recipient: VAULT,
212
+ * })
213
+ * const batch = buildCuratorBatch([action])
214
+ * await submitActions(walletClient, publicClient, vault, batch)
215
+ * ```
216
+ */
217
+ export function buildUniswapV3Swap(params: {
218
+ chainId: number
219
+ tokenIn: Address
220
+ tokenOut: Address
221
+ fee: number
222
+ amountIn: bigint
223
+ minAmountOut: bigint
224
+ recipient: Address
225
+ }): CuratorAction {
226
+ const { targetContract, swapCallData } = encodeUniswapV3SwapCalldata(params)
227
+
228
+ return {
229
+ type: 'swap',
230
+ params: {
231
+ targetContract,
232
+ tokenIn: params.tokenIn,
233
+ tokenOut: params.tokenOut,
234
+ maxAmountIn: params.amountIn,
235
+ minAmountOut: params.minAmountOut,
236
+ swapCallData,
237
+ },
238
+ }
239
+ }
package/src/viem/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Provides typed helpers for all deposit, redeem, and cross-chain vault flows.
3
3
 
4
4
  // --- Chain constants ---
5
- export { CHAIN_IDS, LZ_EIDS, EID_TO_CHAIN_ID, CHAIN_ID_TO_EID, OFT_ROUTES, STARGATE_TAXI_CMD, USDC_STARGATE_OFT, USDC_TOKEN, LZ_TIMEOUTS } from './chains'
5
+ export { CHAIN_IDS, LZ_EIDS, EID_TO_CHAIN_ID, CHAIN_ID_TO_EID, OFT_ROUTES, STARGATE_TAXI_CMD, USDC_STARGATE_OFT, USDC_TOKEN, LZ_TIMEOUTS, UNISWAP_V3_ROUTERS } from './chains'
6
6
 
7
7
  // --- ABIs ---
8
8
  export {
@@ -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,10 @@ export type {
38
41
  SubmitActionsResult,
39
42
  CuratorAction,
40
43
  CuratorVaultStatus,
44
+ AssetInfo,
45
+ AssetBalance,
46
+ VaultAnalysis,
47
+ VaultAssetBreakdown,
41
48
  } from './types'
42
49
  export { ActionType } from './types'
43
50
 
@@ -155,7 +162,21 @@ export {
155
162
  getCuratorVaultStatus,
156
163
  getPendingActions,
157
164
  isCurator,
165
+ getVaultAnalysis,
166
+ checkProtocolWhitelist,
167
+ getVaultAssetBreakdown,
158
168
  } from './curatorStatus'
169
+ export {
170
+ encodeCuratorAction,
171
+ buildCuratorBatch,
172
+ submitActions,
173
+ executeActions,
174
+ vetoActions,
175
+ } from './curatorMulticall'
176
+ export {
177
+ buildUniswapV3Swap,
178
+ encodeUniswapV3SwapCalldata,
179
+ } from './curatorSwaps'
159
180
 
160
181
  // --- wagmi compatibility ---
161
182
  // Re-export viem's PublicClient type for wagmi compatibility.
package/src/viem/types.ts CHANGED
@@ -155,3 +155,41 @@ 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
+ }
180
+
181
+ export interface AssetBalance extends AssetInfo {
182
+ /** Raw balance held by the vault */
183
+ balance: bigint
184
+ }
185
+
186
+ export interface VaultAssetBreakdown {
187
+ /** Per-asset balances held by the vault on the hub chain */
188
+ assets: AssetBalance[]
189
+ /** totalAssets() as reported by the vault (all positions converted to underlying) */
190
+ totalAssets: bigint
191
+ /** totalSupply() of vault shares */
192
+ totalSupply: bigint
193
+ /** Vault underlying token decimals */
194
+ underlyingDecimals: number
195
+ }
@@ -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
  // ─────────────────────────────────────────────────────────────────────────────