@oydual31/more-vaults-sdk 0.3.1 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oydual31/more-vaults-sdk",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "TypeScript SDK for MoreVaults protocol — viem/wagmi and ethers.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -261,6 +261,23 @@ export const LZ_TIMEOUTS = {
261
261
  FULL_SPOKE_REDEEM: 3_600_000, // 60 min
262
262
  } as const
263
263
 
264
+ /**
265
+ * Uniswap V3 SwapRouter addresses per chain.
266
+ * Used by curator swap helpers to build calldata for on-chain swaps.
267
+ *
268
+ * Note on struct differences:
269
+ * - SwapRouter (Eth/Arb/Op, 0xE592...): exactInputSingle struct includes `deadline`
270
+ * - SwapRouter02 (Base, 0x2626...): exactInputSingle struct does NOT include `deadline`
271
+ * - FlowSwap V3 (Flow EVM, 0xeEDC...): derived from original UniV3, includes `deadline`
272
+ */
273
+ export const UNISWAP_V3_ROUTERS: Record<number, `0x${string}`> = {
274
+ [8453]: '0x2626664c2603336E57B271c5C0b26F421741e481', // Base — SwapRouter02 (no deadline)
275
+ [1]: '0xE592427A0AEce92De3Edee1F18E0157C05861564', // Ethereum — SwapRouter
276
+ [42161]: '0xE592427A0AEce92De3Edee1F18E0157C05861564', // Arbitrum — SwapRouter
277
+ [10]: '0xE592427A0AEce92De3Edee1F18E0157C05861564', // Optimism — SwapRouter
278
+ [747]: '0xeEDC6Ff75e1b10B903D9013c358e446a73d35341', // Flow EVM — FlowSwap V3 SwapRouter
279
+ }
280
+
264
281
  // ---------------------------------------------------------------------------
265
282
  // Legacy flat exports — kept for backwards compat, prefer OFT_ROUTES
266
283
  // ---------------------------------------------------------------------------
@@ -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, VAULT_ANALYSIS_ABI, REGISTRY_ABI, METADATA_ABI } from './abis.js'
10
- import type { CuratorVaultStatus, PendingAction, VaultAnalysis, AssetInfo } from './types.js'
9
+ import { MULTICALL_ABI, CURATOR_CONFIG_ABI, VAULT_ANALYSIS_ABI, REGISTRY_ABI, METADATA_ABI, ERC20_ABI, VAULT_ABI } from './abis.js'
10
+ import type { CuratorVaultStatus, PendingAction, VaultAnalysis, AssetInfo, AssetBalance, VaultAssetBreakdown } from './types.js'
11
11
 
12
12
  // ─────────────────────────────────────────────────────────────────────────────
13
13
 
@@ -253,3 +253,67 @@ export async function checkProtocolWhitelist(
253
253
  return out
254
254
  }
255
255
 
256
+ // ─────────────────────────────────────────────────────────────────────────────
257
+
258
+ /**
259
+ * Get the vault's per-asset balance breakdown on the hub chain.
260
+ *
261
+ * Returns the balance of every available asset held by the vault, plus
262
+ * totalAssets and totalSupply for context. Useful for portfolio views
263
+ * that need to show individual holdings rather than a single USD-denominated total.
264
+ *
265
+ * @param publicClient Viem public client (must be on the vault's hub chain)
266
+ * @param vault Vault address (diamond proxy)
267
+ * @returns VaultAssetBreakdown with per-asset balances
268
+ */
269
+ export async function getVaultAssetBreakdown(
270
+ publicClient: PublicClient,
271
+ vault: Address,
272
+ ): Promise<VaultAssetBreakdown> {
273
+ const v = getAddress(vault)
274
+
275
+ // Step 1: get available assets list
276
+ const availableRaw = await publicClient.readContract({
277
+ address: v,
278
+ abi: VAULT_ANALYSIS_ABI,
279
+ functionName: 'getAvailableAssets',
280
+ }) as Address[]
281
+
282
+ const addresses = availableRaw.map(getAddress)
283
+
284
+ // Step 2: multicall — balanceOf + metadata for each asset + totalAssets + totalSupply + vault decimals
285
+ const results = await publicClient.multicall({
286
+ contracts: [
287
+ // Per-asset: balanceOf, name, symbol, decimals
288
+ ...addresses.flatMap((addr) => [
289
+ { address: addr, abi: ERC20_ABI, functionName: 'balanceOf' as const, args: [v] as [Address] },
290
+ { address: addr, abi: METADATA_ABI, functionName: 'name' as const },
291
+ { address: addr, abi: METADATA_ABI, functionName: 'symbol' as const },
292
+ { address: addr, abi: METADATA_ABI, functionName: 'decimals' as const },
293
+ ]),
294
+ // Vault totals
295
+ { address: v, abi: VAULT_ABI, functionName: 'totalAssets' as const },
296
+ { address: v, abi: VAULT_ABI, functionName: 'totalSupply' as const },
297
+ { address: v, abi: METADATA_ABI, functionName: 'decimals' as const },
298
+ ],
299
+ allowFailure: true,
300
+ })
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]?.status === 'success' ? (results[base].result as bigint) : 0n
306
+ const name = results[base + 1]?.status === 'success' ? (results[base + 1].result as string) : ''
307
+ const symbol = results[base + 2]?.status === 'success' ? (results[base + 2].result as string) : ''
308
+ const decimals = results[base + 3]?.status === 'success' ? (results[base + 3].result as number) : 18
309
+
310
+ return { address: addr, name, symbol, decimals, balance }
311
+ })
312
+
313
+ const totalsBase = addresses.length * perAssetFields
314
+ const totalAssets = results[totalsBase]?.status === 'success' ? (results[totalsBase].result as bigint) : 0n
315
+ const totalSupply = results[totalsBase + 1]?.status === 'success' ? (results[totalsBase + 1].result as bigint) : 0n
316
+ const underlyingDecimals = results[totalsBase + 2]?.status === 'success' ? (results[totalsBase + 2].result as number) : 6
317
+
318
+ return { assets, totalAssets, totalSupply, underlyingDecimals }
319
+ }
@@ -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 {
@@ -42,7 +42,9 @@ export type {
42
42
  CuratorAction,
43
43
  CuratorVaultStatus,
44
44
  AssetInfo,
45
+ AssetBalance,
45
46
  VaultAnalysis,
47
+ VaultAssetBreakdown,
46
48
  } from './types'
47
49
  export { ActionType } from './types'
48
50
 
@@ -162,6 +164,7 @@ export {
162
164
  isCurator,
163
165
  getVaultAnalysis,
164
166
  checkProtocolWhitelist,
167
+ getVaultAssetBreakdown,
165
168
  } from './curatorStatus'
166
169
  export {
167
170
  encodeCuratorAction,
@@ -170,6 +173,10 @@ export {
170
173
  executeActions,
171
174
  vetoActions,
172
175
  } from './curatorMulticall'
176
+ export {
177
+ buildUniswapV3Swap,
178
+ encodeUniswapV3SwapCalldata,
179
+ } from './curatorSwaps'
173
180
 
174
181
  // --- wagmi compatibility ---
175
182
  // Re-export viem's PublicClient type for wagmi compatibility.
package/src/viem/types.ts CHANGED
@@ -177,3 +177,19 @@ export interface VaultAnalysis {
177
177
  /** Registry address for global protocol whitelist checks */
178
178
  registryAddress: Address | null
179
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
+ }