@oydual31/more-vaults-sdk 0.1.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.
@@ -0,0 +1,331 @@
1
+ import {
2
+ type Address,
3
+ type PublicClient,
4
+ type WalletClient,
5
+ encodeAbiParameters,
6
+ getAddress,
7
+ } from 'viem'
8
+ import { VAULT_ABI, BRIDGE_ABI, ERC20_ABI } from './abis'
9
+ import type {
10
+ VaultAddresses,
11
+ DepositResult,
12
+ AsyncRequestResult,
13
+ } from './types'
14
+ import { ActionType } from './types'
15
+ import { ensureAllowance, getVaultStatus, quoteLzFee } from './utils'
16
+ import { preflightSync, preflightAsync } from './preflight'
17
+ import { MissingEscrowAddressError, VaultPausedError, CapacityFullError } from './errors'
18
+
19
+ /**
20
+ * D1 / D3 — Simple deposit (ERC-4626 standard).
21
+ *
22
+ * Works for both local vaults and cross-chain hubs with oracle accounting ON.
23
+ * When oracle accounting is enabled the cross-chain accounting is transparent
24
+ * to the caller — the vault resolves totalAssets synchronously.
25
+ *
26
+ * **User transactions**: 1 approve (skipped if allowance sufficient) + 1 deposit.
27
+ *
28
+ * @param walletClient Wallet client with account attached
29
+ * @param publicClient Public client for reads and simulation
30
+ * @param addresses Vault address set (only `vault` is used)
31
+ * @param assets Amount of underlying token to deposit (in token decimals)
32
+ * @param receiver Address that will receive the minted vault shares
33
+ * @returns Transaction hash and amount of shares minted
34
+ */
35
+ export async function depositSimple(
36
+ walletClient: WalletClient,
37
+ publicClient: PublicClient,
38
+ addresses: VaultAddresses,
39
+ assets: bigint,
40
+ receiver: Address,
41
+ ): Promise<DepositResult> {
42
+ const account = walletClient.account!
43
+ const vault = getAddress(addresses.vault)
44
+
45
+ // Pre-flight: validate vault is operational and accepting deposits
46
+ await preflightSync(publicClient, vault)
47
+
48
+ // Resolve underlying asset
49
+ const underlying = await publicClient.readContract({
50
+ address: vault,
51
+ abi: VAULT_ABI,
52
+ functionName: 'asset',
53
+ })
54
+
55
+ // Approve vault if needed
56
+ await ensureAllowance(walletClient, publicClient, underlying, vault, assets)
57
+
58
+ // Simulate then send
59
+ const { result: shares } = await publicClient.simulateContract({
60
+ address: vault,
61
+ abi: VAULT_ABI,
62
+ functionName: 'deposit',
63
+ args: [assets, getAddress(receiver)],
64
+ account: account.address,
65
+ })
66
+
67
+ const txHash = await walletClient.writeContract({
68
+ address: vault,
69
+ abi: VAULT_ABI,
70
+ functionName: 'deposit',
71
+ args: [assets, getAddress(receiver)],
72
+ account,
73
+ chain: walletClient.chain,
74
+ })
75
+
76
+ return { txHash, shares }
77
+ }
78
+
79
+ /**
80
+ * Alias: D3 — Cross-chain hub deposit when oracle accounting is ON.
81
+ * Exactly the same UX as depositSimple because the vault resolves accounting synchronously.
82
+ */
83
+ export { depositSimple as depositCrossChainOracleOn }
84
+
85
+ /**
86
+ * D2 — Multi-asset deposit.
87
+ *
88
+ * Deposits multiple ERC-20 tokens into the vault in a single vault call.
89
+ * The vault converts each token to the underlying via oracle pricing.
90
+ *
91
+ * **User transactions**: N approves (one per token, skipped if sufficient) + 1 deposit.
92
+ *
93
+ * @param walletClient Wallet client with account attached
94
+ * @param publicClient Public client for reads and simulation
95
+ * @param addresses Vault address set (only `vault` is used)
96
+ * @param tokens Array of token addresses to deposit
97
+ * @param amounts Array of amounts (one per token, in each token's decimals)
98
+ * @param receiver Address that will receive the minted vault shares
99
+ * @param minShares Minimum shares to accept (slippage protection)
100
+ * @returns Transaction hash and amount of shares minted
101
+ */
102
+ export async function depositMultiAsset(
103
+ walletClient: WalletClient,
104
+ publicClient: PublicClient,
105
+ addresses: VaultAddresses,
106
+ tokens: Address[],
107
+ amounts: bigint[],
108
+ receiver: Address,
109
+ minShares: bigint,
110
+ ): Promise<DepositResult> {
111
+ const account = walletClient.account!
112
+ const vault = getAddress(addresses.vault)
113
+
114
+ // Approve each token
115
+ for (let i = 0; i < tokens.length; i++) {
116
+ await ensureAllowance(walletClient, publicClient, tokens[i], vault, amounts[i])
117
+ }
118
+
119
+ const { result: shares } = await publicClient.simulateContract({
120
+ address: vault,
121
+ abi: VAULT_ABI,
122
+ functionName: 'deposit',
123
+ args: [tokens, amounts, getAddress(receiver), minShares],
124
+ account: account.address,
125
+ })
126
+
127
+ const txHash = await walletClient.writeContract({
128
+ address: vault,
129
+ abi: VAULT_ABI,
130
+ functionName: 'deposit',
131
+ args: [tokens, amounts, getAddress(receiver), minShares],
132
+ account,
133
+ chain: walletClient.chain,
134
+ })
135
+
136
+ return { txHash, shares }
137
+ }
138
+
139
+ /**
140
+ * D4 — Async deposit (cross-chain hub, oracle OFF).
141
+ *
142
+ * Sends assets to the escrow and initiates a cross-chain accounting request via
143
+ * `initVaultActionRequest(DEPOSIT, ...)`. The LZ Read callback will resolve
144
+ * accounting and `executeRequest` will mint shares.
145
+ *
146
+ * **User transactions**: 1 approve (to ESCROW, not vault!) + 1 initVaultActionRequest.
147
+ * **Wait**: Shares arrive after the LZ callback + executeRequest (automated by keeper).
148
+ *
149
+ * @param walletClient Wallet client with account attached
150
+ * @param publicClient Public client for reads and simulation
151
+ * @param addresses Vault address set (`vault` + `escrow` required)
152
+ * @param assets Amount of underlying to deposit
153
+ * @param receiver Address that will receive shares after resolution
154
+ * @param lzFee msg.value for LZ Read fee (quote first with `quoteLzFee`)
155
+ * @param extraOptions Optional LZ extra options bytes (default 0x)
156
+ * @returns Transaction hash and GUID for tracking
157
+ */
158
+ export async function depositAsync(
159
+ walletClient: WalletClient,
160
+ publicClient: PublicClient,
161
+ addresses: VaultAddresses,
162
+ assets: bigint,
163
+ receiver: Address,
164
+ lzFee: bigint,
165
+ extraOptions: `0x${string}` = '0x',
166
+ ): Promise<AsyncRequestResult> {
167
+ const account = walletClient.account!
168
+ const vault = getAddress(addresses.vault)
169
+ if (!addresses.escrow) throw new MissingEscrowAddressError()
170
+ const escrow = getAddress(addresses.escrow)
171
+
172
+ // Pre-flight: validate async cross-chain setup before sending any transaction
173
+ await preflightAsync(publicClient, vault, escrow)
174
+
175
+ // Resolve underlying asset
176
+ const underlying = await publicClient.readContract({
177
+ address: vault,
178
+ abi: VAULT_ABI,
179
+ functionName: 'asset',
180
+ })
181
+
182
+ // Approve ESCROW (not vault!) for the deposit amount
183
+ await ensureAllowance(walletClient, publicClient, underlying, escrow, assets)
184
+
185
+ // Encode parameters only (no selector) — contracts use abi.decode on these bytes
186
+ const actionCallData = encodeAbiParameters(
187
+ [{ type: 'uint256', name: 'assets' }, { type: 'address', name: 'receiver' }],
188
+ [assets, getAddress(receiver)],
189
+ ) as `0x${string}`
190
+
191
+ const { result: guid } = await publicClient.simulateContract({
192
+ address: vault,
193
+ abi: BRIDGE_ABI,
194
+ functionName: 'initVaultActionRequest',
195
+ args: [ActionType.DEPOSIT, actionCallData, 0n, extraOptions],
196
+ value: lzFee,
197
+ account: account.address,
198
+ })
199
+
200
+ const txHash = await walletClient.writeContract({
201
+ address: vault,
202
+ abi: BRIDGE_ABI,
203
+ functionName: 'initVaultActionRequest',
204
+ args: [ActionType.DEPOSIT, actionCallData, 0n, extraOptions],
205
+ value: lzFee,
206
+ account,
207
+ chain: walletClient.chain,
208
+ })
209
+
210
+ return { txHash, guid: guid as `0x${string}` }
211
+ }
212
+
213
+ /**
214
+ * D5 — Async mint (cross-chain hub, oracle OFF).
215
+ *
216
+ * Mints an exact amount of shares by depositing up to `maxAssets` of underlying.
217
+ * Similar flow to D4 but uses MINT action type.
218
+ *
219
+ * **User transactions**: 1 approve (to ESCROW for maxAssets) + 1 initVaultActionRequest.
220
+ * **Wait**: Shares arrive after the LZ callback + executeRequest.
221
+ *
222
+ * @param walletClient Wallet client with account attached
223
+ * @param publicClient Public client for reads and simulation
224
+ * @param addresses Vault address set (`vault` + `escrow` required)
225
+ * @param shares Exact number of shares to mint
226
+ * @param maxAssets Maximum assets to spend (slippage protection)
227
+ * @param receiver Address that will receive the minted shares
228
+ * @param lzFee msg.value for LZ Read fee
229
+ * @param extraOptions Optional LZ extra options bytes
230
+ * @returns Transaction hash and GUID for tracking
231
+ */
232
+ export async function mintAsync(
233
+ walletClient: WalletClient,
234
+ publicClient: PublicClient,
235
+ addresses: VaultAddresses,
236
+ shares: bigint,
237
+ maxAssets: bigint,
238
+ receiver: Address,
239
+ lzFee: bigint,
240
+ extraOptions: `0x${string}` = '0x',
241
+ ): Promise<AsyncRequestResult> {
242
+ const account = walletClient.account!
243
+ const vault = getAddress(addresses.vault)
244
+ if (!addresses.escrow) throw new MissingEscrowAddressError()
245
+ const escrow = getAddress(addresses.escrow)
246
+
247
+ // Pre-flight: validate async cross-chain setup before sending any transaction
248
+ await preflightAsync(publicClient, vault, escrow)
249
+
250
+ const underlying = await publicClient.readContract({
251
+ address: vault,
252
+ abi: VAULT_ABI,
253
+ functionName: 'asset',
254
+ })
255
+
256
+ // Approve ESCROW for maxAssets
257
+ await ensureAllowance(walletClient, publicClient, underlying, escrow, maxAssets)
258
+
259
+ // Encode parameters only (no selector) — contracts use abi.decode on these bytes
260
+ const actionCallData = encodeAbiParameters(
261
+ [{ type: 'uint256', name: 'shares' }, { type: 'address', name: 'receiver' }],
262
+ [shares, getAddress(receiver)],
263
+ ) as `0x${string}`
264
+
265
+ // amountLimit = maxAssets (slippage check: actual assets spent must be <= maxAssets)
266
+ const { result: guid } = await publicClient.simulateContract({
267
+ address: vault,
268
+ abi: BRIDGE_ABI,
269
+ functionName: 'initVaultActionRequest',
270
+ args: [ActionType.MINT, actionCallData, maxAssets, extraOptions],
271
+ value: lzFee,
272
+ account: account.address,
273
+ })
274
+
275
+ const txHash = await walletClient.writeContract({
276
+ address: vault,
277
+ abi: BRIDGE_ABI,
278
+ functionName: 'initVaultActionRequest',
279
+ args: [ActionType.MINT, actionCallData, maxAssets, extraOptions],
280
+ value: lzFee,
281
+ account,
282
+ chain: walletClient.chain,
283
+ })
284
+
285
+ return { txHash, guid: guid as `0x${string}` }
286
+ }
287
+
288
+ /**
289
+ * Smart deposit — auto-selects the correct flow based on vault configuration.
290
+ *
291
+ * Calls getVaultStatus internally to determine the vault mode, then dispatches
292
+ * to the appropriate flow:
293
+ * - local / cross-chain-oracle → depositSimple
294
+ * - cross-chain-async → depositAsync (quotes LZ fee automatically)
295
+ *
296
+ * @param walletClient Wallet client with account attached
297
+ * @param publicClient Public client for reads
298
+ * @param addresses Vault address set (`escrow` required for async vaults)
299
+ * @param assets Amount of underlying to deposit
300
+ * @param receiver Address that will receive shares
301
+ * @param extraOptions Optional LZ extra options (only used for async vaults)
302
+ * @returns DepositResult or AsyncRequestResult depending on vault mode
303
+ * @throws VaultPausedError if vault is paused
304
+ * @throws CapacityFullError if vault is full
305
+ */
306
+ export async function smartDeposit(
307
+ walletClient: WalletClient,
308
+ publicClient: PublicClient,
309
+ addresses: VaultAddresses,
310
+ assets: bigint,
311
+ receiver: Address,
312
+ extraOptions: `0x${string}` = '0x',
313
+ ): Promise<DepositResult | AsyncRequestResult> {
314
+ const vault = getAddress(addresses.vault)
315
+ const status = await getVaultStatus(publicClient, vault)
316
+
317
+ if (status.mode === 'paused') {
318
+ throw new VaultPausedError(vault)
319
+ }
320
+ if (status.mode === 'full') {
321
+ throw new CapacityFullError(vault)
322
+ }
323
+
324
+ if (status.recommendedDepositFlow === 'depositAsync') {
325
+ const lzFee = await quoteLzFee(publicClient, vault, extraOptions)
326
+ return depositAsync(walletClient, publicClient, addresses, assets, receiver, lzFee, extraOptions)
327
+ }
328
+
329
+ // local or cross-chain-oracle
330
+ return depositSimple(walletClient, publicClient, addresses, assets, receiver)
331
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Typed error classes for the MoreVaults SDK.
3
+ *
4
+ * Frontend code can use instanceof checks to handle errors programmatically:
5
+ * catch (e) {
6
+ * if (e instanceof InsufficientLiquidityError) { ... }
7
+ * }
8
+ */
9
+
10
+ export class MoreVaultsError extends Error {
11
+ constructor(message: string) {
12
+ super(message)
13
+ this.name = 'MoreVaultsError'
14
+ }
15
+ }
16
+
17
+ export class VaultPausedError extends MoreVaultsError {
18
+ constructor(vault: string) {
19
+ super(`[MoreVaults] Vault ${vault} is paused. Cannot perform any actions.`)
20
+ this.name = 'VaultPausedError'
21
+ }
22
+ }
23
+
24
+ export class CapacityFullError extends MoreVaultsError {
25
+ constructor(vault: string) {
26
+ super(`[MoreVaults] Vault ${vault} has reached deposit capacity. No more deposits accepted.`)
27
+ this.name = 'CapacityFullError'
28
+ }
29
+ }
30
+
31
+ export class NotWhitelistedError extends MoreVaultsError {
32
+ constructor(vault: string, user: string) {
33
+ super(`[MoreVaults] Address ${user} is not whitelisted to deposit in vault ${vault}.`)
34
+ this.name = 'NotWhitelistedError'
35
+ }
36
+ }
37
+
38
+ export class InsufficientLiquidityError extends MoreVaultsError {
39
+ hubLiquid: bigint
40
+ required: bigint
41
+ constructor(vault: string, hubLiquid: bigint, required: bigint) {
42
+ super(
43
+ `[MoreVaults] Insufficient hub liquidity for redeem.\n` +
44
+ ` Hub liquid balance : ${hubLiquid}\n` +
45
+ ` Estimated required : ${required}\n` +
46
+ `Submitting this redeem will waste the LayerZero fee — the request will be auto-refunded.\n` +
47
+ `Ask the vault curator to repatriate liquidity from spoke chains first.`
48
+ )
49
+ this.name = 'InsufficientLiquidityError'
50
+ this.hubLiquid = hubLiquid
51
+ this.required = required
52
+ }
53
+ }
54
+
55
+ export class CCManagerNotConfiguredError extends MoreVaultsError {
56
+ constructor(vault: string) {
57
+ super(`[MoreVaults] CCManager not configured on vault ${vault}. Call setCrossChainAccountingManager(ccManagerAddress) as vault owner first.`)
58
+ this.name = 'CCManagerNotConfiguredError'
59
+ }
60
+ }
61
+
62
+ export class EscrowNotConfiguredError extends MoreVaultsError {
63
+ constructor(vault: string) {
64
+ super(`[MoreVaults] Escrow not configured for vault ${vault}. The registry must have an escrow set for this vault.`)
65
+ this.name = 'EscrowNotConfiguredError'
66
+ }
67
+ }
68
+
69
+ export class NotHubVaultError extends MoreVaultsError {
70
+ constructor(vault: string) {
71
+ super(`[MoreVaults] Vault ${vault} is not a hub vault. Async flows (D4/D5/R5) only work on hub vaults.`)
72
+ this.name = 'NotHubVaultError'
73
+ }
74
+ }
75
+
76
+ export class MissingEscrowAddressError extends MoreVaultsError {
77
+ constructor() {
78
+ super(`[MoreVaults] This flow requires an escrow address. Set VaultAddresses.escrow before calling async deposit/redeem flows.`)
79
+ this.name = 'MissingEscrowAddressError'
80
+ }
81
+ }
@@ -0,0 +1,100 @@
1
+ // MoreVaults SDK — viem/wagmi
2
+ // Provides typed helpers for all deposit, redeem, and cross-chain vault flows.
3
+
4
+ // --- ABIs ---
5
+ export {
6
+ VAULT_ABI,
7
+ BRIDGE_ABI,
8
+ CONFIG_ABI,
9
+ ERC20_ABI,
10
+ OFT_ABI,
11
+ METADATA_ABI,
12
+ } from './abis'
13
+
14
+ // --- Types ---
15
+ export type {
16
+ VaultAddresses,
17
+ DepositResult,
18
+ RedeemResult,
19
+ AsyncRequestResult,
20
+ CrossChainRequestInfo,
21
+ ActionTypeValue,
22
+ } from './types'
23
+ export { ActionType } from './types'
24
+
25
+ // --- Errors ---
26
+ export {
27
+ MoreVaultsError,
28
+ VaultPausedError,
29
+ CapacityFullError,
30
+ NotWhitelistedError,
31
+ InsufficientLiquidityError,
32
+ CCManagerNotConfiguredError,
33
+ EscrowNotConfiguredError,
34
+ NotHubVaultError,
35
+ MissingEscrowAddressError,
36
+ } from './errors'
37
+
38
+ // --- Deposit Flows ---
39
+ export {
40
+ depositSimple,
41
+ depositCrossChainOracleOn,
42
+ depositMultiAsset,
43
+ depositAsync,
44
+ mintAsync,
45
+ smartDeposit,
46
+ } from './depositFlows'
47
+
48
+ // --- Cross-Chain Flows ---
49
+ export {
50
+ depositFromSpoke,
51
+ depositFromSpokeAsync,
52
+ quoteDepositFromSpokeFee,
53
+ } from './crossChainFlows'
54
+
55
+ // --- Redeem Flows ---
56
+ export {
57
+ redeemShares,
58
+ withdrawAssets,
59
+ requestRedeem,
60
+ getWithdrawalRequest,
61
+ redeemAsync,
62
+ bridgeSharesToHub,
63
+ } from './redeemFlows'
64
+
65
+ // --- Utilities ---
66
+ export {
67
+ ensureAllowance,
68
+ quoteLzFee,
69
+ isAsyncMode,
70
+ getAsyncRequestStatus,
71
+ getVaultStatus,
72
+ } from './utils'
73
+ export type { VaultStatus, VaultMode } from './utils'
74
+
75
+ // --- Pre-flight validation ---
76
+ export { preflightSync, preflightAsync, preflightRedeemLiquidity } from './preflight'
77
+
78
+ // --- User Helpers ---
79
+ export {
80
+ getUserPosition,
81
+ previewDeposit,
82
+ previewRedeem,
83
+ canDeposit,
84
+ getVaultMetadata,
85
+ getAsyncRequestStatusLabel,
86
+ getUserBalances,
87
+ getMaxWithdrawable,
88
+ getVaultSummary,
89
+ } from './userHelpers'
90
+ export type {
91
+ UserPosition,
92
+ DepositEligibility,
93
+ DepositBlockReason,
94
+ VaultMetadata,
95
+ AsyncRequestStatus,
96
+ AsyncRequestStatusInfo,
97
+ UserBalances,
98
+ MaxWithdrawable,
99
+ VaultSummary,
100
+ } from './userHelpers'