viem 2.55.5 → 2.55.7

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/_cjs/errors/version.js +1 -1
  3. package/_cjs/tempo/Abis.js +1809 -1
  4. package/_cjs/tempo/Abis.js.map +1 -1
  5. package/_cjs/tempo/Decorator.js +25 -2
  6. package/_cjs/tempo/Decorator.js.map +1 -1
  7. package/_cjs/tempo/actions/earn.js +1368 -0
  8. package/_cjs/tempo/actions/earn.js.map +1 -0
  9. package/_cjs/tempo/actions/index.js +2 -1
  10. package/_cjs/tempo/actions/index.js.map +1 -1
  11. package/_cjs/tempo/actions/zone.js +47 -61
  12. package/_cjs/tempo/actions/zone.js.map +1 -1
  13. package/_cjs/tempo/chainConfig.js +1 -1
  14. package/_cjs/tempo/chainConfig.js.map +1 -1
  15. package/_cjs/tempo/errors.js +28 -4
  16. package/_cjs/tempo/errors.js.map +1 -1
  17. package/_cjs/tempo/index.js +2 -1
  18. package/_cjs/tempo/index.js.map +1 -1
  19. package/_cjs/tempo/internal/WithdrawalSenderTag.js +9 -0
  20. package/_cjs/tempo/internal/WithdrawalSenderTag.js.map +1 -0
  21. package/_cjs/tempo/zones/Abis.js +46 -0
  22. package/_cjs/tempo/zones/Abis.js.map +1 -1
  23. package/_cjs/utils/blob/fromBlobs.js +5 -2
  24. package/_cjs/utils/blob/fromBlobs.js.map +1 -1
  25. package/_cjs/utils/encoding/fromBytes.js +2 -2
  26. package/_cjs/utils/encoding/fromBytes.js.map +1 -1
  27. package/_esm/errors/version.js +1 -1
  28. package/_esm/tempo/Abis.js +1810 -0
  29. package/_esm/tempo/Abis.js.map +1 -1
  30. package/_esm/tempo/Decorator.js +25 -2
  31. package/_esm/tempo/Decorator.js.map +1 -1
  32. package/_esm/tempo/actions/earn.js +2100 -0
  33. package/_esm/tempo/actions/earn.js.map +1 -0
  34. package/_esm/tempo/actions/index.js +1 -0
  35. package/_esm/tempo/actions/index.js.map +1 -1
  36. package/_esm/tempo/actions/zone.js +104 -119
  37. package/_esm/tempo/actions/zone.js.map +1 -1
  38. package/_esm/tempo/chainConfig.js +2 -1
  39. package/_esm/tempo/chainConfig.js.map +1 -1
  40. package/_esm/tempo/errors.js +25 -2
  41. package/_esm/tempo/errors.js.map +1 -1
  42. package/_esm/tempo/index.js +1 -1
  43. package/_esm/tempo/index.js.map +1 -1
  44. package/_esm/tempo/internal/WithdrawalSenderTag.js +7 -0
  45. package/_esm/tempo/internal/WithdrawalSenderTag.js.map +1 -0
  46. package/_esm/tempo/zones/Abis.js +46 -0
  47. package/_esm/tempo/zones/Abis.js.map +1 -1
  48. package/_esm/utils/blob/fromBlobs.js +6 -2
  49. package/_esm/utils/blob/fromBlobs.js.map +1 -1
  50. package/_esm/utils/encoding/fromBytes.js +2 -2
  51. package/_esm/utils/encoding/fromBytes.js.map +1 -1
  52. package/_types/errors/version.d.ts +1 -1
  53. package/_types/tempo/Abis.d.ts +2880 -0
  54. package/_types/tempo/Abis.d.ts.map +1 -1
  55. package/_types/tempo/Decorator.d.ts +499 -41
  56. package/_types/tempo/Decorator.d.ts.map +1 -1
  57. package/_types/tempo/actions/earn.d.ts +7549 -0
  58. package/_types/tempo/actions/earn.d.ts.map +1 -0
  59. package/_types/tempo/actions/index.d.ts +1 -0
  60. package/_types/tempo/actions/index.d.ts.map +1 -1
  61. package/_types/tempo/actions/zone.d.ts +86 -109
  62. package/_types/tempo/actions/zone.d.ts.map +1 -1
  63. package/_types/tempo/chainConfig.d.ts.map +1 -1
  64. package/_types/tempo/errors.d.ts +33 -3
  65. package/_types/tempo/errors.d.ts.map +1 -1
  66. package/_types/tempo/index.d.ts +1 -1
  67. package/_types/tempo/index.d.ts.map +1 -1
  68. package/_types/tempo/internal/WithdrawalSenderTag.d.ts +11 -0
  69. package/_types/tempo/internal/WithdrawalSenderTag.d.ts.map +1 -0
  70. package/_types/tempo/zones/Abis.d.ts +87 -0
  71. package/_types/tempo/zones/Abis.d.ts.map +1 -1
  72. package/_types/utils/blob/fromBlobs.d.ts.map +1 -1
  73. package/errors/version.ts +1 -1
  74. package/package.json +2 -2
  75. package/tempo/Abis.ts +1822 -0
  76. package/tempo/Decorator.ts +572 -47
  77. package/tempo/actions/earn.ts +3319 -0
  78. package/tempo/actions/index.ts +1 -0
  79. package/tempo/actions/zone.ts +183 -230
  80. package/tempo/chainConfig.ts +3 -1
  81. package/tempo/errors.ts +54 -6
  82. package/tempo/index.ts +1 -0
  83. package/tempo/internal/WithdrawalSenderTag.ts +20 -0
  84. package/tempo/zones/Abis.ts +46 -0
  85. package/utils/blob/fromBlobs.ts +6 -2
  86. package/utils/encoding/fromBytes.ts +2 -2
@@ -0,0 +1,3319 @@
1
+ import type { Address } from 'abitype'
2
+ import { Hex } from 'ox'
3
+ import { EarnShares, TokenId } from 'ox/tempo'
4
+ import type { Account } from '../../accounts/types.js'
5
+ import { parseAccount } from '../../accounts/utils/parseAccount.js'
6
+ import { estimateContractGas } from '../../actions/public/estimateContractGas.js'
7
+ import { getBlockNumber } from '../../actions/public/getBlockNumber.js'
8
+ import { type GetLogsErrorType, getLogs } from '../../actions/public/getLogs.js'
9
+ import { multicall } from '../../actions/public/multicall.js'
10
+ import {
11
+ type ReadContractReturnType,
12
+ readContract,
13
+ } from '../../actions/public/readContract.js'
14
+ import {
15
+ type SimulateContractReturnType,
16
+ simulateContract,
17
+ } from '../../actions/public/simulateContract.js'
18
+ import * as internal_Token from '../../actions/token/internal.js'
19
+ import {
20
+ type SendTransactionReturnType,
21
+ sendTransaction,
22
+ } from '../../actions/wallet/sendTransaction.js'
23
+ import { sendTransactionSync } from '../../actions/wallet/sendTransactionSync.js'
24
+ import { writeContractSync } from '../../actions/wallet/writeContractSync.js'
25
+ import type { Client } from '../../clients/createClient.js'
26
+ import type { Transport } from '../../clients/transports/createTransport.js'
27
+ import { AccountNotFoundError } from '../../errors/account.js'
28
+ import type { BaseErrorType } from '../../errors/base.js'
29
+ import type { Chain } from '../../types/chain.js'
30
+ import type { Log } from '../../types/log.js'
31
+ import type { Compute, OneOf } from '../../types/utils.js'
32
+ import { encodeAbiParameters } from '../../utils/abi/encodeAbiParameters.js'
33
+ import { getAbiItem } from '../../utils/abi/getAbiItem.js'
34
+ import { parseEventLogs } from '../../utils/abi/parseEventLogs.js'
35
+ import { getAddress } from '../../utils/address/getAddress.js'
36
+ import { isAddressEqual } from '../../utils/address/isAddressEqual.js'
37
+ import { type ObserveErrorType, observe } from '../../utils/observe.js'
38
+ import { type PollErrorType, poll } from '../../utils/poll.js'
39
+ import { withResolvers } from '../../utils/promise/withResolvers.js'
40
+ import { stringify } from '../../utils/stringify.js'
41
+ import * as Abis from '../Abis.js'
42
+ import * as Addresses from '../Addresses.js'
43
+ import {
44
+ GetVaultEngineChangedError,
45
+ type GetVaultEngineChangedErrorType,
46
+ WaitForPrivateDepositTimeoutError,
47
+ type WaitForPrivateDepositTimeoutErrorType,
48
+ WaitForPrivateRedeemTimeoutError,
49
+ type WaitForPrivateRedeemTimeoutErrorType,
50
+ } from '../errors.js'
51
+ import type {
52
+ GetAccountParameter,
53
+ ReadParameters,
54
+ WriteParameters,
55
+ WriteSyncParameters,
56
+ } from '../internal/types.js'
57
+ import {
58
+ type CallParameters,
59
+ defineCall,
60
+ pickWriteParameters,
61
+ resolveCallParameters,
62
+ resolveTokenWithDecimals,
63
+ } from '../internal/utils.js'
64
+ import type { TransactionReceipt } from '../Transaction.js'
65
+ import * as policyActions from './policy.js'
66
+ import * as tokenActions from './token.js'
67
+ import * as zoneActions from './zone.js'
68
+
69
+ /** TIP-403 policy ID that allows every sender, recipient, and mint recipient. */
70
+ export const alwaysAllowPolicyId = 1n
71
+
72
+ /** Admission-only TIP-403 policy attached to an Earn vault share token. */
73
+ export type ExitSafePolicy = {
74
+ /** Compound policy attached to the vault share token. */
75
+ transferPolicyId: bigint
76
+ /** Sender policy. Must be {@link alwaysAllowPolicyId} so holders can exit. */
77
+ senderPolicyId: bigint
78
+ /** Recipient eligibility policy. */
79
+ recipientPolicyId: bigint
80
+ /** Mint-recipient eligibility policy. Must match `recipientPolicyId`. */
81
+ mintRecipientPolicyId: bigint
82
+ }
83
+
84
+ /** Receipts produced while configuring an exit-safe Earn policy. */
85
+ export type ExitSafePolicyReceipts = {
86
+ /** Whitelist policy creation receipt. */
87
+ eligibilityPolicy: TransactionReceipt
88
+ /** Compound policy creation receipt. */
89
+ compoundPolicy: TransactionReceipt
90
+ /** Vault share token policy update receipt. */
91
+ tokenPolicy: TransactionReceipt
92
+ /** Eligibility policy admin transfer receipt, when an administrator is provided. */
93
+ policyAdmin?: TransactionReceipt | undefined
94
+ }
95
+
96
+ /**
97
+ * Creates and attaches an admission-only TIP-403 policy to an Earn vault share
98
+ * token. Existing holders remain able to send shares while recipients and mint
99
+ * recipients must belong to the same whitelist.
100
+ *
101
+ * The action submits three or four sequential transactions and is not atomic.
102
+ * Use {@link validateExitSafePolicy} to verify the final state.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * import { createClient, http } from 'viem'
107
+ * import { privateKeyToAccount } from 'viem/accounts'
108
+ * import { tempoModerato } from 'viem/chains'
109
+ * import { Actions } from 'viem/tempo'
110
+ *
111
+ * const account = privateKeyToAccount('0x...')
112
+ * const client = createClient({
113
+ * account,
114
+ * chain: tempoModerato,
115
+ * transport: http(),
116
+ * })
117
+ *
118
+ * const { policy, receipts } =
119
+ * await Actions.earn.configureExitSafePolicy(client, {
120
+ * accessAdministrator: '0x...',
121
+ * initialMembers: ['0x...', '0x...'],
122
+ * shareToken: '0x...',
123
+ * })
124
+ * ```
125
+ *
126
+ * @param client - Client authorized to change the vault share token policy.
127
+ * @param parameters - Share token, administrator, and initial members.
128
+ * @returns The configured policy IDs and transaction receipts.
129
+ */
130
+ export async function configureExitSafePolicy<
131
+ chain extends Chain | undefined,
132
+ account extends Account | undefined,
133
+ >(
134
+ client: Client<Transport, chain, account>,
135
+ parameters: configureExitSafePolicy.Parameters<account>,
136
+ ): Promise<configureExitSafePolicy.ReturnValue> {
137
+ const account_ = parameters.account ?? client.account
138
+ if (!account_) throw new AccountNotFoundError()
139
+ const account = parseAccount(account_)
140
+ const initialMembers = [
141
+ ...new Set(parameters.initialMembers.map((member) => getAddress(member))),
142
+ ]
143
+ if (initialMembers.length === 0)
144
+ throw new Error('At least one initial policy member is required.')
145
+
146
+ const eligibility = await policyActions.createSync(client, {
147
+ account,
148
+ addresses: initialMembers,
149
+ chain: client.chain,
150
+ type: 'whitelist',
151
+ } as never)
152
+ const compoundPolicy = await writeContractSync(client, {
153
+ account,
154
+ abi: Abis.tip403Registry,
155
+ address: Addresses.tip403Registry,
156
+ args: [alwaysAllowPolicyId, eligibility.policyId, eligibility.policyId],
157
+ chain: client.chain,
158
+ functionName: 'createCompoundPolicy',
159
+ throwOnReceiptRevert: true,
160
+ } as never)
161
+ const [compoundEvent] = parseEventLogs({
162
+ abi: Abis.tip403Registry,
163
+ eventName: 'CompoundPolicyCreated',
164
+ logs: compoundPolicy.logs,
165
+ strict: true,
166
+ })
167
+ if (!compoundEvent)
168
+ throw new Error('`CompoundPolicyCreated` event not found.')
169
+
170
+ const tokenPolicy = await tokenActions.changeTransferPolicySync(client, {
171
+ account,
172
+ chain: client.chain,
173
+ policyId: compoundEvent.args.policyId,
174
+ token: parameters.shareToken,
175
+ } as never)
176
+ const policyAdmin = isAddressEqual(
177
+ parameters.accessAdministrator,
178
+ account.address,
179
+ )
180
+ ? undefined
181
+ : await policyActions.setAdminSync(client, {
182
+ account,
183
+ admin: parameters.accessAdministrator,
184
+ chain: client.chain,
185
+ policyId: eligibility.policyId,
186
+ } as never)
187
+
188
+ return {
189
+ policy: {
190
+ transferPolicyId: compoundEvent.args.policyId,
191
+ senderPolicyId: alwaysAllowPolicyId,
192
+ recipientPolicyId: eligibility.policyId,
193
+ mintRecipientPolicyId: eligibility.policyId,
194
+ },
195
+ receipts: {
196
+ eligibilityPolicy: eligibility.receipt,
197
+ compoundPolicy,
198
+ tokenPolicy: tokenPolicy.receipt,
199
+ policyAdmin: policyAdmin?.receipt,
200
+ },
201
+ }
202
+ }
203
+
204
+ export namespace configureExitSafePolicy {
205
+ export type Args = {
206
+ /** Address that will administer recipient eligibility. */
207
+ accessAdministrator: Address
208
+ /** Addresses initially eligible to receive or be minted vault shares. */
209
+ initialMembers: readonly Address[]
210
+ /** Earn vault share token. */
211
+ shareToken: Address
212
+ }
213
+ export type Parameters<
214
+ account extends Account | undefined = Account | undefined,
215
+ > = GetAccountParameter<account> & Args
216
+ export type ReturnValue = Compute<{
217
+ /** Configured onchain policy IDs. */
218
+ policy: ExitSafePolicy
219
+ /** Receipts for each configuration transaction. */
220
+ receipts: ExitSafePolicyReceipts
221
+ }>
222
+ // TODO: exhaustive error type
223
+ export type ErrorType = BaseErrorType
224
+ }
225
+
226
+ /**
227
+ * Verifies that an Earn vault share token uses the expected exit-safe TIP-403
228
+ * policy and that every required member can receive transfers and mints.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * import { createClient, http } from 'viem'
233
+ * import { tempoModerato } from 'viem/chains'
234
+ * import { Actions } from 'viem/tempo'
235
+ *
236
+ * const client = createClient({
237
+ * chain: tempoModerato,
238
+ * transport: http(),
239
+ * })
240
+ *
241
+ * await Actions.earn.validateExitSafePolicy(client, {
242
+ * accessAdministrator: '0x...',
243
+ * policy: {
244
+ * transferPolicyId: 3n,
245
+ * senderPolicyId: 1n,
246
+ * recipientPolicyId: 2n,
247
+ * mintRecipientPolicyId: 2n,
248
+ * },
249
+ * requiredMembers: ['0x...', '0x...'],
250
+ * shareToken: '0x...',
251
+ * })
252
+ * ```
253
+ *
254
+ * @param client - Client.
255
+ * @param parameters - Expected policy, administrator, and required members.
256
+ * @returns Nothing when the policy is valid.
257
+ */
258
+ export async function validateExitSafePolicy<chain extends Chain | undefined>(
259
+ client: Client<Transport, chain>,
260
+ parameters: validateExitSafePolicy.Parameters,
261
+ ): Promise<validateExitSafePolicy.ReturnValue> {
262
+ const { accessAdministrator, policy, requiredMembers, shareToken, ...rest } =
263
+ parameters
264
+ const [tokenPolicyId, compound, simplePolicy, memberResults] =
265
+ await Promise.all([
266
+ readContract(client, {
267
+ ...rest,
268
+ abi: Abis.tip20,
269
+ address: shareToken,
270
+ functionName: 'transferPolicyId',
271
+ }),
272
+ readContract(client, {
273
+ ...rest,
274
+ abi: Abis.tip403Registry,
275
+ address: Addresses.tip403Registry,
276
+ args: [policy.transferPolicyId],
277
+ functionName: 'compoundPolicyData',
278
+ }),
279
+ readContract(client, {
280
+ ...rest,
281
+ abi: Abis.tip403Registry,
282
+ address: Addresses.tip403Registry,
283
+ args: [policy.recipientPolicyId],
284
+ functionName: 'policyData',
285
+ }),
286
+ Promise.all(
287
+ requiredMembers.map(async (member) => {
288
+ const [recipient, mintRecipient] = await Promise.all([
289
+ readContract(client, {
290
+ ...rest,
291
+ abi: Abis.tip403Registry,
292
+ address: Addresses.tip403Registry,
293
+ args: [policy.transferPolicyId, member],
294
+ functionName: 'isAuthorizedRecipient',
295
+ }),
296
+ readContract(client, {
297
+ ...rest,
298
+ abi: Abis.tip403Registry,
299
+ address: Addresses.tip403Registry,
300
+ args: [policy.transferPolicyId, member],
301
+ functionName: 'isAuthorizedMintRecipient',
302
+ }),
303
+ ])
304
+ return { member, mintRecipient, recipient }
305
+ }),
306
+ ),
307
+ ])
308
+
309
+ if (tokenPolicyId !== policy.transferPolicyId)
310
+ throw new Error('Earn vault share token transfer policy mismatch.')
311
+ if (
312
+ compound[0] !== policy.senderPolicyId ||
313
+ compound[1] !== policy.recipientPolicyId ||
314
+ compound[2] !== policy.mintRecipientPolicyId
315
+ )
316
+ throw new Error('TIP-403 compound policy components mismatch.')
317
+ if (policy.senderPolicyId !== alwaysAllowPolicyId)
318
+ throw new Error('TIP-403 sender policy is not always allow.')
319
+ if (policy.recipientPolicyId !== policy.mintRecipientPolicyId)
320
+ throw new Error('TIP-403 recipient and mint-recipient policies must match.')
321
+ if (simplePolicy[0] !== 0)
322
+ throw new Error('TIP-403 eligibility policy is not a whitelist.')
323
+ if (!isAddressEqual(simplePolicy[1], accessAdministrator))
324
+ throw new Error('TIP-403 access administrator mismatch.')
325
+ const unauthorized = memberResults.find(
326
+ (result) => !result.recipient || !result.mintRecipient,
327
+ )
328
+ if (unauthorized)
329
+ throw new Error(
330
+ `Required TIP-403 member is unauthorized: ${unauthorized.member}`,
331
+ )
332
+ }
333
+
334
+ export namespace validateExitSafePolicy {
335
+ export type Args = {
336
+ /** Expected eligibility policy administrator. */
337
+ accessAdministrator: Address
338
+ /** Expected exit-safe policy IDs. */
339
+ policy: ExitSafePolicy
340
+ /** Addresses that must be eligible to receive or be minted vault shares. */
341
+ requiredMembers: readonly Address[]
342
+ /** Earn vault share token. */
343
+ shareToken: Address
344
+ }
345
+ export type Parameters = Omit<ReadParameters, 'account'> & Args
346
+ export type ReturnValue = void
347
+ // TODO: exhaustive error type
348
+ export type ErrorType = BaseErrorType
349
+ }
350
+
351
+ /**
352
+ * Deposits assets into a vault and mints vault shares to `recipient`. The
353
+ * transaction includes the required asset approval.
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * import { createClient, http } from 'viem'
358
+ * import { privateKeyToAccount } from 'viem/accounts'
359
+ * import { tempoModerato } from 'viem/chains'
360
+ * import { Actions } from 'viem/tempo'
361
+ *
362
+ * const client = createClient({
363
+ * account: privateKeyToAccount('0x...'),
364
+ * chain: tempoModerato,
365
+ * transport: http(),
366
+ * })
367
+ *
368
+ * const hash = await Actions.earn.deposit(client, {
369
+ * assetAmount: 100_000_000n,
370
+ * shareAmount: 99_900_000n,
371
+ * slippageBps: 50,
372
+ * vault: '0x...',
373
+ * })
374
+ * ```
375
+ *
376
+ * @param client - Client.
377
+ * @param parameters - Parameters.
378
+ * @returns The transaction hash.
379
+ */
380
+ export async function deposit<
381
+ chain extends Chain | undefined,
382
+ account extends Account | undefined,
383
+ >(
384
+ client: Client<Transport, chain, account>,
385
+ parameters: deposit.Parameters<chain, account>,
386
+ ): Promise<deposit.ReturnValue> {
387
+ return deposit.inner(sendTransaction, client, parameters)
388
+ }
389
+
390
+ export namespace deposit {
391
+ export type Args = {
392
+ /** Assets to deposit; base units or `{ formatted, decimals? }` (asset decimals). */
393
+ assetAmount: internal_Token.AmountInput
394
+ /** Vault share recipient. @default `account.address` */
395
+ recipient?: Address | undefined
396
+ /** Vault address. */
397
+ vault: Address
398
+ } & OneOf<
399
+ | {
400
+ /** Minimum vault share output to accept; must be greater than zero. */
401
+ shareAmountMin: bigint
402
+ }
403
+ | {
404
+ /** Quoted vault share output; floored by `slippageBps`. */
405
+ shareAmount: bigint
406
+ /** Slippage tolerance in basis points under `shareAmount` (50 = 0.5%). */
407
+ slippageBps: number
408
+ }
409
+ >
410
+ export type Parameters<
411
+ chain extends Chain | undefined = Chain | undefined,
412
+ account extends Account | undefined = Account | undefined,
413
+ > = WriteParameters<chain, account> & Args
414
+ export type ReturnValue = SendTransactionReturnType
415
+ // TODO: exhaustive error type
416
+ export type ErrorType = BaseErrorType
417
+
418
+ /** @internal Shared dispatch; reads the asset for the approval. */
419
+ export async function inner<
420
+ action extends typeof sendTransaction | typeof sendTransactionSync,
421
+ chain extends Chain | undefined,
422
+ account extends Account | undefined,
423
+ >(
424
+ action: action,
425
+ client: Client<Transport, chain, account>,
426
+ parameters: deposit.Parameters<chain, account>,
427
+ ): Promise<ReturnType<action>> {
428
+ const [args, assetToken] = await Promise.all([
429
+ toDepositArgs(client, parameters as never),
430
+ readContract(client, {
431
+ abi: Abis.vaultAdapter,
432
+ address: parameters.vault,
433
+ functionName: 'asset',
434
+ }),
435
+ ])
436
+ return (await action(client, {
437
+ ...parameters,
438
+ calls: deposit.calls({ ...args, assetToken }),
439
+ } as never)) as never
440
+ }
441
+
442
+ /**
443
+ * Defines a deposit call without an approval. Provide token decimals for
444
+ * formatted inputs and an explicit output bound because this builder performs no reads.
445
+ *
446
+ * @param parameters - Client (optional), followed by the call arguments.
447
+ * @returns The call.
448
+ */
449
+ export function call<chain extends Chain | undefined>(
450
+ ...parameters: CallParameters<call.Args, Client<Transport, chain>>
451
+ ) {
452
+ const [, args] = resolveCallParameters(parameters)
453
+ const { recipient, vault } = args
454
+ const shareAmountMin = (() => {
455
+ if (args.shareAmountMin !== undefined) return args.shareAmountMin
456
+ return EarnShares.minimumOutput(args.shareAmount, args.slippageBps)
457
+ })()
458
+ return defineCall({
459
+ address: vault,
460
+ abi: Abis.vaultAdapter,
461
+ functionName: 'deposit',
462
+ args: [
463
+ internal_Token.toBaseUnits(args.assetAmount, undefined),
464
+ recipient,
465
+ shareAmountMin,
466
+ ],
467
+ })
468
+ }
469
+ export namespace call {
470
+ export type Args = {
471
+ /** Assets to deposit; base units or `{ formatted, decimals? }` (asset decimals). */
472
+ assetAmount: internal_Token.AmountInput
473
+ /** Vault share recipient. */
474
+ recipient: Address
475
+ /** Vault address. */
476
+ vault: Address
477
+ } & OneOf<
478
+ | {
479
+ /** Minimum vault share output to accept. */
480
+ shareAmountMin: bigint
481
+ }
482
+ | {
483
+ /** Quoted vault share output; floored by `slippageBps`. */
484
+ shareAmount: bigint
485
+ /** Slippage tolerance in basis points under `shareAmount` (50 = 0.5%). */
486
+ slippageBps: number
487
+ }
488
+ >
489
+ }
490
+
491
+ /**
492
+ * Defines the asset approval and deposit calls for atomic execution. Pass
493
+ * `assetToken` and token decimals explicitly because this builder performs no reads.
494
+ *
495
+ * @param args - Arguments.
496
+ * @returns The calls.
497
+ */
498
+ export function calls(
499
+ args: call.Args & {
500
+ /** Asset token approved for the deposit. */
501
+ assetToken: TokenId.TokenIdOrAddress
502
+ },
503
+ ) {
504
+ const { assetToken, vault } = args
505
+ const assetAmount = internal_Token.toBaseUnits(args.assetAmount, undefined)
506
+ return [
507
+ defineCall({
508
+ address: TokenId.toAddress(assetToken),
509
+ abi: Abis.tip20,
510
+ functionName: 'approve',
511
+ args: [vault, assetAmount],
512
+ }),
513
+ deposit.call({ ...args, assetAmount }),
514
+ ]
515
+ }
516
+
517
+ /**
518
+ * Extracts a `Deposited` event from the vault's logs.
519
+ *
520
+ * @param logs - Logs.
521
+ * @param parameters - Parameters.
522
+ * @returns The `Deposited` event.
523
+ */
524
+ export function extractEvent(logs: Log[], parameters: { vault: Address }) {
525
+ const { vault } = parameters
526
+ // Earn contracts are user-deployed: several adapters can emit the same
527
+ // signature in one receipt, so filter by emitting address before decode.
528
+ const [log] = parseEventLogs({
529
+ abi: Abis.vaultAdapter,
530
+ eventName: 'Deposited',
531
+ logs: logs.filter((log) => isAddressEqual(log.address, vault)),
532
+ })
533
+ if (!log) throw new Error('`Deposited` event not found.')
534
+ return log
535
+ }
536
+
537
+ /**
538
+ * Estimates gas for a deposit, assuming the vault has enough asset allowance.
539
+ *
540
+ * @param client - Client.
541
+ * @param parameters - Parameters.
542
+ * @returns The gas estimate.
543
+ */
544
+ export async function estimateGas<
545
+ chain extends Chain | undefined,
546
+ account extends Account | undefined,
547
+ >(
548
+ client: Client<Transport, chain, account>,
549
+ parameters: deposit.Parameters<chain, account>,
550
+ ): Promise<bigint> {
551
+ return estimateContractGas(client, {
552
+ ...pickWriteParameters(parameters as never),
553
+ ...deposit.call(await toDepositArgs(client, parameters as never)),
554
+ } as never)
555
+ }
556
+
557
+ /**
558
+ * Simulates a deposit, assuming the vault has enough asset allowance.
559
+ *
560
+ * @param client - Client.
561
+ * @param parameters - Parameters.
562
+ * @returns The simulation result and write request.
563
+ */
564
+ export async function simulate<
565
+ chain extends Chain | undefined,
566
+ account extends Account | undefined,
567
+ >(
568
+ client: Client<Transport, chain, account>,
569
+ parameters: deposit.Parameters<chain, account>,
570
+ ): Promise<SimulateContractReturnType<typeof Abis.vaultAdapter, 'deposit'>> {
571
+ return simulateContract(client, {
572
+ ...pickWriteParameters(parameters as never),
573
+ ...deposit.call(await toDepositArgs(client, parameters as never)),
574
+ } as never) as never
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Deposits assets and returns the confirmed receipt and event data.
580
+ *
581
+ * @example
582
+ * ```ts
583
+ * import { createClient, http } from 'viem'
584
+ * import { privateKeyToAccount } from 'viem/accounts'
585
+ * import { tempoModerato } from 'viem/chains'
586
+ * import { Actions, EarnShares } from 'viem/tempo'
587
+ *
588
+ * const client = createClient({
589
+ * account: privateKeyToAccount('0x...'),
590
+ * chain: tempoModerato,
591
+ * transport: http(),
592
+ * })
593
+ *
594
+ * const { shareAmount } = await Actions.earn.depositSync(client, {
595
+ * assetAmount: 100_000_000n,
596
+ * shareAmountMin: EarnShares.minimumOutput(99_900_000n, 50),
597
+ * vault: '0x...',
598
+ * })
599
+ * ```
600
+ *
601
+ * @param client - Client.
602
+ * @param parameters - Parameters.
603
+ * @returns The transaction receipt and event data.
604
+ */
605
+ export async function depositSync<
606
+ chain extends Chain | undefined,
607
+ account extends Account | undefined,
608
+ >(
609
+ client: Client<Transport, chain, account>,
610
+ parameters: depositSync.Parameters<chain, account>,
611
+ ): Promise<depositSync.ReturnValue> {
612
+ const { throwOnReceiptRevert = true, vault } = parameters
613
+ const receipt = await deposit.inner(sendTransactionSync, client, {
614
+ ...parameters,
615
+ throwOnReceiptRevert,
616
+ } as never)
617
+ const { args } = deposit.extractEvent(receipt.logs, { vault })
618
+ return {
619
+ assetAmount: args.assets,
620
+ caller: args.caller,
621
+ receipt,
622
+ recipient: args.receiver,
623
+ shareAmount: args.shares,
624
+ }
625
+ }
626
+
627
+ export namespace depositSync {
628
+ export type Args = deposit.Args
629
+ export type Parameters<
630
+ chain extends Chain | undefined = Chain | undefined,
631
+ account extends Account | undefined = Account | undefined,
632
+ > = deposit.Parameters<chain, account> & WriteSyncParameters<chain, account>
633
+ export type ReturnValue = Compute<{
634
+ /** Assets deposited. */
635
+ assetAmount: bigint
636
+ /** Depositing caller. */
637
+ caller: Address
638
+ /** Transaction receipt. */
639
+ receipt: TransactionReceipt
640
+ /** Vault share recipient. */
641
+ recipient: Address
642
+ /** Vault shares minted. */
643
+ shareAmount: bigint
644
+ }>
645
+ // TODO: exhaustive error type
646
+ export type ErrorType = BaseErrorType
647
+ }
648
+
649
+ /**
650
+ * Deposits venue shares into a vault and mints vault shares to `recipient`.
651
+ * The transaction includes the required venue share approval.
652
+ *
653
+ * @example
654
+ * ```ts
655
+ * import { createClient, http } from 'viem'
656
+ * import { privateKeyToAccount } from 'viem/accounts'
657
+ * import { tempoModerato } from 'viem/chains'
658
+ * import { Actions } from 'viem/tempo'
659
+ *
660
+ * const client = createClient({
661
+ * account: privateKeyToAccount('0x...'),
662
+ * chain: tempoModerato,
663
+ * transport: http(),
664
+ * })
665
+ *
666
+ * const hash = await Actions.earn.depositShares(client, {
667
+ * earnShareAmount: 499_000_000n,
668
+ * slippageBps: 30,
669
+ * vault: '0x...',
670
+ * venueShareAmount: 500_000_000n,
671
+ * venueShareToken: '0x...',
672
+ * })
673
+ * ```
674
+ *
675
+ * @param client - Client.
676
+ * @param parameters - Parameters.
677
+ * @returns The transaction hash.
678
+ */
679
+ export async function depositShares<
680
+ chain extends Chain | undefined,
681
+ account extends Account | undefined,
682
+ >(
683
+ client: Client<Transport, chain, account>,
684
+ parameters: depositShares.Parameters<chain, account>,
685
+ ): Promise<depositShares.ReturnValue> {
686
+ return depositShares.inner(sendTransaction, client, parameters)
687
+ }
688
+
689
+ export namespace depositShares {
690
+ export type Args = {
691
+ /** Venue shares to deposit, base units. */
692
+ venueShareAmount: bigint
693
+ /** Vault share recipient. @default `account.address` */
694
+ recipient?: Address | undefined
695
+ /** Vault address. */
696
+ vault: Address
697
+ /** Venue share token approved for the deposit. */
698
+ venueShareToken: Address
699
+ } & OneOf<
700
+ | {
701
+ /** Minimum vault share output to accept; must be greater than zero. */
702
+ earnShareAmountMin: bigint
703
+ }
704
+ | {
705
+ /** Quoted vault share output; floored by `slippageBps`. */
706
+ earnShareAmount: bigint
707
+ /** Slippage tolerance in basis points under `earnShareAmount` (50 = 0.5%). */
708
+ slippageBps: number
709
+ }
710
+ >
711
+ export type Parameters<
712
+ chain extends Chain | undefined = Chain | undefined,
713
+ account extends Account | undefined = Account | undefined,
714
+ > = WriteParameters<chain, account> & Args
715
+ export type ReturnValue = SendTransactionReturnType
716
+ // TODO: exhaustive error type
717
+ export type ErrorType = BaseErrorType
718
+
719
+ /** @internal Shared dispatch; reads the engine for the approval. */
720
+ export async function inner<
721
+ action extends typeof sendTransaction | typeof sendTransactionSync,
722
+ chain extends Chain | undefined,
723
+ account extends Account | undefined,
724
+ >(
725
+ action: action,
726
+ client: Client<Transport, chain, account>,
727
+ parameters: depositShares.Parameters<chain, account>,
728
+ ): Promise<ReturnType<action>> {
729
+ const engine = await readContract(client, {
730
+ abi: Abis.vaultAdapter,
731
+ address: parameters.vault,
732
+ functionName: 'engine',
733
+ })
734
+ return (await action(client, {
735
+ ...parameters,
736
+ calls: depositShares.calls({
737
+ ...toDepositSharesArgs(client, parameters as never),
738
+ engine,
739
+ venueShareToken: parameters.venueShareToken,
740
+ }),
741
+ } as never)) as never
742
+ }
743
+
744
+ /**
745
+ * Defines a venue share deposit call without an approval. Provide an
746
+ * explicit output bound because this builder performs no reads.
747
+ *
748
+ * @param parameters - Client (optional), followed by the call arguments.
749
+ * @returns The call.
750
+ */
751
+ export function call<chain extends Chain | undefined>(
752
+ ...parameters: CallParameters<call.Args, Client<Transport, chain>>
753
+ ) {
754
+ const [, args] = resolveCallParameters(parameters)
755
+ const { recipient, vault, venueShareAmount } = args
756
+ const earnShareAmountMin = (() => {
757
+ if (args.earnShareAmountMin !== undefined) return args.earnShareAmountMin
758
+ return EarnShares.minimumOutput(args.earnShareAmount, args.slippageBps)
759
+ })()
760
+ return defineCall({
761
+ address: vault,
762
+ abi: Abis.vaultAdapter,
763
+ functionName: 'depositShares',
764
+ args: [venueShareAmount, recipient, earnShareAmountMin],
765
+ })
766
+ }
767
+ export namespace call {
768
+ export type Args = {
769
+ /** Venue shares to deposit, base units. */
770
+ venueShareAmount: bigint
771
+ /** Vault share recipient. */
772
+ recipient: Address
773
+ /** Vault address. */
774
+ vault: Address
775
+ } & OneOf<
776
+ | {
777
+ /** Minimum vault share output to accept. */
778
+ earnShareAmountMin: bigint
779
+ }
780
+ | {
781
+ /** Quoted vault share output; floored by `slippageBps`. */
782
+ earnShareAmount: bigint
783
+ /** Slippage tolerance in basis points under `earnShareAmount` (50 = 0.5%). */
784
+ slippageBps: number
785
+ }
786
+ >
787
+ }
788
+
789
+ /**
790
+ * Defines the venue share approval and deposit calls for atomic execution.
791
+ * Pass the vault's current `engine` and `venueShareToken` explicitly.
792
+ *
793
+ * @param args - Arguments.
794
+ * @returns The calls.
795
+ */
796
+ export function calls(
797
+ args: call.Args & {
798
+ /** Current vault engine that pulls the venue shares. */
799
+ engine: Address
800
+ /** Venue share token pulled by the engine. */
801
+ venueShareToken: Address
802
+ },
803
+ ) {
804
+ const { engine, venueShareAmount, venueShareToken } = args
805
+ return [
806
+ defineCall({
807
+ address: venueShareToken,
808
+ abi: Abis.tip20,
809
+ functionName: 'approve',
810
+ args: [engine, venueShareAmount],
811
+ }),
812
+ depositShares.call(args),
813
+ ]
814
+ }
815
+
816
+ /**
817
+ * Extracts a `VenueSharesDeposited` event from the vault's logs.
818
+ *
819
+ * @param logs - Logs.
820
+ * @param parameters - Parameters.
821
+ * @returns The `VenueSharesDeposited` event.
822
+ */
823
+ export function extractEvent(logs: Log[], parameters: { vault: Address }) {
824
+ const { vault } = parameters
825
+ // Earn contracts are user-deployed: several adapters can emit the same
826
+ // signature in one receipt, so filter by emitting address before decode.
827
+ const [log] = parseEventLogs({
828
+ abi: Abis.vaultAdapter,
829
+ eventName: 'VenueSharesDeposited',
830
+ logs: logs.filter((log) => isAddressEqual(log.address, vault)),
831
+ })
832
+ if (!log) throw new Error('`VenueSharesDeposited` event not found.')
833
+ return log
834
+ }
835
+
836
+ /**
837
+ * Estimates gas for a venue share deposit, assuming enough allowance.
838
+ *
839
+ * @param client - Client.
840
+ * @param parameters - Parameters.
841
+ * @returns The gas estimate.
842
+ */
843
+ export async function estimateGas<
844
+ chain extends Chain | undefined,
845
+ account extends Account | undefined,
846
+ >(
847
+ client: Client<Transport, chain, account>,
848
+ parameters: depositShares.Parameters<chain, account>,
849
+ ): Promise<bigint> {
850
+ return estimateContractGas(client, {
851
+ ...pickWriteParameters(parameters as never),
852
+ ...depositShares.call(toDepositSharesArgs(client, parameters as never)),
853
+ } as never)
854
+ }
855
+
856
+ /**
857
+ * Simulates a venue share deposit, assuming enough allowance.
858
+ *
859
+ * @param client - Client.
860
+ * @param parameters - Parameters.
861
+ * @returns The simulation result and write request.
862
+ */
863
+ export async function simulate<
864
+ chain extends Chain | undefined,
865
+ account extends Account | undefined,
866
+ >(
867
+ client: Client<Transport, chain, account>,
868
+ parameters: depositShares.Parameters<chain, account>,
869
+ ): Promise<
870
+ SimulateContractReturnType<typeof Abis.vaultAdapter, 'depositShares'>
871
+ > {
872
+ return simulateContract(client, {
873
+ ...pickWriteParameters(parameters as never),
874
+ ...depositShares.call(toDepositSharesArgs(client, parameters as never)),
875
+ } as never) as never
876
+ }
877
+ }
878
+
879
+ /**
880
+ * Deposits venue shares and returns the confirmed receipt and event data.
881
+ *
882
+ * @example
883
+ * ```ts
884
+ * import { createClient, http } from 'viem'
885
+ * import { privateKeyToAccount } from 'viem/accounts'
886
+ * import { tempoModerato } from 'viem/chains'
887
+ * import { Actions, EarnShares } from 'viem/tempo'
888
+ *
889
+ * const client = createClient({
890
+ * account: privateKeyToAccount('0x...'),
891
+ * chain: tempoModerato,
892
+ * transport: http(),
893
+ * })
894
+ *
895
+ * const { earnShareAmount } = await Actions.earn.depositSharesSync(client, {
896
+ * earnShareAmount: 499_000_000n,
897
+ * slippageBps: 30,
898
+ * vault: '0x...',
899
+ * venueShareAmount: 500_000_000n,
900
+ * venueShareToken: '0x...',
901
+ * })
902
+ * ```
903
+ *
904
+ * @param client - Client.
905
+ * @param parameters - Parameters.
906
+ * @returns The transaction receipt and event data.
907
+ */
908
+ export async function depositSharesSync<
909
+ chain extends Chain | undefined,
910
+ account extends Account | undefined,
911
+ >(
912
+ client: Client<Transport, chain, account>,
913
+ parameters: depositSharesSync.Parameters<chain, account>,
914
+ ): Promise<depositSharesSync.ReturnValue> {
915
+ const { throwOnReceiptRevert = true, vault } = parameters
916
+ const receipt = await depositShares.inner(sendTransactionSync, client, {
917
+ ...parameters,
918
+ throwOnReceiptRevert,
919
+ } as never)
920
+ const { args } = depositShares.extractEvent(receipt.logs, { vault })
921
+ return {
922
+ caller: args.caller,
923
+ earnShareAmount: args.earnShares,
924
+ receipt,
925
+ receivedVenueShareAmount: args.receivedVenueShares,
926
+ recipient: args.receiver,
927
+ venueShareAmount: args.requestedVenueShares,
928
+ }
929
+ }
930
+
931
+ export namespace depositSharesSync {
932
+ export type Args = depositShares.Args
933
+ export type Parameters<
934
+ chain extends Chain | undefined = Chain | undefined,
935
+ account extends Account | undefined = Account | undefined,
936
+ > = depositShares.Parameters<chain, account> &
937
+ WriteSyncParameters<chain, account>
938
+ export type ReturnValue = Compute<{
939
+ /** Depositing caller. */
940
+ caller: Address
941
+ /** Vault shares minted. */
942
+ earnShareAmount: bigint
943
+ /** Transaction receipt. */
944
+ receipt: TransactionReceipt
945
+ /** Venue shares measured as received by the engine. */
946
+ receivedVenueShareAmount: bigint
947
+ /** Vault share recipient. */
948
+ recipient: Address
949
+ /** Venue shares requested for pull. */
950
+ venueShareAmount: bigint
951
+ }>
952
+ // TODO: exhaustive error type
953
+ export type ErrorType = BaseErrorType
954
+ }
955
+
956
+ /**
957
+ * Withdraws assets from a Zone and deposits them into a vault on the parent
958
+ * chain. Use {@link privateDeposit.prepare} to build the encrypted callback.
959
+ *
960
+ * @example
961
+ * ```ts
962
+ * const prepared = await Actions.earn.privateDeposit.prepare(parentClient, {
963
+ * assetAmount: 100_000_000n,
964
+ * assetToken: '0x...',
965
+ * gateway: '0x...',
966
+ * recipient: '0x...',
967
+ * recoveryRecipient: '0x...',
968
+ * shareAmountMin: 99_500_000n,
969
+ * vaultAssetAmountMin: 99_000_000n,
970
+ * })
971
+ * const hash = await Actions.earn.privateDeposit(zoneClient, prepared)
972
+ * ```
973
+ *
974
+ * @param client - Zone client.
975
+ * @param parameters - Prepared deposit and transaction parameters.
976
+ * @returns The transaction hash.
977
+ */
978
+ export async function privateDeposit<
979
+ chain extends Chain | undefined,
980
+ account extends Account | undefined,
981
+ >(
982
+ client: Client<Transport, chain, account>,
983
+ parameters: privateDeposit.Parameters<chain, account>,
984
+ ): Promise<privateDeposit.ReturnValue> {
985
+ await assertPreparedZoneRequestChain(client, parameters)
986
+ return zoneActions.requestWithdrawal(client, parameters)
987
+ }
988
+
989
+ export namespace privateDeposit {
990
+ export type Args = prepare.ReturnValue
991
+ export type Parameters<
992
+ chain extends Chain | undefined = Chain | undefined,
993
+ account extends Account | undefined = Account | undefined,
994
+ > = WriteParameters<chain, account> & Args
995
+ export type ReturnValue = SendTransactionReturnType
996
+ export type ErrorType = zoneActions.requestWithdrawal.ErrorType
997
+
998
+ /**
999
+ * Builds an encrypted Zone withdrawal that deposits into the gateway's
1000
+ * vault and returns the resulting shares to the Zone.
1001
+ *
1002
+ * @param client - Parent-chain client.
1003
+ * @param parameters - Deposit intent and recovery parameters.
1004
+ * @returns The prepared withdrawal and correlation data.
1005
+ */
1006
+ export async function prepare<chain extends Chain | undefined>(
1007
+ client: Client<Transport, chain>,
1008
+ parameters: prepare.Parameters,
1009
+ ): Promise<prepare.ReturnValue> {
1010
+ const chainId = client.chain?.id
1011
+ if (!chainId) throw new Error('`chain` is required.')
1012
+ const {
1013
+ actionId = Hex.random(32),
1014
+ assetAmount,
1015
+ callbackGas = zoneGatewayCallbackGas,
1016
+ fallbackRecipient = parameters.recoveryRecipient,
1017
+ gateway,
1018
+ recipient,
1019
+ recoveryRecipient,
1020
+ returnMemo,
1021
+ withdrawalMemo,
1022
+ } = parameters
1023
+ const readParameters = pickReadParameters(parameters)
1024
+ const [fromBlock, config] = await Promise.all([
1025
+ getBlockNumber(client, { cacheTime: 0 }),
1026
+ getZoneGatewayConfig(client, { ...readParameters, gateway }),
1027
+ ])
1028
+ const assetToken = parameters.assetToken ?? config.vaultAsset
1029
+ const { encrypted, keyIndex } =
1030
+ await zoneActions.encryptedDeposit.prepareRecipient(client, {
1031
+ ...readParameters,
1032
+ memo: returnMemo,
1033
+ portalAddress: config.zonePortal,
1034
+ recipient,
1035
+ zoneId: config.zoneId,
1036
+ })
1037
+ const shareAmountMin = resolveMinimumShareAmount(parameters)
1038
+ const direct = isAddressEqual(assetToken, config.vaultAsset)
1039
+ const data = encodeAbiParameters(Abis.zoneGatewayCallbackData, [
1040
+ {
1041
+ flow: 0,
1042
+ outputToken: config.shareToken,
1043
+ keyIndex,
1044
+ encrypted,
1045
+ minVaultAssets: direct
1046
+ ? assetAmount
1047
+ : (parameters.vaultAssetAmountMin ?? 0n),
1048
+ minVaultShares: shareAmountMin,
1049
+ minOutputAmount: 0n,
1050
+ actionId,
1051
+ refundRecipient: recoveryRecipient,
1052
+ },
1053
+ ])
1054
+ return {
1055
+ actionId,
1056
+ amount: assetAmount,
1057
+ callbackGas,
1058
+ chainId,
1059
+ data,
1060
+ fallbackRecipient,
1061
+ fromBlock,
1062
+ memo: withdrawalMemo,
1063
+ to: gateway,
1064
+ token: assetToken,
1065
+ zoneId: config.zoneId,
1066
+ }
1067
+ }
1068
+
1069
+ export namespace prepare {
1070
+ export type Parameters = Omit<ReadParameters, 'account'> & Args
1071
+ export type Args = PrivatePreparationParameters & {
1072
+ /** Assets withdrawn from the Zone, base units. */
1073
+ assetAmount: bigint
1074
+ /** Asset token withdrawn from the Zone. @default gateway vault asset */
1075
+ assetToken?: Address | undefined
1076
+ /** Minimum gateway vault assets accepted after swapping `assetToken`. @default `0n` */
1077
+ vaultAssetAmountMin?: bigint | undefined
1078
+ } & MinimumShareAmountParameters
1079
+ export type ReturnValue = PreparedZoneRequest
1080
+ export type ErrorType = BaseErrorType
1081
+ }
1082
+
1083
+ /**
1084
+ * Defines the approval and Zone withdrawal calls for a prepared deposit.
1085
+ *
1086
+ * @param args - Prepared deposit arguments.
1087
+ * @returns The Zone withdrawal calls.
1088
+ */
1089
+ export function calls(args: Args) {
1090
+ return zoneActions.requestWithdrawal.calls(args)
1091
+ }
1092
+ }
1093
+
1094
+ /**
1095
+ * Requests a private Zone deposit and waits for the Zone transaction receipt.
1096
+ * The receipt confirms withdrawal acceptance, not the parent-chain deposit.
1097
+ *
1098
+ * @param client - Zone client.
1099
+ * @param parameters - Prepared deposit and transaction parameters.
1100
+ * @returns The Zone transaction receipt and parent-chain withdrawal sender tag.
1101
+ */
1102
+ export async function privateDepositSync<
1103
+ chain extends Chain | undefined,
1104
+ account extends Account | undefined,
1105
+ >(
1106
+ client: Client<Transport, chain, account>,
1107
+ parameters: privateDepositSync.Parameters<chain, account>,
1108
+ ): Promise<privateDepositSync.ReturnValue> {
1109
+ await assertPreparedZoneRequestChain(client, parameters)
1110
+ return zoneActions.requestWithdrawalSync(client, parameters)
1111
+ }
1112
+
1113
+ export namespace privateDepositSync {
1114
+ export type Args = privateDeposit.Args
1115
+ export type Parameters<
1116
+ chain extends Chain | undefined = Chain | undefined,
1117
+ account extends Account | undefined = Account | undefined,
1118
+ > = privateDeposit.Parameters<chain, account> &
1119
+ WriteSyncParameters<chain, account>
1120
+ export type ReturnValue = zoneActions.requestWithdrawalSync.ReturnValue
1121
+ export type ErrorType = zoneActions.requestWithdrawalSync.ErrorType
1122
+ }
1123
+
1124
+ /**
1125
+ * Waits for a Zone gateway deposit to complete on the parent chain.
1126
+ *
1127
+ * @example
1128
+ * ```ts
1129
+ * const result = await Actions.earn.waitForPrivateDeposit(parentClient, {
1130
+ * actionId: prepared.actionId,
1131
+ * fromBlock: prepared.fromBlock,
1132
+ * gateway: '0x...',
1133
+ * })
1134
+ * ```
1135
+ *
1136
+ * @param client - Parent-chain client.
1137
+ * @param parameters - Prepared action correlation and polling parameters.
1138
+ * @returns The completed gateway deposit.
1139
+ */
1140
+ export async function waitForPrivateDeposit<chain extends Chain | undefined>(
1141
+ client: Client<Transport, chain>,
1142
+ parameters: waitForPrivateDeposit.Parameters,
1143
+ ): Promise<waitForPrivateDeposit.ReturnType> {
1144
+ const {
1145
+ actionId,
1146
+ fromBlock,
1147
+ gateway,
1148
+ pollingInterval = client.pollingInterval,
1149
+ timeout = 60_000,
1150
+ } = parameters
1151
+ const event = getAbiItem({
1152
+ abi: Abis.zoneGateway,
1153
+ name: 'EarnDeposit',
1154
+ })
1155
+ const observerId = stringify([
1156
+ 'waitForPrivateDeposit',
1157
+ client.uid,
1158
+ gateway,
1159
+ actionId,
1160
+ fromBlock,
1161
+ ])
1162
+ const { promise, reject, resolve } =
1163
+ withResolvers<waitForPrivateDeposit.ReturnType>()
1164
+
1165
+ let timer: ReturnType<typeof setTimeout> | undefined
1166
+ let unobserve: () => void
1167
+ const cleanup = () => {
1168
+ clearTimeout(timer)
1169
+ unobserve()
1170
+ }
1171
+ const resolve_ = (result: waitForPrivateDeposit.ReturnType) => {
1172
+ cleanup()
1173
+ resolve(result)
1174
+ }
1175
+ const reject_ = (error: unknown) => {
1176
+ cleanup()
1177
+ reject(error)
1178
+ }
1179
+
1180
+ unobserve = observe(
1181
+ observerId,
1182
+ { reject: reject_, resolve: resolve_ },
1183
+ (emit) => {
1184
+ const unpoll = poll(
1185
+ async () => {
1186
+ try {
1187
+ const [log] = await getLogs(client, {
1188
+ address: gateway,
1189
+ args: { actionId },
1190
+ event,
1191
+ fromBlock,
1192
+ strict: true,
1193
+ toBlock: 'latest',
1194
+ })
1195
+ if (!log) return
1196
+ unpoll()
1197
+ emit.resolve({
1198
+ actionId: log.args.actionId,
1199
+ inputAmount: log.args.inputAmount,
1200
+ inputToken: log.args.inputToken,
1201
+ shares: log.args.shares,
1202
+ tempoBlockNumber: log.blockNumber,
1203
+ vaultAssets: log.args.vaultAssets,
1204
+ zoneDepositHash: log.args.zoneDepositHash,
1205
+ })
1206
+ } catch (error) {
1207
+ unpoll()
1208
+ emit.reject(error)
1209
+ }
1210
+ },
1211
+ { emitOnBegin: true, interval: pollingInterval },
1212
+ )
1213
+
1214
+ return unpoll
1215
+ },
1216
+ )
1217
+
1218
+ timer = timeout
1219
+ ? setTimeout(() => {
1220
+ reject_(new WaitForPrivateDepositTimeoutError({ actionId, gateway }))
1221
+ }, timeout)
1222
+ : undefined
1223
+
1224
+ return await promise
1225
+ }
1226
+
1227
+ export namespace waitForPrivateDeposit {
1228
+ export type Parameters = {
1229
+ /** Correlation id from {@link privateDeposit.prepare}. */
1230
+ actionId: Hex.Hex
1231
+ /** Lower bound for the parent-chain log scan. */
1232
+ fromBlock: bigint
1233
+ /** Zone gateway address. */
1234
+ gateway: Address
1235
+ /** Polling frequency in milliseconds. @default `client.pollingInterval` */
1236
+ pollingInterval?: number | undefined
1237
+ /** Timeout in milliseconds; `0` disables it. @default `60_000` */
1238
+ timeout?: number | undefined
1239
+ }
1240
+ export type ReturnType = {
1241
+ /** Correlation id for the completed deposit. */
1242
+ actionId: Hex.Hex
1243
+ /** Tokens delivered to the gateway, base units. */
1244
+ inputAmount: bigint
1245
+ /** Token delivered to the gateway. */
1246
+ inputToken: Address
1247
+ /** Vault shares returned to the Zone. */
1248
+ shares: bigint
1249
+ /** Parent-chain block containing the gateway event. */
1250
+ tempoBlockNumber: bigint
1251
+ /** Vault assets deposited after any swap. */
1252
+ vaultAssets: bigint
1253
+ /** Encrypted return deposit hash. */
1254
+ zoneDepositHash: Hex.Hex
1255
+ }
1256
+ export type ErrorType =
1257
+ | GetLogsErrorType
1258
+ | ObserveErrorType
1259
+ | PollErrorType
1260
+ | WaitForPrivateDepositTimeoutErrorType
1261
+ | BaseErrorType
1262
+ }
1263
+
1264
+ /**
1265
+ * Gets the vault's active fee configuration, pending fees, and fee baselines.
1266
+ *
1267
+ * @example
1268
+ * ```ts
1269
+ * import { createClient, http } from 'viem'
1270
+ * import { tempoModerato } from 'viem/chains'
1271
+ * import { Actions } from 'viem/tempo'
1272
+ *
1273
+ * const client = createClient({
1274
+ * chain: tempoModerato,
1275
+ * transport: http(),
1276
+ * })
1277
+ *
1278
+ * const feeState = await Actions.earn.getFeeState(client, {
1279
+ * vault: '0x...',
1280
+ * })
1281
+ * ```
1282
+ *
1283
+ * @param client - Client.
1284
+ * @param parameters - Parameters.
1285
+ * @returns The active fee configuration, pending fees, and baselines.
1286
+ */
1287
+ export async function getFeeState<chain extends Chain | undefined>(
1288
+ client: Client<Transport, chain>,
1289
+ parameters: getFeeState.Parameters,
1290
+ ): Promise<getFeeState.ReturnValue> {
1291
+ const { recipient, vault, ...rest } = parameters
1292
+ const contracts = [
1293
+ defineCall({
1294
+ address: vault,
1295
+ abi: Abis.vaultAdapter,
1296
+ functionName: 'currentFeeConfigId',
1297
+ }),
1298
+ defineCall({
1299
+ address: vault,
1300
+ abi: Abis.vaultAdapter,
1301
+ functionName: 'feesActive',
1302
+ }),
1303
+ defineCall({
1304
+ address: vault,
1305
+ abi: Abis.vaultAdapter,
1306
+ functionName: 'highWaterMark',
1307
+ }),
1308
+ defineCall({
1309
+ address: vault,
1310
+ abi: Abis.vaultAdapter,
1311
+ functionName: 'previewAccruedFees',
1312
+ }),
1313
+ defineCall({
1314
+ address: vault,
1315
+ abi: Abis.vaultAdapter,
1316
+ functionName: 'targetBase',
1317
+ }),
1318
+ ] as const
1319
+ // Stored configs are immutable per id, so a follow-up `feeConfig` read stays
1320
+ // consistent with the batched id.
1321
+ const feeConfig = async (configId: bigint) =>
1322
+ toFeeConfig(
1323
+ await readContract(client, {
1324
+ ...rest,
1325
+ abi: Abis.vaultAdapter,
1326
+ address: vault,
1327
+ functionName: 'feeConfig',
1328
+ args: [configId],
1329
+ }),
1330
+ )
1331
+ if (recipient !== undefined) {
1332
+ const [configId, feesActive, highWaterMark, preview, targetBase, shares] =
1333
+ await multicall(client, {
1334
+ ...rest,
1335
+ allowFailure: false,
1336
+ contracts: [
1337
+ ...contracts,
1338
+ defineCall({
1339
+ address: vault,
1340
+ abi: Abis.vaultAdapter,
1341
+ functionName: 'claimableFeeShares',
1342
+ args: [recipient],
1343
+ }),
1344
+ ],
1345
+ deployless: true,
1346
+ })
1347
+ return {
1348
+ claimableShares: shares,
1349
+ config: await feeConfig(configId),
1350
+ configId,
1351
+ feesActive,
1352
+ highWaterMark,
1353
+ preview: toFeePreview(preview),
1354
+ targetBase,
1355
+ }
1356
+ }
1357
+ const [configId, feesActive, highWaterMark, preview, targetBase] =
1358
+ await multicall(client, {
1359
+ ...rest,
1360
+ allowFailure: false,
1361
+ contracts,
1362
+ deployless: true,
1363
+ })
1364
+ return {
1365
+ config: await feeConfig(configId),
1366
+ configId,
1367
+ feesActive,
1368
+ highWaterMark,
1369
+ preview: toFeePreview(preview),
1370
+ targetBase,
1371
+ }
1372
+ }
1373
+
1374
+ export namespace getFeeState {
1375
+ export type Args = {
1376
+ /** Optional fee recipient whose claimable vault shares are included. */
1377
+ recipient?: Address | undefined
1378
+ /** Vault address. */
1379
+ vault: Address
1380
+ }
1381
+ export type Parameters = Omit<ReadParameters, 'account'> & Args
1382
+ export type ReturnValue = {
1383
+ /** Claimable fee shares for `recipient`; present when provided. */
1384
+ claimableShares?: bigint | undefined
1385
+ /** Active fee configuration. */
1386
+ config: FeeConfig
1387
+ /** Active fee configuration id (starts at `1`). */
1388
+ configId: bigint
1389
+ /** Whether fees are configured and not emergency-disabled. */
1390
+ feesActive: boolean
1391
+ /** Post-fee high-water mark per vault share. */
1392
+ highWaterMark: bigint
1393
+ /** Pending fee amounts. */
1394
+ preview: FeePreview
1395
+ /** Excess-return fee target per vault share. */
1396
+ targetBase: bigint
1397
+ }
1398
+ // TODO: exhaustive error type
1399
+ export type ErrorType = BaseErrorType
1400
+ }
1401
+
1402
+ /** Vault fee configuration. */
1403
+ export type FeeConfig = {
1404
+ /** Optional excess-return fee over a growing target line. */
1405
+ excess: {
1406
+ /** Excess fee recipient. */
1407
+ account: Address
1408
+ /** Annual target growth rate, scaled to 18 decimals. */
1409
+ annualTargetRate: bigint
1410
+ /** Whether the excess fee is active. */
1411
+ enabled: boolean
1412
+ /** Rate applied above the target, scaled to 18 decimals. */
1413
+ excessFeeRate: bigint
1414
+ }
1415
+ /** Fixed fee recipients and their 18-decimal rates. */
1416
+ fixedFees: readonly { account: Address; rate: bigint }[]
1417
+ }
1418
+
1419
+ /** Pending vault fee amounts. */
1420
+ export type FeePreview = {
1421
+ /** Assets backing active vault shares. */
1422
+ activeAssets: bigint
1423
+ /** Fee allocations in assets and vault shares. */
1424
+ allocations: readonly {
1425
+ account: Address
1426
+ feeAssets: bigint
1427
+ feeShares: bigint
1428
+ }[]
1429
+ /** Excess-return fee portion, asset units. */
1430
+ excessFeeAssets: bigint
1431
+ /** Fixed fee portion, asset units. */
1432
+ fixedFeeAssets: bigint
1433
+ /** Accrual above the high-water mark, asset units. */
1434
+ positiveAccrualAssets: bigint
1435
+ /** Scaled asset value per vault share after fees. */
1436
+ postFeeValuePerShare: bigint
1437
+ /** Scaled asset value per vault share before fees. */
1438
+ preFeeValuePerShare: bigint
1439
+ /** Scaled excess-fee target per vault share. */
1440
+ targetValuePerShare: bigint
1441
+ /** Total fee liability, asset units. */
1442
+ totalFeeAssets: bigint
1443
+ /** Vault shares minted to cover the total fee. */
1444
+ totalFeeShares: bigint
1445
+ }
1446
+
1447
+ /**
1448
+ * Gets an account's asset and vault share balances, allowances, and current
1449
+ * share value. The value includes fees.
1450
+ *
1451
+ * @example
1452
+ * ```ts
1453
+ * import { createClient, http } from 'viem'
1454
+ * import { tempoModerato } from 'viem/chains'
1455
+ * import { Actions } from 'viem/tempo'
1456
+ *
1457
+ * const client = createClient({
1458
+ * chain: tempoModerato,
1459
+ * transport: http(),
1460
+ * })
1461
+ *
1462
+ * const position = await Actions.earn.getPosition(client, {
1463
+ * account: '0x...',
1464
+ * vault: '0x...',
1465
+ * })
1466
+ * ```
1467
+ *
1468
+ * @param client - Client.
1469
+ * @param parameters - Parameters.
1470
+ * @returns The asset and vault share balances, allowances, and value.
1471
+ */
1472
+ export async function getPosition<
1473
+ chain extends Chain | undefined,
1474
+ account extends Account | undefined,
1475
+ >(
1476
+ client: Client<Transport, chain, account>,
1477
+ parameters: getPosition.Parameters<account>,
1478
+ ): Promise<getPosition.ReturnValue> {
1479
+ const { account: account_ = client.account, vault, ...rest } = parameters
1480
+ if (!account_) throw new AccountNotFoundError()
1481
+ const account = parseAccount(account_).address
1482
+ const [assetToken, shareToken] = await multicall(client, {
1483
+ ...rest,
1484
+ allowFailure: false,
1485
+ contracts: [
1486
+ defineCall({
1487
+ address: vault,
1488
+ abi: Abis.vaultAdapter,
1489
+ functionName: 'asset',
1490
+ }),
1491
+ defineCall({
1492
+ address: vault,
1493
+ abi: Abis.vaultAdapter,
1494
+ functionName: 'shareToken',
1495
+ }),
1496
+ ],
1497
+ deployless: true,
1498
+ })
1499
+ const [assetAllowance, assetBalance, shareAllowance, shareBalance] =
1500
+ await multicall(client, {
1501
+ ...rest,
1502
+ allowFailure: false,
1503
+ contracts: [
1504
+ defineCall({
1505
+ address: assetToken,
1506
+ abi: Abis.tip20,
1507
+ functionName: 'allowance',
1508
+ args: [account, vault],
1509
+ }),
1510
+ defineCall({
1511
+ address: assetToken,
1512
+ abi: Abis.tip20,
1513
+ functionName: 'balanceOf',
1514
+ args: [account],
1515
+ }),
1516
+ defineCall({
1517
+ address: shareToken,
1518
+ abi: Abis.tip20,
1519
+ functionName: 'allowance',
1520
+ args: [account, vault],
1521
+ }),
1522
+ defineCall({
1523
+ address: shareToken,
1524
+ abi: Abis.tip20,
1525
+ functionName: 'balanceOf',
1526
+ args: [account],
1527
+ }),
1528
+ ],
1529
+ deployless: true,
1530
+ })
1531
+ const value = await readContract(client, {
1532
+ ...rest,
1533
+ abi: Abis.vaultAdapter,
1534
+ address: vault,
1535
+ args: [shareBalance],
1536
+ functionName: 'previewRedeem',
1537
+ })
1538
+ return {
1539
+ assetAllowance,
1540
+ assetBalance,
1541
+ assetToken,
1542
+ shareAllowance,
1543
+ shareBalance,
1544
+ shareToken,
1545
+ value,
1546
+ }
1547
+ }
1548
+
1549
+ export namespace getPosition {
1550
+ export type Args<account extends Account | undefined = Account | undefined> =
1551
+ GetAccountParameter<account> & {
1552
+ /** Vault address. */
1553
+ vault: Address
1554
+ }
1555
+ export type Parameters<
1556
+ account extends Account | undefined = Account | undefined,
1557
+ > = Omit<ReadParameters, 'account'> & Args<account>
1558
+ export type ReturnValue = {
1559
+ /** Assets the vault may spend from the account. */
1560
+ assetAllowance: bigint
1561
+ /** Asset balance held by the account. */
1562
+ assetBalance: bigint
1563
+ /** Token accepted by the vault. */
1564
+ assetToken: Address
1565
+ /** Vault shares the vault may spend from the account. */
1566
+ shareAllowance: bigint
1567
+ /** Vault share balance held by the account. */
1568
+ shareBalance: bigint
1569
+ /** Token representing vault shares. */
1570
+ shareToken: Address
1571
+ /** Current asset value of the vault share balance, including fees. */
1572
+ value: bigint
1573
+ }
1574
+ // TODO: exhaustive error type
1575
+ export type ErrorType = BaseErrorType
1576
+ }
1577
+
1578
+ /**
1579
+ * Gets the asset output for an exact vault share input, including fees.
1580
+ *
1581
+ * @example
1582
+ * ```ts
1583
+ * import { createClient, http } from 'viem'
1584
+ * import { tempoModerato } from 'viem/chains'
1585
+ * import { Actions } from 'viem/tempo'
1586
+ *
1587
+ * const client = createClient({
1588
+ * chain: tempoModerato,
1589
+ * transport: http(),
1590
+ * })
1591
+ *
1592
+ * const assetAmount = await Actions.earn.getRedeemQuote(client, {
1593
+ * shareAmount: 100_000_000n,
1594
+ * vault: '0x...',
1595
+ * })
1596
+ * ```
1597
+ *
1598
+ * @param client - Client.
1599
+ * @param parameters - Parameters.
1600
+ * @returns The asset output, including fees.
1601
+ */
1602
+ export async function getRedeemQuote<chain extends Chain | undefined>(
1603
+ client: Client<Transport, chain>,
1604
+ parameters: getRedeemQuote.Parameters,
1605
+ ): Promise<getRedeemQuote.ReturnValue> {
1606
+ const { shareAmount, vault, ...rest } = parameters
1607
+ return readContract(client, {
1608
+ ...rest,
1609
+ ...getRedeemQuote.call({ shareAmount, vault }),
1610
+ })
1611
+ }
1612
+
1613
+ export namespace getRedeemQuote {
1614
+ export type Args = {
1615
+ /** Exact vault share input, base units. */
1616
+ shareAmount: bigint
1617
+ /** Vault address. */
1618
+ vault: Address
1619
+ }
1620
+ export type Parameters = Omit<ReadParameters, 'account'> & Args
1621
+ /** Asset output, including fees. */
1622
+ export type ReturnValue = ReadContractReturnType<
1623
+ typeof Abis.vaultAdapter,
1624
+ 'previewRedeem',
1625
+ never
1626
+ >
1627
+ // TODO: exhaustive error type
1628
+ export type ErrorType = BaseErrorType
1629
+
1630
+ /**
1631
+ * Defines a call to the vault's `previewRedeem` function.
1632
+ *
1633
+ * Can be passed as a parameter to:
1634
+ * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
1635
+ * - [`multicall`](https://viem.sh/docs/contract/multicall): batch the call with other contract reads
1636
+ * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
1637
+ *
1638
+ * @example
1639
+ * ```ts
1640
+ * import { Actions } from 'viem/tempo'
1641
+ *
1642
+ * const call = Actions.earn.getRedeemQuote.call({
1643
+ * shareAmount: 100_000_000n,
1644
+ * vault: '0x...',
1645
+ * })
1646
+ * ```
1647
+ *
1648
+ * @param args - Arguments.
1649
+ * @returns The call.
1650
+ */
1651
+ export function call(args: Args) {
1652
+ const { shareAmount, vault } = args
1653
+ return defineCall({
1654
+ address: vault,
1655
+ abi: Abis.vaultAdapter,
1656
+ args: [shareAmount],
1657
+ functionName: 'previewRedeem',
1658
+ })
1659
+ }
1660
+ }
1661
+
1662
+ /**
1663
+ * Gets the vault's addresses, configuration, accounting state, and supported
1664
+ * actions. Throws {@link GetVaultEngineChangedError} if its engine changes mid-read.
1665
+ *
1666
+ * @example
1667
+ * ```ts
1668
+ * import { createClient, http } from 'viem'
1669
+ * import { tempoModerato } from 'viem/chains'
1670
+ * import { Actions } from 'viem/tempo'
1671
+ *
1672
+ * const client = createClient({
1673
+ * chain: tempoModerato,
1674
+ * transport: http(),
1675
+ * })
1676
+ *
1677
+ * const vault = await Actions.earn.getVault(client, {
1678
+ * vault: '0x...',
1679
+ * })
1680
+ * ```
1681
+ *
1682
+ * @param client - Client.
1683
+ * @param parameters - Parameters.
1684
+ * @returns The vault state and metadata.
1685
+ */
1686
+ export async function getVault<chain extends Chain | undefined>(
1687
+ client: Client<Transport, chain>,
1688
+ parameters: getVault.Parameters,
1689
+ ): Promise<getVault.ReturnValue> {
1690
+ const { vault, ...rest } = parameters
1691
+ const engine = await readContract(client, {
1692
+ ...rest,
1693
+ abi: Abis.vaultAdapter,
1694
+ address: vault,
1695
+ functionName: 'engine',
1696
+ })
1697
+ const [
1698
+ assetToken,
1699
+ engine_,
1700
+ shareToken,
1701
+ operator,
1702
+ emergencyGuardian,
1703
+ asyncJanitor,
1704
+ engineMigrationMode,
1705
+ depositsPaused,
1706
+ engineShares,
1707
+ shareSupply,
1708
+ isSynced,
1709
+ pendingRedeemCount,
1710
+ feesActive,
1711
+ totalAssets,
1712
+ name,
1713
+ symbol,
1714
+ asyncRedeem,
1715
+ exactWithdraw,
1716
+ inKindDeposit,
1717
+ syncRedeem,
1718
+ ] = await multicall(client, {
1719
+ ...rest,
1720
+ allowFailure: false,
1721
+ contracts: getVault.calls({ engine, vault }),
1722
+ deployless: true,
1723
+ })
1724
+ if (!isAddressEqual(engine, engine_))
1725
+ throw new GetVaultEngineChangedError({ vault })
1726
+ return {
1727
+ assetToken,
1728
+ asyncJanitor,
1729
+ capabilities: { asyncRedeem, exactWithdraw, inKindDeposit, syncRedeem },
1730
+ depositsPaused,
1731
+ emergencyGuardian,
1732
+ engine: { address: engine_, name, symbol, totalAssets },
1733
+ // `EngineMigrationMode`: 0 = UserOnly, 1 = OperatorEnabled.
1734
+ engineMigrationMode:
1735
+ engineMigrationMode === 0 ? 'userOnly' : 'operatorEnabled',
1736
+ engineShares,
1737
+ feesActive,
1738
+ isSynced,
1739
+ operator,
1740
+ pendingRedeemCount,
1741
+ shareSupply,
1742
+ shareToken,
1743
+ }
1744
+ }
1745
+
1746
+ export namespace getVault {
1747
+ export type Args = {
1748
+ /** Vault address. */
1749
+ vault: Address
1750
+ }
1751
+ export type Parameters = Omit<ReadParameters, 'account'> & Args
1752
+ export type ReturnValue = {
1753
+ /** Token accepted by the vault. */
1754
+ assetToken: Address
1755
+ /** Address allowed to cancel queued redemptions; zero when disabled. */
1756
+ asyncJanitor: Address
1757
+ /** Actions supported by the current venue integration. */
1758
+ capabilities: {
1759
+ /** Queued redemptions. */
1760
+ asyncRedeem: boolean
1761
+ /** Exact asset withdrawals. */
1762
+ exactWithdraw: boolean
1763
+ /** Venue share deposits. */
1764
+ inKindDeposit: boolean
1765
+ /** Immediate redemptions. */
1766
+ syncRedeem: boolean
1767
+ }
1768
+ /** Whether new deposits are paused. */
1769
+ depositsPaused: boolean
1770
+ /** Address allowed to pause deposits; zero when disabled. */
1771
+ emergencyGuardian: Address
1772
+ /** Current venue integration. */
1773
+ engine: {
1774
+ /** Integration address. */
1775
+ address: Address
1776
+ /** Engine display name. */
1777
+ name: string
1778
+ /** Engine display symbol. */
1779
+ symbol: string
1780
+ /** Asset value of the active backing. */
1781
+ totalAssets: bigint
1782
+ }
1783
+ /** Whether only users may change the venue integration. */
1784
+ engineMigrationMode: 'operatorEnabled' | 'userOnly'
1785
+ /** Venue shares held for the vault. */
1786
+ engineShares: bigint
1787
+ /** Whether fees are configured and not emergency-disabled. */
1788
+ feesActive: boolean
1789
+ /** Whether vault share supply matches its asset backing. */
1790
+ isSynced: boolean
1791
+ /** Vault governance address. */
1792
+ operator: Address
1793
+ /** Open queued redemptions. */
1794
+ pendingRedeemCount: bigint
1795
+ /** Active vault share supply. */
1796
+ shareSupply: bigint
1797
+ /** Token representing vault shares. */
1798
+ shareToken: Address
1799
+ }
1800
+ // TODO: exhaustive error type
1801
+ export type ErrorType = GetVaultEngineChangedErrorType | BaseErrorType
1802
+
1803
+ /**
1804
+ * Defines the reads used by {@link getVault}. Pass the vault's current
1805
+ * engine address.
1806
+ *
1807
+ * @param args - Arguments.
1808
+ * @returns The calls.
1809
+ */
1810
+ export function calls(args: Args & { engine: Address }) {
1811
+ const { engine, vault } = args
1812
+ return [
1813
+ defineCall({
1814
+ address: vault,
1815
+ abi: Abis.vaultAdapter,
1816
+ functionName: 'asset',
1817
+ }),
1818
+ defineCall({
1819
+ address: vault,
1820
+ abi: Abis.vaultAdapter,
1821
+ functionName: 'engine',
1822
+ }),
1823
+ defineCall({
1824
+ address: vault,
1825
+ abi: Abis.vaultAdapter,
1826
+ functionName: 'shareToken',
1827
+ }),
1828
+ defineCall({
1829
+ address: vault,
1830
+ abi: Abis.vaultAdapter,
1831
+ functionName: 'operator',
1832
+ }),
1833
+ defineCall({
1834
+ address: vault,
1835
+ abi: Abis.vaultAdapter,
1836
+ functionName: 'emergencyGuardian',
1837
+ }),
1838
+ defineCall({
1839
+ address: vault,
1840
+ abi: Abis.vaultAdapter,
1841
+ functionName: 'asyncJanitor',
1842
+ }),
1843
+ defineCall({
1844
+ address: vault,
1845
+ abi: Abis.vaultAdapter,
1846
+ functionName: 'engineMigrationMode',
1847
+ }),
1848
+ defineCall({
1849
+ address: vault,
1850
+ abi: Abis.vaultAdapter,
1851
+ functionName: 'depositsPaused',
1852
+ }),
1853
+ defineCall({
1854
+ address: vault,
1855
+ abi: Abis.vaultAdapter,
1856
+ functionName: 'engineShares',
1857
+ }),
1858
+ defineCall({
1859
+ address: vault,
1860
+ abi: Abis.vaultAdapter,
1861
+ functionName: 'shareSupply',
1862
+ }),
1863
+ defineCall({
1864
+ address: vault,
1865
+ abi: Abis.vaultAdapter,
1866
+ functionName: 'isSynced',
1867
+ }),
1868
+ defineCall({
1869
+ address: vault,
1870
+ abi: Abis.vaultAdapter,
1871
+ functionName: 'pendingRedeemCount',
1872
+ }),
1873
+ defineCall({
1874
+ address: vault,
1875
+ abi: Abis.vaultAdapter,
1876
+ functionName: 'feesActive',
1877
+ }),
1878
+ defineCall({
1879
+ address: engine,
1880
+ abi: Abis.vaultEngine,
1881
+ functionName: 'totalAssets',
1882
+ }),
1883
+ defineCall({
1884
+ address: engine,
1885
+ abi: Abis.vaultEngine,
1886
+ functionName: 'name',
1887
+ }),
1888
+ defineCall({
1889
+ address: engine,
1890
+ abi: Abis.vaultEngine,
1891
+ functionName: 'symbol',
1892
+ }),
1893
+ defineCall({
1894
+ address: engine,
1895
+ abi: Abis.vaultEngine,
1896
+ functionName: 'supportsInterface',
1897
+ args: [interfaceIds.asyncRedeem],
1898
+ }),
1899
+ defineCall({
1900
+ address: engine,
1901
+ abi: Abis.vaultEngine,
1902
+ functionName: 'supportsInterface',
1903
+ args: [interfaceIds.exactWithdraw],
1904
+ }),
1905
+ defineCall({
1906
+ address: engine,
1907
+ abi: Abis.vaultEngine,
1908
+ functionName: 'supportsInterface',
1909
+ args: [interfaceIds.inKindDeposit],
1910
+ }),
1911
+ defineCall({
1912
+ address: engine,
1913
+ abi: Abis.vaultEngine,
1914
+ functionName: 'supportsInterface',
1915
+ args: [interfaceIds.syncRedeem],
1916
+ }),
1917
+ ] as const
1918
+ }
1919
+ }
1920
+
1921
+ /**
1922
+ * Gets the vault shares required for an exact asset output, including fees
1923
+ * and ceiling rounding.
1924
+ *
1925
+ * @example
1926
+ * ```ts
1927
+ * import { createClient, http } from 'viem'
1928
+ * import { tempoModerato } from 'viem/chains'
1929
+ * import { Actions } from 'viem/tempo'
1930
+ *
1931
+ * const client = createClient({
1932
+ * chain: tempoModerato,
1933
+ * transport: http(),
1934
+ * })
1935
+ *
1936
+ * const shareAmount = await Actions.earn.getWithdrawQuote(client, {
1937
+ * assetAmount: 250_000_000n,
1938
+ * vault: '0x...',
1939
+ * })
1940
+ * ```
1941
+ *
1942
+ * @param client - Client.
1943
+ * @param parameters - Parameters.
1944
+ * @returns The required vault share input, ceiling-rounded.
1945
+ */
1946
+ export async function getWithdrawQuote<chain extends Chain | undefined>(
1947
+ client: Client<Transport, chain>,
1948
+ parameters: getWithdrawQuote.Parameters,
1949
+ ): Promise<getWithdrawQuote.ReturnValue> {
1950
+ const { assetAmount, vault, ...rest } = parameters
1951
+ return readContract(client, {
1952
+ ...rest,
1953
+ ...getWithdrawQuote.call({ assetAmount, vault }),
1954
+ })
1955
+ }
1956
+
1957
+ export namespace getWithdrawQuote {
1958
+ export type Args = {
1959
+ /** Exact asset output, base units. */
1960
+ assetAmount: bigint
1961
+ /** Vault address. */
1962
+ vault: Address
1963
+ }
1964
+ export type Parameters = Omit<ReadParameters, 'account'> & Args
1965
+ /** Required vault share input, ceiling-rounded. */
1966
+ export type ReturnValue = ReadContractReturnType<
1967
+ typeof Abis.vaultAdapter,
1968
+ 'previewWithdraw',
1969
+ never
1970
+ >
1971
+ // TODO: exhaustive error type
1972
+ export type ErrorType = BaseErrorType
1973
+
1974
+ /**
1975
+ * Defines a call to the vault's `previewWithdraw` function.
1976
+ *
1977
+ * Can be passed as a parameter to:
1978
+ * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
1979
+ * - [`multicall`](https://viem.sh/docs/contract/multicall): batch the call with other contract reads
1980
+ * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
1981
+ *
1982
+ * @example
1983
+ * ```ts
1984
+ * import { Actions } from 'viem/tempo'
1985
+ *
1986
+ * const call = Actions.earn.getWithdrawQuote.call({
1987
+ * assetAmount: 250_000_000n,
1988
+ * vault: '0x...',
1989
+ * })
1990
+ * ```
1991
+ *
1992
+ * @param args - Arguments.
1993
+ * @returns The call.
1994
+ */
1995
+ export function call(args: Args) {
1996
+ const { assetAmount, vault } = args
1997
+ return defineCall({
1998
+ address: vault,
1999
+ abi: Abis.vaultAdapter,
2000
+ args: [assetAmount],
2001
+ functionName: 'previewWithdraw',
2002
+ })
2003
+ }
2004
+ }
2005
+
2006
+ /**
2007
+ * Redeems vault shares for assets sent to `recipient`. The transaction
2008
+ * includes the required vault share approval.
2009
+ *
2010
+ * @example
2011
+ * ```ts
2012
+ * import { createClient, http } from 'viem'
2013
+ * import { privateKeyToAccount } from 'viem/accounts'
2014
+ * import { tempoModerato } from 'viem/chains'
2015
+ * import { Actions } from 'viem/tempo'
2016
+ *
2017
+ * const client = createClient({
2018
+ * account: privateKeyToAccount('0x...'),
2019
+ * chain: tempoModerato,
2020
+ * transport: http(),
2021
+ * })
2022
+ *
2023
+ * const hash = await Actions.earn.redeem(client, {
2024
+ * shareAmount: 100_000_000n,
2025
+ * slippageBps: 50,
2026
+ * vault: '0x...',
2027
+ * })
2028
+ * ```
2029
+ *
2030
+ * @param client - Client.
2031
+ * @param parameters - Parameters.
2032
+ * @returns The transaction hash.
2033
+ */
2034
+ export async function redeem<
2035
+ chain extends Chain | undefined,
2036
+ account extends Account | undefined,
2037
+ >(
2038
+ client: Client<Transport, chain, account>,
2039
+ parameters: redeem.Parameters<chain, account>,
2040
+ ): Promise<redeem.ReturnValue> {
2041
+ return redeem.inner(sendTransaction, client, parameters)
2042
+ }
2043
+
2044
+ export namespace redeem {
2045
+ export type Args = {
2046
+ /** Vault shares to redeem; base units or `{ formatted, decimals? }`. */
2047
+ shareAmount: internal_Token.AmountInput
2048
+ /** Asset recipient. @default `account.address` */
2049
+ recipient?: Address | undefined
2050
+ /** Vault address. */
2051
+ vault: Address
2052
+ } & OneOf<
2053
+ | {
2054
+ /** Minimum asset output to accept; must be greater than zero. */
2055
+ assetAmountMin: bigint
2056
+ }
2057
+ | {
2058
+ /** Slippage tolerance in basis points under a live {@link getRedeemQuote} (50 = 0.5%). */
2059
+ slippageBps: number
2060
+ }
2061
+ | {
2062
+ /** Quoted asset output; floored by `slippageBps`. */
2063
+ assetAmount: bigint
2064
+ /** Slippage tolerance in basis points under `assetAmount` (50 = 0.5%). */
2065
+ slippageBps: number
2066
+ }
2067
+ >
2068
+ export type Parameters<
2069
+ chain extends Chain | undefined = Chain | undefined,
2070
+ account extends Account | undefined = Account | undefined,
2071
+ > = WriteParameters<chain, account> & Args
2072
+ export type ReturnValue = SendTransactionReturnType
2073
+ // TODO: exhaustive error type
2074
+ export type ErrorType = BaseErrorType
2075
+
2076
+ /** @internal Shared dispatch; reads the vault share token for the approval. */
2077
+ export async function inner<
2078
+ action extends typeof sendTransaction | typeof sendTransactionSync,
2079
+ chain extends Chain | undefined,
2080
+ account extends Account | undefined,
2081
+ >(
2082
+ action: action,
2083
+ client: Client<Transport, chain, account>,
2084
+ parameters: redeem.Parameters<chain, account>,
2085
+ ): Promise<ReturnType<action>> {
2086
+ const [args, shareToken] = await Promise.all([
2087
+ toRedeemArgs(client, parameters as never),
2088
+ readContract(client, {
2089
+ abi: Abis.vaultAdapter,
2090
+ address: parameters.vault,
2091
+ functionName: 'shareToken',
2092
+ }),
2093
+ ])
2094
+ return (await action(client, {
2095
+ ...parameters,
2096
+ calls: redeem.calls({ ...args, shareToken }),
2097
+ } as never)) as never
2098
+ }
2099
+
2100
+ /**
2101
+ * Defines a redeem call without an approval. Provide vault share decimals
2102
+ * for formatted inputs and an explicit output bound because this builder performs no reads.
2103
+ *
2104
+ * @param parameters - Client (optional), followed by the call arguments.
2105
+ * @returns The call.
2106
+ */
2107
+ export function call<chain extends Chain | undefined>(
2108
+ ...parameters: CallParameters<call.Args, Client<Transport, chain>>
2109
+ ) {
2110
+ const [, args] = resolveCallParameters(parameters)
2111
+ const { recipient, vault } = args
2112
+ const assetAmountMin = (() => {
2113
+ if (args.assetAmountMin !== undefined) return args.assetAmountMin
2114
+ return EarnShares.minimumOutput(args.assetAmount, args.slippageBps)
2115
+ })()
2116
+ return defineCall({
2117
+ address: vault,
2118
+ abi: Abis.vaultAdapter,
2119
+ functionName: 'redeem',
2120
+ args: [
2121
+ internal_Token.toBaseUnits(args.shareAmount, undefined),
2122
+ recipient,
2123
+ assetAmountMin,
2124
+ ],
2125
+ })
2126
+ }
2127
+ export namespace call {
2128
+ export type Args = {
2129
+ /** Vault shares to redeem; base units or `{ formatted, decimals? }`. */
2130
+ shareAmount: internal_Token.AmountInput
2131
+ /** Asset recipient. */
2132
+ recipient: Address
2133
+ /** Vault address. */
2134
+ vault: Address
2135
+ } & OneOf<
2136
+ | {
2137
+ /** Minimum asset output to accept. */
2138
+ assetAmountMin: bigint
2139
+ }
2140
+ | {
2141
+ /** Quoted asset output; floored by `slippageBps`. */
2142
+ assetAmount: bigint
2143
+ /** Slippage tolerance in basis points under `assetAmount` (50 = 0.5%). */
2144
+ slippageBps: number
2145
+ }
2146
+ >
2147
+ }
2148
+
2149
+ /**
2150
+ * Defines the vault share approval and redeem calls for atomic execution.
2151
+ * Pass `shareToken` explicitly because this builder performs no reads.
2152
+ *
2153
+ * @param args - Arguments.
2154
+ * @returns The calls.
2155
+ */
2156
+ export function calls(
2157
+ args: call.Args & {
2158
+ /** Vault share token approved for the redemption. */
2159
+ shareToken: Address
2160
+ },
2161
+ ) {
2162
+ const { shareToken, vault } = args
2163
+ const shareAmount = internal_Token.toBaseUnits(args.shareAmount, undefined)
2164
+ return [
2165
+ defineCall({
2166
+ address: shareToken,
2167
+ abi: Abis.tip20,
2168
+ functionName: 'approve',
2169
+ args: [vault, shareAmount],
2170
+ }),
2171
+ redeem.call({ ...args, shareAmount }),
2172
+ ]
2173
+ }
2174
+
2175
+ /**
2176
+ * Extracts a `Redeemed` event from the vault's logs.
2177
+ *
2178
+ * @param logs - Logs.
2179
+ * @param parameters - Parameters.
2180
+ * @returns The `Redeemed` event.
2181
+ */
2182
+ export function extractEvent(logs: Log[], parameters: { vault: Address }) {
2183
+ const { vault } = parameters
2184
+ // Earn contracts are user-deployed: several adapters can emit the same
2185
+ // signature in one receipt, so filter by emitting address before decode.
2186
+ const [log] = parseEventLogs({
2187
+ abi: Abis.vaultAdapter,
2188
+ eventName: 'Redeemed',
2189
+ logs: logs.filter((log) => isAddressEqual(log.address, vault)),
2190
+ })
2191
+ if (!log) throw new Error('`Redeemed` event not found.')
2192
+ return log
2193
+ }
2194
+
2195
+ /**
2196
+ * Estimates gas for a redemption, assuming enough vault share allowance.
2197
+ *
2198
+ * @param client - Client.
2199
+ * @param parameters - Parameters.
2200
+ * @returns The gas estimate.
2201
+ */
2202
+ export async function estimateGas<
2203
+ chain extends Chain | undefined,
2204
+ account extends Account | undefined,
2205
+ >(
2206
+ client: Client<Transport, chain, account>,
2207
+ parameters: redeem.Parameters<chain, account>,
2208
+ ): Promise<bigint> {
2209
+ return estimateContractGas(client, {
2210
+ ...pickWriteParameters(parameters as never),
2211
+ ...redeem.call(await toRedeemArgs(client, parameters as never)),
2212
+ } as never)
2213
+ }
2214
+
2215
+ /**
2216
+ * Simulates a redemption, assuming enough vault share allowance.
2217
+ *
2218
+ * @param client - Client.
2219
+ * @param parameters - Parameters.
2220
+ * @returns The simulation result and write request.
2221
+ */
2222
+ export async function simulate<
2223
+ chain extends Chain | undefined,
2224
+ account extends Account | undefined,
2225
+ >(
2226
+ client: Client<Transport, chain, account>,
2227
+ parameters: redeem.Parameters<chain, account>,
2228
+ ): Promise<SimulateContractReturnType<typeof Abis.vaultAdapter, 'redeem'>> {
2229
+ return simulateContract(client, {
2230
+ ...pickWriteParameters(parameters as never),
2231
+ ...redeem.call(await toRedeemArgs(client, parameters as never)),
2232
+ } as never) as never
2233
+ }
2234
+ }
2235
+
2236
+ /**
2237
+ * Redeems vault shares and returns the confirmed receipt and event data.
2238
+ *
2239
+ * @example
2240
+ * ```ts
2241
+ * import { createClient, http } from 'viem'
2242
+ * import { privateKeyToAccount } from 'viem/accounts'
2243
+ * import { tempoModerato } from 'viem/chains'
2244
+ * import { Actions } from 'viem/tempo'
2245
+ *
2246
+ * const client = createClient({
2247
+ * account: privateKeyToAccount('0x...'),
2248
+ * chain: tempoModerato,
2249
+ * transport: http(),
2250
+ * })
2251
+ *
2252
+ * const { assetAmount } = await Actions.earn.redeemSync(client, {
2253
+ * assetAmountMin: 99_500_000n,
2254
+ * shareAmount: 100_000_000n,
2255
+ * vault: '0x...',
2256
+ * })
2257
+ * ```
2258
+ *
2259
+ * @param client - Client.
2260
+ * @param parameters - Parameters.
2261
+ * @returns The transaction receipt and event data.
2262
+ */
2263
+ export async function redeemSync<
2264
+ chain extends Chain | undefined,
2265
+ account extends Account | undefined,
2266
+ >(
2267
+ client: Client<Transport, chain, account>,
2268
+ parameters: redeemSync.Parameters<chain, account>,
2269
+ ): Promise<redeemSync.ReturnValue> {
2270
+ const { throwOnReceiptRevert = true, vault } = parameters
2271
+ const receipt = await redeem.inner(sendTransactionSync, client, {
2272
+ ...parameters,
2273
+ throwOnReceiptRevert,
2274
+ } as never)
2275
+ const { args } = redeem.extractEvent(receipt.logs, { vault })
2276
+ return {
2277
+ assetAmount: args.assets,
2278
+ caller: args.caller,
2279
+ receipt,
2280
+ recipient: args.receiver,
2281
+ shareAmount: args.shares,
2282
+ }
2283
+ }
2284
+
2285
+ export namespace redeemSync {
2286
+ export type Args = redeem.Args
2287
+ export type Parameters<
2288
+ chain extends Chain | undefined = Chain | undefined,
2289
+ account extends Account | undefined = Account | undefined,
2290
+ > = redeem.Parameters<chain, account> & WriteSyncParameters<chain, account>
2291
+ export type ReturnValue = Compute<{
2292
+ /** Assets paid out. */
2293
+ assetAmount: bigint
2294
+ /** Redeeming caller. */
2295
+ caller: Address
2296
+ /** Transaction receipt. */
2297
+ receipt: TransactionReceipt
2298
+ /** Asset recipient. */
2299
+ recipient: Address
2300
+ /** Vault shares burned. */
2301
+ shareAmount: bigint
2302
+ }>
2303
+ // TODO: exhaustive error type
2304
+ export type ErrorType = BaseErrorType
2305
+ }
2306
+
2307
+ /**
2308
+ * Withdraws vault shares from a Zone and redeems them on the parent chain. Use
2309
+ * {@link privateRedeem.prepare} to build the encrypted callback.
2310
+ *
2311
+ * @example
2312
+ * ```ts
2313
+ * const prepared = await Actions.earn.privateRedeem.prepare(parentClient, {
2314
+ * gateway: '0x...',
2315
+ * recipient: '0x...',
2316
+ * recoveryRecipient: '0x...',
2317
+ * shareAmount: 100_000_000n,
2318
+ * slippageBps: 50,
2319
+ * })
2320
+ * const hash = await Actions.earn.privateRedeem(zoneClient, prepared)
2321
+ * ```
2322
+ *
2323
+ * @param client - Zone client.
2324
+ * @param parameters - Prepared redemption and transaction parameters.
2325
+ * @returns The transaction hash.
2326
+ */
2327
+ export async function privateRedeem<
2328
+ chain extends Chain | undefined,
2329
+ account extends Account | undefined,
2330
+ >(
2331
+ client: Client<Transport, chain, account>,
2332
+ parameters: privateRedeem.Parameters<chain, account>,
2333
+ ): Promise<privateRedeem.ReturnValue> {
2334
+ await assertPreparedZoneRequestChain(client, parameters)
2335
+ return zoneActions.requestWithdrawal(client, parameters)
2336
+ }
2337
+
2338
+ export namespace privateRedeem {
2339
+ export type Args = prepare.ReturnValue
2340
+ export type Parameters<
2341
+ chain extends Chain | undefined = Chain | undefined,
2342
+ account extends Account | undefined = Account | undefined,
2343
+ > = WriteParameters<chain, account> & Args
2344
+ export type ReturnValue = SendTransactionReturnType
2345
+ export type ErrorType = zoneActions.requestWithdrawal.ErrorType
2346
+
2347
+ /**
2348
+ * Builds an encrypted Zone withdrawal that redeems vault shares and returns
2349
+ * the resulting assets to the Zone.
2350
+ *
2351
+ * @param client - Parent-chain client.
2352
+ * @param parameters - Redemption intent and recovery parameters.
2353
+ * @returns The prepared withdrawal and correlation data.
2354
+ */
2355
+ export async function prepare<chain extends Chain | undefined>(
2356
+ client: Client<Transport, chain>,
2357
+ parameters: prepare.Parameters,
2358
+ ): Promise<prepare.ReturnValue> {
2359
+ const chainId = client.chain?.id
2360
+ if (!chainId) throw new Error('`chain` is required.')
2361
+ const {
2362
+ actionId = Hex.random(32),
2363
+ callbackGas = zoneGatewayCallbackGas,
2364
+ fallbackRecipient = parameters.recoveryRecipient,
2365
+ gateway,
2366
+ recipient,
2367
+ recoveryRecipient,
2368
+ returnMemo,
2369
+ shareAmount,
2370
+ withdrawalMemo,
2371
+ } = parameters
2372
+ const readParameters = pickReadParameters(parameters)
2373
+ const [fromBlock, config] = await Promise.all([
2374
+ getBlockNumber(client, { cacheTime: 0 }),
2375
+ getZoneGatewayConfig(client, { ...readParameters, gateway }),
2376
+ ])
2377
+ const assetToken = parameters.assetToken ?? config.vaultAsset
2378
+ if (isAddressEqual(assetToken, config.shareToken))
2379
+ throw new Error('`assetToken` cannot be the gateway vault share token.')
2380
+
2381
+ const [{ encrypted, keyIndex }, assetAmountMin] = await Promise.all([
2382
+ zoneActions.encryptedDeposit.prepareRecipient(client, {
2383
+ ...readParameters,
2384
+ memo: returnMemo,
2385
+ portalAddress: config.zonePortal,
2386
+ recipient,
2387
+ zoneId: config.zoneId,
2388
+ }),
2389
+ (async () => {
2390
+ if (parameters.assetAmountMin !== undefined)
2391
+ return EarnShares.minimumOutput(parameters.assetAmountMin, 0)
2392
+ if (parameters.assetAmount !== undefined)
2393
+ return EarnShares.minimumOutput(
2394
+ parameters.assetAmount,
2395
+ parameters.slippageBps,
2396
+ )
2397
+ const assetAmount = await getRedeemQuote(client, {
2398
+ ...readParameters,
2399
+ shareAmount,
2400
+ vault: config.vaultAdapter,
2401
+ })
2402
+ return EarnShares.minimumOutput(assetAmount, parameters.slippageBps)
2403
+ })(),
2404
+ ])
2405
+ const direct = isAddressEqual(assetToken, config.vaultAsset)
2406
+ const data = encodeAbiParameters(Abis.zoneGatewayCallbackData, [
2407
+ {
2408
+ flow: 1,
2409
+ outputToken: assetToken,
2410
+ keyIndex,
2411
+ encrypted,
2412
+ minVaultAssets: direct ? assetAmountMin : 1n,
2413
+ minVaultShares: 0n,
2414
+ minOutputAmount: direct ? 0n : assetAmountMin,
2415
+ actionId,
2416
+ refundRecipient: recoveryRecipient,
2417
+ },
2418
+ ])
2419
+ return {
2420
+ actionId,
2421
+ amount: shareAmount,
2422
+ callbackGas,
2423
+ chainId,
2424
+ data,
2425
+ fallbackRecipient,
2426
+ fromBlock,
2427
+ memo: withdrawalMemo,
2428
+ to: gateway,
2429
+ token: config.shareToken,
2430
+ zoneId: config.zoneId,
2431
+ }
2432
+ }
2433
+
2434
+ export namespace prepare {
2435
+ export type Parameters = Omit<ReadParameters, 'account'> &
2436
+ PrivatePreparationParameters & {
2437
+ /** Vault shares withdrawn from the Zone, base units. */
2438
+ shareAmount: bigint
2439
+ } & (
2440
+ | ({
2441
+ /** Asset token returned to the Zone. @default gateway vault asset */
2442
+ assetToken?: undefined
2443
+ } & OneOf<
2444
+ | {
2445
+ /** Minimum assets returned to the Zone. */
2446
+ assetAmountMin: bigint
2447
+ }
2448
+ | {
2449
+ /** Quoted assets returned to the Zone. */
2450
+ assetAmount: bigint
2451
+ /** Slippage tolerance under `assetAmount` (50 = 0.5%). */
2452
+ slippageBps: number
2453
+ }
2454
+ | {
2455
+ /** Slippage tolerance under a live vault quote (50 = 0.5%). */
2456
+ slippageBps: number
2457
+ }
2458
+ >)
2459
+ | ({
2460
+ /** Asset token returned to the Zone after a swap. */
2461
+ assetToken: Address
2462
+ } & MinimumAssetAmountParameters)
2463
+ )
2464
+ export type ReturnValue = PreparedZoneRequest
2465
+ export type ErrorType = BaseErrorType
2466
+ }
2467
+
2468
+ /**
2469
+ * Defines the approval and Zone withdrawal calls for a prepared redemption.
2470
+ *
2471
+ * @param args - Prepared redemption arguments.
2472
+ * @returns The Zone withdrawal calls.
2473
+ */
2474
+ export function calls(args: Args) {
2475
+ return zoneActions.requestWithdrawal.calls(args)
2476
+ }
2477
+ }
2478
+
2479
+ /**
2480
+ * Requests a private Zone redemption and waits for the Zone transaction
2481
+ * receipt. The receipt confirms withdrawal acceptance, not redemption.
2482
+ *
2483
+ * @param client - Zone client.
2484
+ * @param parameters - Prepared redemption and transaction parameters.
2485
+ * @returns The Zone transaction receipt and parent-chain withdrawal sender tag.
2486
+ */
2487
+ export async function privateRedeemSync<
2488
+ chain extends Chain | undefined,
2489
+ account extends Account | undefined,
2490
+ >(
2491
+ client: Client<Transport, chain, account>,
2492
+ parameters: privateRedeemSync.Parameters<chain, account>,
2493
+ ): Promise<privateRedeemSync.ReturnValue> {
2494
+ await assertPreparedZoneRequestChain(client, parameters)
2495
+ return zoneActions.requestWithdrawalSync(client, parameters)
2496
+ }
2497
+
2498
+ export namespace privateRedeemSync {
2499
+ export type Args = privateRedeem.Args
2500
+ export type Parameters<
2501
+ chain extends Chain | undefined = Chain | undefined,
2502
+ account extends Account | undefined = Account | undefined,
2503
+ > = privateRedeem.Parameters<chain, account> &
2504
+ WriteSyncParameters<chain, account>
2505
+ export type ReturnValue = zoneActions.requestWithdrawalSync.ReturnValue
2506
+ export type ErrorType = zoneActions.requestWithdrawalSync.ErrorType
2507
+ }
2508
+
2509
+ /**
2510
+ * Waits for a Zone gateway redemption to complete on the parent chain.
2511
+ *
2512
+ * @example
2513
+ * ```ts
2514
+ * const result = await Actions.earn.waitForPrivateRedeem(parentClient, {
2515
+ * actionId: prepared.actionId,
2516
+ * fromBlock: prepared.fromBlock,
2517
+ * gateway: '0x...',
2518
+ * })
2519
+ * ```
2520
+ *
2521
+ * @param client - Parent-chain client.
2522
+ * @param parameters - Prepared action correlation and polling parameters.
2523
+ * @returns The completed gateway redemption.
2524
+ */
2525
+ export async function waitForPrivateRedeem<chain extends Chain | undefined>(
2526
+ client: Client<Transport, chain>,
2527
+ parameters: waitForPrivateRedeem.Parameters,
2528
+ ): Promise<waitForPrivateRedeem.ReturnType> {
2529
+ const {
2530
+ actionId,
2531
+ fromBlock,
2532
+ gateway,
2533
+ pollingInterval = client.pollingInterval,
2534
+ timeout = 60_000,
2535
+ } = parameters
2536
+ const event = getAbiItem({
2537
+ abi: Abis.zoneGateway,
2538
+ name: 'EarnRedeem',
2539
+ })
2540
+ const observerId = stringify([
2541
+ 'waitForPrivateRedeem',
2542
+ client.uid,
2543
+ gateway,
2544
+ actionId,
2545
+ fromBlock,
2546
+ ])
2547
+ const { promise, reject, resolve } =
2548
+ withResolvers<waitForPrivateRedeem.ReturnType>()
2549
+
2550
+ let timer: ReturnType<typeof setTimeout> | undefined
2551
+ let unobserve: () => void
2552
+ const cleanup = () => {
2553
+ clearTimeout(timer)
2554
+ unobserve()
2555
+ }
2556
+ const resolve_ = (result: waitForPrivateRedeem.ReturnType) => {
2557
+ cleanup()
2558
+ resolve(result)
2559
+ }
2560
+ const reject_ = (error: unknown) => {
2561
+ cleanup()
2562
+ reject(error)
2563
+ }
2564
+
2565
+ unobserve = observe(
2566
+ observerId,
2567
+ { reject: reject_, resolve: resolve_ },
2568
+ (emit) => {
2569
+ const unpoll = poll(
2570
+ async () => {
2571
+ try {
2572
+ const [log] = await getLogs(client, {
2573
+ address: gateway,
2574
+ args: { actionId },
2575
+ event,
2576
+ fromBlock,
2577
+ strict: true,
2578
+ toBlock: 'latest',
2579
+ })
2580
+ if (!log) return
2581
+ unpoll()
2582
+ emit.resolve({
2583
+ actionId: log.args.actionId,
2584
+ outputAmount: log.args.outputAmount,
2585
+ outputToken: log.args.outputToken,
2586
+ shares: log.args.shares,
2587
+ tempoBlockNumber: log.blockNumber,
2588
+ vaultAssets: log.args.vaultAssets,
2589
+ zoneDepositHash: log.args.zoneDepositHash,
2590
+ })
2591
+ } catch (error) {
2592
+ unpoll()
2593
+ emit.reject(error)
2594
+ }
2595
+ },
2596
+ { emitOnBegin: true, interval: pollingInterval },
2597
+ )
2598
+
2599
+ return unpoll
2600
+ },
2601
+ )
2602
+
2603
+ timer = timeout
2604
+ ? setTimeout(() => {
2605
+ reject_(new WaitForPrivateRedeemTimeoutError({ actionId, gateway }))
2606
+ }, timeout)
2607
+ : undefined
2608
+
2609
+ return await promise
2610
+ }
2611
+
2612
+ export namespace waitForPrivateRedeem {
2613
+ export type Parameters = {
2614
+ /** Correlation id from {@link privateRedeem.prepare}. */
2615
+ actionId: Hex.Hex
2616
+ /** Lower bound for the parent-chain log scan. */
2617
+ fromBlock: bigint
2618
+ /** Zone gateway address. */
2619
+ gateway: Address
2620
+ /** Polling frequency in milliseconds. @default `client.pollingInterval` */
2621
+ pollingInterval?: number | undefined
2622
+ /** Timeout in milliseconds; `0` disables it. @default `60_000` */
2623
+ timeout?: number | undefined
2624
+ }
2625
+ export type ReturnType = {
2626
+ /** Correlation id for the completed redemption. */
2627
+ actionId: Hex.Hex
2628
+ /** Tokens returned to the Zone, base units. */
2629
+ outputAmount: bigint
2630
+ /** Token returned to the Zone. */
2631
+ outputToken: Address
2632
+ /** Vault shares redeemed. */
2633
+ shares: bigint
2634
+ /** Parent-chain block containing the gateway event. */
2635
+ tempoBlockNumber: bigint
2636
+ /** Vault assets produced before any swap. */
2637
+ vaultAssets: bigint
2638
+ /** Encrypted return deposit hash. */
2639
+ zoneDepositHash: Hex.Hex
2640
+ }
2641
+ export type ErrorType =
2642
+ | GetLogsErrorType
2643
+ | ObserveErrorType
2644
+ | PollErrorType
2645
+ | WaitForPrivateRedeemTimeoutErrorType
2646
+ | BaseErrorType
2647
+ }
2648
+
2649
+ /**
2650
+ * Withdraws an exact asset amount to `recipient`, up to the specified vault
2651
+ * share limit. The transaction includes the required vault share approval;
2652
+ * use {@link redeem} for a full exit.
2653
+ *
2654
+ * @example
2655
+ * ```ts
2656
+ * import { createClient, http } from 'viem'
2657
+ * import { privateKeyToAccount } from 'viem/accounts'
2658
+ * import { tempoModerato } from 'viem/chains'
2659
+ * import { Actions } from 'viem/tempo'
2660
+ *
2661
+ * const client = createClient({
2662
+ * account: privateKeyToAccount('0x...'),
2663
+ * chain: tempoModerato,
2664
+ * transport: http(),
2665
+ * })
2666
+ *
2667
+ * const hash = await Actions.earn.withdrawExact(client, {
2668
+ * assetAmount: 40_000_000n,
2669
+ * slippageBps: 50,
2670
+ * vault: '0x...',
2671
+ * })
2672
+ * ```
2673
+ *
2674
+ * @param client - Client.
2675
+ * @param parameters - Parameters.
2676
+ * @returns The transaction hash.
2677
+ */
2678
+ export async function withdrawExact<
2679
+ chain extends Chain | undefined,
2680
+ account extends Account | undefined,
2681
+ >(
2682
+ client: Client<Transport, chain, account>,
2683
+ parameters: withdrawExact.Parameters<chain, account>,
2684
+ ): Promise<withdrawExact.ReturnValue> {
2685
+ return withdrawExact.inner(sendTransaction, client, parameters)
2686
+ }
2687
+
2688
+ export namespace withdrawExact {
2689
+ export type Args = {
2690
+ /** Exact assets to receive; base units or `{ formatted, decimals? }`. */
2691
+ assetAmount: internal_Token.AmountInput
2692
+ /** Asset recipient. @default `account.address` */
2693
+ recipient?: Address | undefined
2694
+ /** Vault address. */
2695
+ vault: Address
2696
+ } & OneOf<
2697
+ | {
2698
+ /** Maximum vault share input to burn. */
2699
+ shareAmountMax: bigint
2700
+ }
2701
+ | {
2702
+ /** Slippage headroom above a live {@link getWithdrawQuote}, ceiling-rounded (50 = 0.5%). */
2703
+ slippageBps: number
2704
+ }
2705
+ | {
2706
+ /** Quoted vault share input; raised by `slippageBps`. */
2707
+ shareAmount: bigint
2708
+ /** Slippage tolerance in basis points over `shareAmount` (50 = 0.5%). */
2709
+ slippageBps: number
2710
+ }
2711
+ >
2712
+ export type Parameters<
2713
+ chain extends Chain | undefined = Chain | undefined,
2714
+ account extends Account | undefined = Account | undefined,
2715
+ > = WriteParameters<chain, account> & Args
2716
+ export type ReturnValue = SendTransactionReturnType
2717
+ // TODO: exhaustive error type
2718
+ export type ErrorType = BaseErrorType
2719
+
2720
+ /** @internal Shared dispatch; reads the vault share token for the approval. */
2721
+ export async function inner<
2722
+ action extends typeof sendTransaction | typeof sendTransactionSync,
2723
+ chain extends Chain | undefined,
2724
+ account extends Account | undefined,
2725
+ >(
2726
+ action: action,
2727
+ client: Client<Transport, chain, account>,
2728
+ parameters: withdrawExact.Parameters<chain, account>,
2729
+ ): Promise<ReturnType<action>> {
2730
+ const [args, shareToken] = await Promise.all([
2731
+ toWithdrawExactArgs(client, parameters as never),
2732
+ readContract(client, {
2733
+ abi: Abis.vaultAdapter,
2734
+ address: parameters.vault,
2735
+ functionName: 'shareToken',
2736
+ }),
2737
+ ])
2738
+ return (await action(client, {
2739
+ ...parameters,
2740
+ calls: withdrawExact.calls({ ...args, shareToken }),
2741
+ } as never)) as never
2742
+ }
2743
+
2744
+ /**
2745
+ * Defines an exact withdrawal call without an approval. Provide asset
2746
+ * decimals and an explicit input limit because this builder performs no reads.
2747
+ *
2748
+ * @param parameters - Client (optional), followed by the call arguments.
2749
+ * @returns The call.
2750
+ */
2751
+ export function call<chain extends Chain | undefined>(
2752
+ ...parameters: CallParameters<call.Args, Client<Transport, chain>>
2753
+ ) {
2754
+ const [, args] = resolveCallParameters(parameters)
2755
+ const { recipient, vault } = args
2756
+ const shareAmountMax = (() => {
2757
+ if (args.shareAmountMax !== undefined) return args.shareAmountMax
2758
+ return maximumInput(args.shareAmount, args.slippageBps)
2759
+ })()
2760
+ return defineCall({
2761
+ address: vault,
2762
+ abi: Abis.vaultAdapter,
2763
+ functionName: 'withdrawExact',
2764
+ args: [
2765
+ internal_Token.toBaseUnits(args.assetAmount, undefined),
2766
+ recipient,
2767
+ shareAmountMax,
2768
+ ],
2769
+ })
2770
+ }
2771
+ export namespace call {
2772
+ export type Args = {
2773
+ /** Exact assets to receive; base units or `{ formatted, decimals? }`. */
2774
+ assetAmount: internal_Token.AmountInput
2775
+ /** Asset recipient. */
2776
+ recipient: Address
2777
+ /** Vault address. */
2778
+ vault: Address
2779
+ } & OneOf<
2780
+ | {
2781
+ /** Maximum vault share input to burn. */
2782
+ shareAmountMax: bigint
2783
+ }
2784
+ | {
2785
+ /** Quoted vault share input; raised by `slippageBps`. */
2786
+ shareAmount: bigint
2787
+ /** Slippage tolerance in basis points over `shareAmount` (50 = 0.5%). */
2788
+ slippageBps: number
2789
+ }
2790
+ >
2791
+ }
2792
+
2793
+ /**
2794
+ * Defines the vault share approval and withdrawal calls for atomic
2795
+ * execution. Pass `shareToken` explicitly because this builder performs no reads.
2796
+ *
2797
+ * @param args - Arguments.
2798
+ * @returns The calls.
2799
+ */
2800
+ export function calls(
2801
+ args: call.Args & {
2802
+ /** Vault share token approved for the withdrawal. */
2803
+ shareToken: Address
2804
+ },
2805
+ ) {
2806
+ const { shareToken, vault } = args
2807
+ const assetAmount = internal_Token.toBaseUnits(args.assetAmount, undefined)
2808
+ const call = withdrawExact.call({ ...args, assetAmount })
2809
+ const [, , shareAmountMax] = call.args
2810
+ return [
2811
+ defineCall({
2812
+ address: shareToken,
2813
+ abi: Abis.tip20,
2814
+ functionName: 'approve',
2815
+ args: [vault, shareAmountMax],
2816
+ }),
2817
+ call,
2818
+ ]
2819
+ }
2820
+
2821
+ /**
2822
+ * Extracts a `WithdrewExact` event from the vault's logs.
2823
+ *
2824
+ * @param logs - Logs.
2825
+ * @param parameters - Parameters.
2826
+ * @returns The `WithdrewExact` event.
2827
+ */
2828
+ export function extractEvent(logs: Log[], parameters: { vault: Address }) {
2829
+ const { vault } = parameters
2830
+ // Earn contracts are user-deployed: several adapters can emit the same
2831
+ // signature in one receipt, so filter by emitting address before decode.
2832
+ const [log] = parseEventLogs({
2833
+ abi: Abis.vaultAdapter,
2834
+ eventName: 'WithdrewExact',
2835
+ logs: logs.filter((log) => isAddressEqual(log.address, vault)),
2836
+ })
2837
+ if (!log) throw new Error('`WithdrewExact` event not found.')
2838
+ return log
2839
+ }
2840
+
2841
+ /**
2842
+ * Estimates gas for an exact withdrawal, assuming enough vault share allowance.
2843
+ *
2844
+ * @param client - Client.
2845
+ * @param parameters - Parameters.
2846
+ * @returns The gas estimate.
2847
+ */
2848
+ export async function estimateGas<
2849
+ chain extends Chain | undefined,
2850
+ account extends Account | undefined,
2851
+ >(
2852
+ client: Client<Transport, chain, account>,
2853
+ parameters: withdrawExact.Parameters<chain, account>,
2854
+ ): Promise<bigint> {
2855
+ return estimateContractGas(client, {
2856
+ ...pickWriteParameters(parameters as never),
2857
+ ...withdrawExact.call(
2858
+ await toWithdrawExactArgs(client, parameters as never),
2859
+ ),
2860
+ } as never)
2861
+ }
2862
+
2863
+ /**
2864
+ * Simulates an exact withdrawal, assuming enough vault share allowance.
2865
+ *
2866
+ * @param client - Client.
2867
+ * @param parameters - Parameters.
2868
+ * @returns The simulation result and write request.
2869
+ */
2870
+ export async function simulate<
2871
+ chain extends Chain | undefined,
2872
+ account extends Account | undefined,
2873
+ >(
2874
+ client: Client<Transport, chain, account>,
2875
+ parameters: withdrawExact.Parameters<chain, account>,
2876
+ ): Promise<
2877
+ SimulateContractReturnType<typeof Abis.vaultAdapter, 'withdrawExact'>
2878
+ > {
2879
+ return simulateContract(client, {
2880
+ ...pickWriteParameters(parameters as never),
2881
+ ...withdrawExact.call(
2882
+ await toWithdrawExactArgs(client, parameters as never),
2883
+ ),
2884
+ } as never) as never
2885
+ }
2886
+ }
2887
+
2888
+ /**
2889
+ * Withdraws an exact asset amount and returns the confirmed receipt and event data.
2890
+ *
2891
+ * @example
2892
+ * ```ts
2893
+ * import { createClient, http } from 'viem'
2894
+ * import { privateKeyToAccount } from 'viem/accounts'
2895
+ * import { tempoModerato } from 'viem/chains'
2896
+ * import { Actions } from 'viem/tempo'
2897
+ *
2898
+ * const client = createClient({
2899
+ * account: privateKeyToAccount('0x...'),
2900
+ * chain: tempoModerato,
2901
+ * transport: http(),
2902
+ * })
2903
+ *
2904
+ * const { shareAmount } = await Actions.earn.withdrawExactSync(client, {
2905
+ * assetAmount: 40_000_000n,
2906
+ * shareAmountMax: 40_200_000n,
2907
+ * vault: '0x...',
2908
+ * })
2909
+ * ```
2910
+ *
2911
+ * @param client - Client.
2912
+ * @param parameters - Parameters.
2913
+ * @returns The transaction receipt and event data.
2914
+ */
2915
+ export async function withdrawExactSync<
2916
+ chain extends Chain | undefined,
2917
+ account extends Account | undefined,
2918
+ >(
2919
+ client: Client<Transport, chain, account>,
2920
+ parameters: withdrawExactSync.Parameters<chain, account>,
2921
+ ): Promise<withdrawExactSync.ReturnValue> {
2922
+ const { throwOnReceiptRevert = true, vault } = parameters
2923
+ const receipt = await withdrawExact.inner(sendTransactionSync, client, {
2924
+ ...parameters,
2925
+ throwOnReceiptRevert,
2926
+ } as never)
2927
+ const { args } = withdrawExact.extractEvent(receipt.logs, { vault })
2928
+ return {
2929
+ assetAmount: args.assets,
2930
+ caller: args.caller,
2931
+ receipt,
2932
+ recipient: args.receiver,
2933
+ shareAmount: args.sharesBurned,
2934
+ }
2935
+ }
2936
+
2937
+ export namespace withdrawExactSync {
2938
+ export type Args = withdrawExact.Args
2939
+ export type Parameters<
2940
+ chain extends Chain | undefined = Chain | undefined,
2941
+ account extends Account | undefined = Account | undefined,
2942
+ > = withdrawExact.Parameters<chain, account> &
2943
+ WriteSyncParameters<chain, account>
2944
+ export type ReturnValue = Compute<{
2945
+ /** Exact assets received. */
2946
+ assetAmount: bigint
2947
+ /** Withdrawing caller. */
2948
+ caller: Address
2949
+ /** Transaction receipt. */
2950
+ receipt: TransactionReceipt
2951
+ /** Asset recipient. */
2952
+ recipient: Address
2953
+ /** Vault shares burned. */
2954
+ shareAmount: bigint
2955
+ }>
2956
+ // TODO: exhaustive error type
2957
+ export type ErrorType = BaseErrorType
2958
+ }
2959
+
2960
+ type MinimumAssetAmountParameters = OneOf<
2961
+ | {
2962
+ /** Minimum assets returned to the Zone. */
2963
+ assetAmountMin: bigint
2964
+ }
2965
+ | {
2966
+ /** Quoted assets returned to the Zone. */
2967
+ assetAmount: bigint
2968
+ /** Slippage tolerance under `assetAmount` (50 = 0.5%). */
2969
+ slippageBps: number
2970
+ }
2971
+ >
2972
+
2973
+ type MinimumShareAmountParameters = OneOf<
2974
+ | {
2975
+ /** Minimum vault shares returned to the Zone. */
2976
+ shareAmountMin: bigint
2977
+ }
2978
+ | {
2979
+ /** Quoted vault shares returned to the Zone. */
2980
+ shareAmount: bigint
2981
+ /** Slippage tolerance under `shareAmount` (50 = 0.5%). */
2982
+ slippageBps: number
2983
+ }
2984
+ >
2985
+
2986
+ type PrivatePreparationParameters = {
2987
+ /** Optional caller-supplied correlation id. @default Random bytes32 */
2988
+ actionId?: Hex.Hex | undefined
2989
+ /** Gas reserved for the parent-chain callback. @default `10_000_000n` */
2990
+ callbackGas?: bigint | undefined
2991
+ /** Public recipient if the parent-chain callback fails. @default `recoveryRecipient` */
2992
+ fallbackRecipient?: Address | undefined
2993
+ /** Gateway identifying the vault and Zone configuration. */
2994
+ gateway: Address
2995
+ /** Encrypted recipient for the returned tokens. */
2996
+ recipient: Address
2997
+ /** Public recipient if the encrypted return fails. */
2998
+ recoveryRecipient: Address
2999
+ /** Optional memo encrypted with the returned Zone deposit. */
3000
+ returnMemo?: Hex.Hex | undefined
3001
+ /** Optional memo attached to the Zone withdrawal. */
3002
+ withdrawalMemo?: Hex.Hex | undefined
3003
+ }
3004
+
3005
+ type PreparedZoneRequest = {
3006
+ /** Correlation id for the matching wait action. */
3007
+ actionId: Hex.Hex
3008
+ /** Withdrawal amount, passed through to the Zone action. */
3009
+ amount: bigint
3010
+ /** Gas reserved for the parent-chain callback. */
3011
+ callbackGas: bigint
3012
+ /** Parent chain containing the gateway. */
3013
+ chainId: number
3014
+ /** Encoded gateway callback. */
3015
+ data: Hex.Hex
3016
+ /** Public recipient if the parent-chain callback fails. */
3017
+ fallbackRecipient: Address
3018
+ /** Parent-chain block before the withdrawal is submitted. */
3019
+ fromBlock: bigint
3020
+ /** Optional memo attached to the Zone withdrawal. */
3021
+ memo?: Hex.Hex | undefined
3022
+ /** Zone gateway receiving the withdrawal. */
3023
+ to: Address
3024
+ /** Token withdrawn from the Zone. */
3025
+ token: Address
3026
+ /** Zone containing the withdrawn tokens. */
3027
+ zoneId: number
3028
+ }
3029
+
3030
+ const zoneGatewayCallbackGas = 10_000_000n
3031
+
3032
+ function resolveMinimumShareAmount(parameters: MinimumShareAmountParameters) {
3033
+ if (parameters.shareAmountMin !== undefined)
3034
+ return EarnShares.minimumOutput(parameters.shareAmountMin, 0)
3035
+ return EarnShares.minimumOutput(
3036
+ parameters.shareAmount,
3037
+ parameters.slippageBps,
3038
+ )
3039
+ }
3040
+
3041
+ async function assertPreparedZoneRequestChain(
3042
+ client: Client<Transport, Chain | undefined>,
3043
+ parameters: PreparedZoneRequest,
3044
+ ) {
3045
+ const chain = client.chain
3046
+ if (!chain) throw new Error('`chain` is required.')
3047
+ if (chain.sourceId !== parameters.chainId)
3048
+ throw new Error(
3049
+ 'Prepared Zone request parent chain ID does not match client chain.',
3050
+ )
3051
+ const { zoneId } = await zoneActions.getZoneInfo(client)
3052
+ if (zoneId !== parameters.zoneId)
3053
+ throw new Error(
3054
+ 'Prepared Zone request Zone ID does not match client chain.',
3055
+ )
3056
+ }
3057
+
3058
+ function pickReadParameters(parameters: Omit<ReadParameters, 'account'>) {
3059
+ const { blockOverrides, stateOverride } = parameters
3060
+ if (parameters.blockNumber !== undefined)
3061
+ return {
3062
+ blockNumber: parameters.blockNumber,
3063
+ blockOverrides,
3064
+ stateOverride,
3065
+ }
3066
+ return { blockOverrides, blockTag: parameters.blockTag, stateOverride }
3067
+ }
3068
+
3069
+ async function getZoneGatewayConfig<chain extends Chain | undefined>(
3070
+ client: Client<Transport, chain>,
3071
+ parameters: Omit<ReadParameters, 'account'> & { gateway: Address },
3072
+ ) {
3073
+ const { gateway, ...rest } = parameters
3074
+ const [
3075
+ vaultAdapter,
3076
+ vaultAsset,
3077
+ shareToken,
3078
+ zoneId,
3079
+ zonePortal,
3080
+ supportsAsyncFlow,
3081
+ ] = await multicall(client, {
3082
+ ...rest,
3083
+ allowFailure: false,
3084
+ contracts: [
3085
+ {
3086
+ abi: Abis.zoneGateway,
3087
+ address: gateway,
3088
+ functionName: 'vaultAdapter',
3089
+ },
3090
+ {
3091
+ abi: Abis.zoneGateway,
3092
+ address: gateway,
3093
+ functionName: 'vaultAsset',
3094
+ },
3095
+ {
3096
+ abi: Abis.zoneGateway,
3097
+ address: gateway,
3098
+ functionName: 'shareToken',
3099
+ },
3100
+ {
3101
+ abi: Abis.zoneGateway,
3102
+ address: gateway,
3103
+ functionName: 'zoneId',
3104
+ },
3105
+ {
3106
+ abi: Abis.zoneGateway,
3107
+ address: gateway,
3108
+ functionName: 'zonePortal',
3109
+ },
3110
+ {
3111
+ abi: Abis.zoneGateway,
3112
+ address: gateway,
3113
+ args: [2],
3114
+ functionName: 'supportsFlow',
3115
+ },
3116
+ ],
3117
+ deployless: true,
3118
+ })
3119
+ if (supportsAsyncFlow)
3120
+ throw new Error('Async Zone gateways are not supported.')
3121
+ return { shareToken, vaultAdapter, vaultAsset, zoneId, zonePortal }
3122
+ }
3123
+
3124
+ // ERC-165 ids of the optional engine capability interfaces (XOR of each
3125
+ // interface's function selectors).
3126
+ const interfaceIds = {
3127
+ /** `IVaultEngineAsync`. */
3128
+ asyncRedeem: '0xa1a6a1d7',
3129
+ /** `IVaultEngineExactWithdraw`. */
3130
+ exactWithdraw: '0x0adfb0b9',
3131
+ /** `IVaultEngineShares`. */
3132
+ inKindDeposit: '0x7d28a2f2',
3133
+ /** `IVaultEngineSync`. */
3134
+ syncRedeem: '0x370457f4',
3135
+ } as const
3136
+
3137
+ /** Trims the decoded `IVaultFees.FeeConfig` to its active fixed-fee count. */
3138
+ function toFeeConfig(
3139
+ config: ReadContractReturnType<typeof Abis.vaultAdapter, 'feeConfig'>,
3140
+ ): FeeConfig {
3141
+ return {
3142
+ excess: config.excess,
3143
+ fixedFees: config.fixedFees.slice(0, config.fixedFeeCount),
3144
+ }
3145
+ }
3146
+
3147
+ /** Trims the decoded `IVaultFees.FeePreview` to its active allocation count. */
3148
+ function toFeePreview(
3149
+ preview: ReadContractReturnType<
3150
+ typeof Abis.vaultAdapter,
3151
+ 'previewAccruedFees'
3152
+ >,
3153
+ ): FeePreview {
3154
+ const { allocationCount, allocations, ...rest } = preview
3155
+ return { ...rest, allocations: allocations.slice(0, allocationCount) }
3156
+ }
3157
+
3158
+ /** Resolves `deposit` parameters into the adapter call args. @internal */
3159
+ async function toDepositArgs(
3160
+ client: Client<Transport, Chain | undefined, Account | undefined>,
3161
+ parameters: deposit.Parameters,
3162
+ ): Promise<deposit.call.Args> {
3163
+ const { vault } = parameters
3164
+ const assetAmount = await toBaseUnitsLive(client, {
3165
+ amount: parameters.assetAmount,
3166
+ token: 'asset',
3167
+ vault,
3168
+ })
3169
+ const args = {
3170
+ assetAmount,
3171
+ recipient: resolveRecipient(client, parameters),
3172
+ vault,
3173
+ }
3174
+ if (parameters.shareAmountMin !== undefined)
3175
+ return { ...args, shareAmountMin: parameters.shareAmountMin }
3176
+ return {
3177
+ ...args,
3178
+ shareAmount: parameters.shareAmount,
3179
+ slippageBps: parameters.slippageBps,
3180
+ }
3181
+ }
3182
+
3183
+ /** Resolves `depositShares` parameters into the adapter call args. @internal */
3184
+ function toDepositSharesArgs(
3185
+ client: Client<Transport, Chain | undefined, Account | undefined>,
3186
+ parameters: depositShares.Parameters,
3187
+ ): depositShares.call.Args {
3188
+ const { vault, venueShareAmount } = parameters
3189
+ const args = {
3190
+ recipient: resolveRecipient(client, parameters),
3191
+ vault,
3192
+ venueShareAmount,
3193
+ }
3194
+ if (parameters.earnShareAmountMin !== undefined)
3195
+ return { ...args, earnShareAmountMin: parameters.earnShareAmountMin }
3196
+ return {
3197
+ ...args,
3198
+ earnShareAmount: parameters.earnShareAmount,
3199
+ slippageBps: parameters.slippageBps,
3200
+ }
3201
+ }
3202
+
3203
+ /** Resolves `redeem` parameters into the adapter call args. @internal */
3204
+ async function toRedeemArgs(
3205
+ client: Client<Transport, Chain | undefined, Account | undefined>,
3206
+ parameters: redeem.Parameters,
3207
+ ): Promise<redeem.call.Args> {
3208
+ const { vault } = parameters
3209
+ const shareAmount = await toBaseUnitsLive(client, {
3210
+ amount: parameters.shareAmount,
3211
+ token: 'shareToken',
3212
+ vault,
3213
+ })
3214
+ const args = {
3215
+ recipient: resolveRecipient(client, parameters),
3216
+ shareAmount,
3217
+ vault,
3218
+ }
3219
+ if (parameters.assetAmountMin !== undefined)
3220
+ return { ...args, assetAmountMin: parameters.assetAmountMin }
3221
+ const assetAmount = await (async () => {
3222
+ if (parameters.assetAmount !== undefined) return parameters.assetAmount
3223
+ return getRedeemQuote(client, { shareAmount, vault })
3224
+ })()
3225
+ return {
3226
+ ...args,
3227
+ assetAmount,
3228
+ slippageBps: parameters.slippageBps,
3229
+ }
3230
+ }
3231
+
3232
+ /** Resolves `withdrawExact` parameters into the adapter call args. @internal */
3233
+ async function toWithdrawExactArgs(
3234
+ client: Client<Transport, Chain | undefined, Account | undefined>,
3235
+ parameters: withdrawExact.Parameters,
3236
+ ): Promise<withdrawExact.call.Args> {
3237
+ const { vault } = parameters
3238
+ const assetAmount = await toBaseUnitsLive(client, {
3239
+ amount: parameters.assetAmount,
3240
+ token: 'asset',
3241
+ vault,
3242
+ })
3243
+ const args = {
3244
+ assetAmount,
3245
+ recipient: resolveRecipient(client, parameters),
3246
+ vault,
3247
+ }
3248
+ if (parameters.shareAmountMax !== undefined)
3249
+ return { ...args, shareAmountMax: parameters.shareAmountMax }
3250
+ const shareAmount = await (async () => {
3251
+ if (parameters.shareAmount !== undefined) return parameters.shareAmount
3252
+ return getWithdrawQuote(client, { assetAmount, vault })
3253
+ })()
3254
+ return {
3255
+ ...args,
3256
+ shareAmount,
3257
+ slippageBps: parameters.slippageBps,
3258
+ }
3259
+ }
3260
+
3261
+ /** Raises a quoted input by basis points with ceiling rounding. @internal */
3262
+ function maximumInput(shareAmount: bigint, slippageBps: number): bigint {
3263
+ if (shareAmount <= 0n)
3264
+ throw new EarnShares.InvalidExpectedOutputError({
3265
+ expectedAmount: shareAmount,
3266
+ })
3267
+ if (
3268
+ !Number.isInteger(slippageBps) ||
3269
+ slippageBps < 0 ||
3270
+ slippageBps >= EarnShares.basisPointScale
3271
+ )
3272
+ throw new EarnShares.InvalidSlippageError({ slippageBps })
3273
+ const scale = BigInt(EarnShares.basisPointScale)
3274
+ const numerator = shareAmount * (scale + BigInt(slippageBps))
3275
+ // Adding the denominator minus one converts floor division to ceiling.
3276
+ return (numerator + scale - 1n) / scale
3277
+ }
3278
+
3279
+ /**
3280
+ * Converts an amount to base units, resolving missing decimals with live
3281
+ * reads of the vault's asset or share token. Vault share tokens are not
3282
+ * genesis-declared, so nothing is cached. @internal
3283
+ */
3284
+ async function toBaseUnitsLive(
3285
+ client: Client<Transport, Chain | undefined>,
3286
+ options: {
3287
+ amount: internal_Token.AmountInput
3288
+ token: 'asset' | 'shareToken'
3289
+ vault: Address
3290
+ },
3291
+ ): Promise<bigint> {
3292
+ const { amount, token, vault } = options
3293
+ if (typeof amount === 'bigint') return amount
3294
+ if (amount.decimals !== undefined)
3295
+ return internal_Token.toBaseUnits(amount, amount.decimals)
3296
+ const address = await readContract(client, {
3297
+ abi: Abis.vaultAdapter,
3298
+ address: vault,
3299
+ functionName: token,
3300
+ })
3301
+ const { decimals } = await resolveTokenWithDecimals(client, {
3302
+ token: address,
3303
+ })
3304
+ return internal_Token.toBaseUnits(amount, decimals)
3305
+ }
3306
+
3307
+ /** Defaults a write's `recipient` to the sending account's address. @internal */
3308
+ function resolveRecipient(
3309
+ client: Client<Transport, Chain | undefined, Account | undefined>,
3310
+ parameters: {
3311
+ account?: Account | Address | null | undefined
3312
+ recipient?: Address | undefined
3313
+ },
3314
+ ): Address {
3315
+ if (parameters.recipient) return parameters.recipient
3316
+ const account = parameters.account ?? client.account
3317
+ if (!account) throw new AccountNotFoundError()
3318
+ return parseAccount(account).address
3319
+ }