viem 2.55.10 → 2.55.13

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 (138) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/_cjs/actions/wallet/prepareTransactionRequest.js +12 -12
  3. package/_cjs/actions/wallet/prepareTransactionRequest.js.map +1 -1
  4. package/_cjs/actions/wallet/sendTransaction.js +24 -12
  5. package/_cjs/actions/wallet/sendTransaction.js.map +1 -1
  6. package/_cjs/actions/wallet/sendTransactionSync.js +24 -12
  7. package/_cjs/actions/wallet/sendTransactionSync.js.map +1 -1
  8. package/_cjs/chains/definitions/story.js +7 -7
  9. package/_cjs/chains/definitions/story.js.map +1 -1
  10. package/_cjs/chains/definitions/storyAeneid.js +8 -8
  11. package/_cjs/chains/definitions/storyAeneid.js.map +1 -1
  12. package/_cjs/clients/createClient.js +1 -0
  13. package/_cjs/clients/createClient.js.map +1 -1
  14. package/_cjs/errors/typedData.js +11 -1
  15. package/_cjs/errors/typedData.js.map +1 -1
  16. package/_cjs/errors/version.js +1 -1
  17. package/_cjs/index.js +5 -4
  18. package/_cjs/index.js.map +1 -1
  19. package/_cjs/tempo/Abis.js +642 -253
  20. package/_cjs/tempo/Abis.js.map +1 -1
  21. package/_cjs/tempo/Decorator.js +7 -0
  22. package/_cjs/tempo/Decorator.js.map +1 -1
  23. package/_cjs/tempo/actions/earn/deployment.js +654 -0
  24. package/_cjs/tempo/actions/earn/deployment.js.map +1 -0
  25. package/_cjs/tempo/actions/earn.js +60 -17
  26. package/_cjs/tempo/actions/earn.js.map +1 -1
  27. package/_cjs/tempo/actions/token.js +18 -6
  28. package/_cjs/tempo/actions/token.js.map +1 -1
  29. package/_cjs/tempo/actions/zone.js +11 -1
  30. package/_cjs/tempo/actions/zone.js.map +1 -1
  31. package/_cjs/tempo/chainConfig.js +24 -23
  32. package/_cjs/tempo/chainConfig.js.map +1 -1
  33. package/_cjs/tempo/internal/WithdrawalSenderTag.js +2 -2
  34. package/_cjs/tempo/internal/WithdrawalSenderTag.js.map +1 -1
  35. package/_cjs/tempo/zones/Abis.js +17 -0
  36. package/_cjs/tempo/zones/Abis.js.map +1 -1
  37. package/_cjs/tokens/definitions/usdc.js +7 -0
  38. package/_cjs/tokens/definitions/usdc.js.map +1 -1
  39. package/_cjs/utils/promise/withRetry.js +2 -2
  40. package/_cjs/utils/promise/withRetry.js.map +1 -1
  41. package/_cjs/utils/typedData.js +3 -0
  42. package/_cjs/utils/typedData.js.map +1 -1
  43. package/_esm/actions/wallet/prepareTransactionRequest.js +14 -13
  44. package/_esm/actions/wallet/prepareTransactionRequest.js.map +1 -1
  45. package/_esm/actions/wallet/sendTransaction.js +24 -12
  46. package/_esm/actions/wallet/sendTransaction.js.map +1 -1
  47. package/_esm/actions/wallet/sendTransactionSync.js +24 -12
  48. package/_esm/actions/wallet/sendTransactionSync.js.map +1 -1
  49. package/_esm/chains/definitions/story.js +7 -7
  50. package/_esm/chains/definitions/story.js.map +1 -1
  51. package/_esm/chains/definitions/storyAeneid.js +8 -8
  52. package/_esm/chains/definitions/storyAeneid.js.map +1 -1
  53. package/_esm/clients/createClient.js +4 -3
  54. package/_esm/clients/createClient.js.map +1 -1
  55. package/_esm/errors/typedData.js +9 -0
  56. package/_esm/errors/typedData.js.map +1 -1
  57. package/_esm/errors/version.js +1 -1
  58. package/_esm/index.js +1 -1
  59. package/_esm/index.js.map +1 -1
  60. package/_esm/tempo/Abis.js +643 -254
  61. package/_esm/tempo/Abis.js.map +1 -1
  62. package/_esm/tempo/Decorator.js +7 -0
  63. package/_esm/tempo/Decorator.js.map +1 -1
  64. package/_esm/tempo/actions/earn/deployment.js +967 -0
  65. package/_esm/tempo/actions/earn/deployment.js.map +1 -0
  66. package/_esm/tempo/actions/earn.js +47 -17
  67. package/_esm/tempo/actions/earn.js.map +1 -1
  68. package/_esm/tempo/actions/token.js +21 -8
  69. package/_esm/tempo/actions/token.js.map +1 -1
  70. package/_esm/tempo/actions/zone.js +11 -1
  71. package/_esm/tempo/actions/zone.js.map +1 -1
  72. package/_esm/tempo/chainConfig.js +26 -28
  73. package/_esm/tempo/chainConfig.js.map +1 -1
  74. package/_esm/tempo/internal/WithdrawalSenderTag.js +3 -3
  75. package/_esm/tempo/internal/WithdrawalSenderTag.js.map +1 -1
  76. package/_esm/tempo/zones/Abis.js +17 -0
  77. package/_esm/tempo/zones/Abis.js.map +1 -1
  78. package/_esm/tokens/definitions/usdc.js +7 -0
  79. package/_esm/tokens/definitions/usdc.js.map +1 -1
  80. package/_esm/utils/promise/withRetry.js +2 -2
  81. package/_esm/utils/promise/withRetry.js.map +1 -1
  82. package/_esm/utils/typedData.js +4 -1
  83. package/_esm/utils/typedData.js.map +1 -1
  84. package/_types/actions/wallet/prepareTransactionRequest.d.ts.map +1 -1
  85. package/_types/actions/wallet/sendTransaction.d.ts.map +1 -1
  86. package/_types/actions/wallet/sendTransactionSync.d.ts.map +1 -1
  87. package/_types/chains/definitions/story.d.ts +7 -7
  88. package/_types/chains/definitions/storyAeneid.d.ts +8 -8
  89. package/_types/clients/createClient.d.ts +3 -3
  90. package/_types/clients/createClient.d.ts.map +1 -1
  91. package/_types/errors/typedData.d.ts +8 -0
  92. package/_types/errors/typedData.d.ts.map +1 -1
  93. package/_types/errors/version.d.ts +1 -1
  94. package/_types/index.d.ts +1 -1
  95. package/_types/index.d.ts.map +1 -1
  96. package/_types/tempo/Abis.d.ts +945 -388
  97. package/_types/tempo/Abis.d.ts.map +1 -1
  98. package/_types/tempo/Decorator.d.ts +18 -0
  99. package/_types/tempo/Decorator.d.ts.map +1 -1
  100. package/_types/tempo/actions/earn/deployment.d.ts +1710 -0
  101. package/_types/tempo/actions/earn/deployment.d.ts.map +1 -0
  102. package/_types/tempo/actions/earn.d.ts +1142 -535
  103. package/_types/tempo/actions/earn.d.ts.map +1 -1
  104. package/_types/tempo/actions/token.d.ts +1 -1
  105. package/_types/tempo/actions/token.d.ts.map +1 -1
  106. package/_types/tempo/actions/zone.d.ts.map +1 -1
  107. package/_types/tempo/chainConfig.d.ts.map +1 -1
  108. package/_types/tempo/internal/WithdrawalSenderTag.d.ts +2 -1
  109. package/_types/tempo/internal/WithdrawalSenderTag.d.ts.map +1 -1
  110. package/_types/tempo/zones/Abis.d.ts +48 -0
  111. package/_types/tempo/zones/Abis.d.ts.map +1 -1
  112. package/_types/tokens/definitions/usdc.d.ts +7 -0
  113. package/_types/tokens/definitions/usdc.d.ts.map +1 -1
  114. package/_types/tokens/sets.d.ts +14 -0
  115. package/_types/tokens/sets.d.ts.map +1 -1
  116. package/_types/utils/typedData.d.ts.map +1 -1
  117. package/actions/wallet/prepareTransactionRequest.ts +18 -17
  118. package/actions/wallet/sendTransaction.ts +25 -10
  119. package/actions/wallet/sendTransactionSync.ts +25 -10
  120. package/chains/definitions/story.ts +7 -7
  121. package/chains/definitions/storyAeneid.ts +8 -8
  122. package/clients/createClient.ts +4 -3
  123. package/errors/typedData.ts +13 -0
  124. package/errors/version.ts +1 -1
  125. package/index.ts +2 -0
  126. package/package.json +1 -1
  127. package/tempo/Abis.ts +644 -254
  128. package/tempo/Decorator.ts +41 -0
  129. package/tempo/actions/earn/deployment.ts +1427 -0
  130. package/tempo/actions/earn.ts +83 -63
  131. package/tempo/actions/token.ts +60 -9
  132. package/tempo/actions/zone.ts +10 -1
  133. package/tempo/chainConfig.ts +30 -27
  134. package/tempo/internal/WithdrawalSenderTag.ts +5 -4
  135. package/tempo/zones/Abis.ts +17 -0
  136. package/tokens/definitions/usdc.ts +7 -0
  137. package/utils/promise/withRetry.ts +4 -4
  138. package/utils/typedData.ts +5 -0
@@ -0,0 +1,1427 @@
1
+ import type { Address } from 'abitype'
2
+ import { Hex } from 'ox'
3
+ import type { Account } from '../../../accounts/types.js'
4
+ import { parseAccount } from '../../../accounts/utils/parseAccount.js'
5
+ import { getCode } from '../../../actions/public/getCode.js'
6
+ import { getLogs } from '../../../actions/public/getLogs.js'
7
+ import { readContract } from '../../../actions/public/readContract.js'
8
+ import { simulateContract } from '../../../actions/public/simulateContract.js'
9
+ import {
10
+ type WriteContractReturnType,
11
+ writeContract,
12
+ } from '../../../actions/wallet/writeContract.js'
13
+ import { writeContractSync } from '../../../actions/wallet/writeContractSync.js'
14
+ import type { Client } from '../../../clients/createClient.js'
15
+ import type { Transport } from '../../../clients/transports/createTransport.js'
16
+ import { zeroAddress } from '../../../constants/address.js'
17
+ import { maxUint64, maxUint256 } from '../../../constants/number.js'
18
+ import { AccountNotFoundError } from '../../../errors/account.js'
19
+ import { BaseError, type BaseErrorType } from '../../../errors/base.js'
20
+ import { TransactionReceiptRevertedError } from '../../../errors/transaction.js'
21
+ import type { Chain } from '../../../types/chain.js'
22
+ import type { GetEventArgs } from '../../../types/contract.js'
23
+ import type { Log } from '../../../types/log.js'
24
+ import type { Compute } from '../../../types/utils.js'
25
+ import { getAbiItem } from '../../../utils/abi/getAbiItem.js'
26
+ import { parseEventLogs } from '../../../utils/abi/parseEventLogs.js'
27
+ import { isAddressEqual } from '../../../utils/address/isAddressEqual.js'
28
+ import * as Abis from '../../Abis.js'
29
+ import * as Addresses from '../../Addresses.js'
30
+ import type {
31
+ WriteParameters,
32
+ WriteSyncParameters,
33
+ } from '../../internal/types.js'
34
+ import {
35
+ defineCall,
36
+ pickWriteParameters,
37
+ pickWriteSyncParameters,
38
+ } from '../../internal/utils.js'
39
+ import type { TransactionReceipt } from '../../Transaction.js'
40
+
41
+ /** @experimental Factory addresses for one reviewed Tempo Earn release. */
42
+ export type EarnFactoryAddresses = {
43
+ /** `ERC4626EngineFactory` address. */
44
+ erc4626Engine: Address
45
+ /** `EarnFactory` address from the same release. */
46
+ earn: Address
47
+ }
48
+
49
+ /** @experimental Deployment-fixed engine migration policy. */
50
+ export type EngineMigrationMode = 'operatorEnabled' | 'userOnly'
51
+
52
+ /** @experimental Initial controls for an Earn vault. */
53
+ export type EarnVaultControls = {
54
+ /** Request-cancellation liveness seat. @default zero address */
55
+ asyncJanitor?: Address | undefined
56
+ /** Fast pause-only seat. @default zero address */
57
+ emergencyGuardian?: Address | undefined
58
+ /** Maximum actively managed assets. Zero means unlimited. @default 0 */
59
+ maxManagedAssets?: bigint | undefined
60
+ /** Whole-pool migration policy. @default 'userOnly' */
61
+ migrationMode?: EngineMigrationMode | undefined
62
+ }
63
+
64
+ /** @experimental Optional protected fee-distributor configuration. */
65
+ export type EarnDistributorConfiguration = {
66
+ /**
67
+ * Distributor address. The first entry in `fees.fixedFees` is its protected
68
+ * fee.
69
+ */
70
+ distributor: Address
71
+ /** Delay before a distributor fee update can execute, in seconds. */
72
+ updateDelay: number
73
+ }
74
+
75
+ /** @experimental Initial Earn fee configuration. Omit to deploy fee-free. */
76
+ export type EarnFeeConfiguration = {
77
+ /** Optional fee on returns above an annual target. */
78
+ excess?:
79
+ | {
80
+ /** Fee recipient. */
81
+ account: Address
82
+ /** Annual target rate in basis points. */
83
+ annualTargetRateBps: number
84
+ /** Rate charged above the target in basis points. */
85
+ rateBps: number
86
+ }
87
+ | undefined
88
+ /**
89
+ * Fixed fee recipients and rates, limited to four entries. When a
90
+ * distributor is enabled, the first entry is its protected fee and the
91
+ * remaining entries are operator-controlled.
92
+ */
93
+ fixedFees?:
94
+ | readonly {
95
+ /** Fee recipient. */
96
+ account: Address
97
+ /** Fee rate in basis points. */
98
+ rateBps: number
99
+ }[]
100
+ | undefined
101
+ }
102
+
103
+ /**
104
+ * Deploys a deterministic ERC-4626 Earn engine.
105
+ *
106
+ * @experimental
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * import { createClient, http } from 'viem'
111
+ * import { privateKeyToAccount } from 'viem/accounts'
112
+ * import { tempoModerato } from 'viem/chains'
113
+ * import { Actions } from 'viem/tempo'
114
+ *
115
+ * const client = createClient({
116
+ * account: privateKeyToAccount('0x...'),
117
+ * chain: tempoModerato,
118
+ * transport: http(),
119
+ * })
120
+ *
121
+ * const hash = await Actions.earn.createErc4626Engine(client, {
122
+ * deploymentId: '0x...',
123
+ * factory: '0x...',
124
+ * venue: '0x...',
125
+ * })
126
+ * ```
127
+ *
128
+ * @param client - Client.
129
+ * @param parameters - Parameters.
130
+ * @returns The transaction hash.
131
+ */
132
+ export async function createErc4626Engine<
133
+ chain extends Chain | undefined,
134
+ account extends Account | undefined,
135
+ >(
136
+ client: Client<Transport, chain, account>,
137
+ parameters: createErc4626Engine.Parameters<chain, account>,
138
+ ): Promise<createErc4626Engine.ReturnValue> {
139
+ return createErc4626Engine.inner(writeContract, client, parameters)
140
+ }
141
+
142
+ export namespace createErc4626Engine {
143
+ export type Args = {
144
+ /** Stable deterministic deployment identifier. */
145
+ deploymentId: Hex.Hex
146
+ /** Reviewed `ERC4626EngineFactory` address. */
147
+ factory: Address
148
+ /** Optional engine name override. Empty derives the venue name. */
149
+ name?: string | undefined
150
+ /** Final engine owner. */
151
+ owner: Address
152
+ /** Optional engine symbol override. Empty derives the venue symbol. */
153
+ symbol?: string | undefined
154
+ /** ERC-4626 venue address. */
155
+ venue: Address
156
+ }
157
+
158
+ export type Parameters<
159
+ chain extends Chain | undefined = Chain | undefined,
160
+ account extends Account | undefined = Account | undefined,
161
+ > = WriteParameters<chain, account> &
162
+ Omit<Args, 'owner'> & {
163
+ /** Final engine owner. @default `account.address` */
164
+ owner?: Account | Address | undefined
165
+ }
166
+
167
+ export type ReturnValue = WriteContractReturnType
168
+
169
+ // TODO: exhaustive error type
170
+ export type ErrorType = BaseErrorType
171
+
172
+ /** @internal */
173
+ export async function inner<
174
+ action extends typeof writeContract | typeof writeContractSync,
175
+ chain extends Chain | undefined,
176
+ account extends Account | undefined,
177
+ >(
178
+ action: action,
179
+ client: Client<Transport, chain, account>,
180
+ parameters: Parameters<chain, account>,
181
+ ): Promise<ReturnType<action>> {
182
+ const {
183
+ account = client.account,
184
+ chain = client.chain,
185
+ deploymentId,
186
+ factory,
187
+ name,
188
+ owner: owner_ = account,
189
+ symbol,
190
+ venue,
191
+ ...rest
192
+ } = parameters
193
+ if (!account) throw new AccountNotFoundError()
194
+ if (!owner_) throw new Error('`owner` is required.')
195
+ const owner = parseAccount(owner_).address
196
+ return (await action(client, {
197
+ ...rest,
198
+ account,
199
+ chain,
200
+ ...createErc4626Engine.call({
201
+ deploymentId,
202
+ factory,
203
+ name,
204
+ owner,
205
+ symbol,
206
+ venue,
207
+ }),
208
+ } as never)) as never
209
+ }
210
+
211
+ /**
212
+ * Defines a call to `ERC4626EngineFactory.deploy`.
213
+ *
214
+ * Can be passed as a parameter to:
215
+ * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
216
+ * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
217
+ * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * import { createClient, http, walletActions } from 'viem'
222
+ * import { tempoModerato } from 'viem/chains'
223
+ * import { Actions } from 'viem/tempo'
224
+ *
225
+ * const client = createClient({ chain: tempoModerato, transport: http() })
226
+ * .extend(walletActions)
227
+ * await client.sendTransaction({
228
+ * calls: [Actions.earn.createErc4626Engine.call({
229
+ * deploymentId: '0x...',
230
+ * factory: '0x...',
231
+ * owner: '0x...',
232
+ * venue: '0x...',
233
+ * })],
234
+ * })
235
+ * ```
236
+ *
237
+ * @param args - Arguments.
238
+ * @returns The call.
239
+ */
240
+ export function call(args: Args) {
241
+ validateDeploymentId(args.deploymentId)
242
+ return defineCall({
243
+ address: args.factory,
244
+ abi: Abis.erc4626EngineFactory,
245
+ functionName: 'deploy',
246
+ args: [
247
+ args.deploymentId,
248
+ args.venue,
249
+ args.owner,
250
+ args.name ?? '',
251
+ args.symbol ?? '',
252
+ ],
253
+ })
254
+ }
255
+
256
+ /**
257
+ * Predicts the deterministic engine address.
258
+ *
259
+ * @param client - Client.
260
+ * @param args - Engine deployment arguments.
261
+ * @returns The predicted engine address.
262
+ */
263
+ export async function predict<chain extends Chain | undefined>(
264
+ client: Client<Transport, chain>,
265
+ args: Args,
266
+ ) {
267
+ validateDeploymentId(args.deploymentId)
268
+ return readContract(client, {
269
+ address: args.factory,
270
+ abi: Abis.erc4626EngineFactory,
271
+ functionName: 'predictEngine',
272
+ args: [
273
+ args.deploymentId,
274
+ args.venue,
275
+ args.owner,
276
+ args.name ?? '',
277
+ args.symbol ?? '',
278
+ ],
279
+ })
280
+ }
281
+
282
+ /**
283
+ * Extracts the `ERC4626EngineDeployed` event from factory logs.
284
+ *
285
+ * @param logs - The logs.
286
+ * @param parameters - Factory address used to filter the logs.
287
+ * @returns The deployment event.
288
+ */
289
+ export function extractEvent(logs: Log[], parameters: { factory: Address }) {
290
+ const [log] = parseEventLogs({
291
+ abi: Abis.erc4626EngineFactory,
292
+ eventName: 'ERC4626EngineDeployed',
293
+ logs: logs.filter((log) =>
294
+ isAddressEqual(log.address, parameters.factory),
295
+ ),
296
+ strict: true,
297
+ })
298
+ if (!log) throw new Error('`ERC4626EngineDeployed` event not found.')
299
+ return log
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Deploys an ERC-4626 engine and waits for confirmation.
305
+ *
306
+ * @experimental
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * import { createClient, http } from 'viem'
311
+ * import { privateKeyToAccount } from 'viem/accounts'
312
+ * import { tempoModerato } from 'viem/chains'
313
+ * import { Actions } from 'viem/tempo'
314
+ *
315
+ * const client = createClient({
316
+ * account: privateKeyToAccount('0x...'),
317
+ * chain: tempoModerato,
318
+ * transport: http(),
319
+ * })
320
+ * const result = await Actions.earn.createErc4626EngineSync(client, {
321
+ * deploymentId: '0x...',
322
+ * factory: '0x...',
323
+ * venue: '0x...',
324
+ * })
325
+ * ```
326
+ *
327
+ * @param client - Client.
328
+ * @param parameters - Parameters.
329
+ * @returns The receipt and deployed engine metadata.
330
+ */
331
+ export async function createErc4626EngineSync<
332
+ chain extends Chain | undefined,
333
+ account extends Account | undefined,
334
+ >(
335
+ client: Client<Transport, chain, account>,
336
+ parameters: createErc4626EngineSync.Parameters<chain, account>,
337
+ ): Promise<createErc4626EngineSync.ReturnValue> {
338
+ const { factory, throwOnReceiptRevert = true } = parameters
339
+ const receipt = await createErc4626Engine.inner(writeContractSync, client, {
340
+ ...parameters,
341
+ throwOnReceiptRevert,
342
+ } as never)
343
+ const { args } = createErc4626Engine.extractEvent(receipt.logs, { factory })
344
+ return { ...args, receipt }
345
+ }
346
+
347
+ export namespace createErc4626EngineSync {
348
+ export type Args = createErc4626Engine.Args
349
+ export type Parameters<
350
+ chain extends Chain | undefined = Chain | undefined,
351
+ account extends Account | undefined = Account | undefined,
352
+ > = createErc4626Engine.Parameters<chain, account> &
353
+ WriteSyncParameters<chain, account>
354
+ export type ReturnValue = Compute<
355
+ GetEventArgs<
356
+ typeof Abis.erc4626EngineFactory,
357
+ 'ERC4626EngineDeployed',
358
+ { IndexedOnly: false; Required: true }
359
+ > & { receipt: TransactionReceipt }
360
+ >
361
+ // TODO: exhaustive error type
362
+ export type ErrorType = BaseErrorType
363
+ }
364
+
365
+ /**
366
+ * Creates an EarnShare, EarnVault, and EarnFees stack around an unbound engine.
367
+ *
368
+ * @experimental
369
+ *
370
+ * @example
371
+ * ```ts
372
+ * import { createClient, http } from 'viem'
373
+ * import { privateKeyToAccount } from 'viem/accounts'
374
+ * import { tempoModerato } from 'viem/chains'
375
+ * import { Actions } from 'viem/tempo'
376
+ *
377
+ * const client = createClient({
378
+ * account: privateKeyToAccount('0x...'),
379
+ * chain: tempoModerato,
380
+ * transport: http(),
381
+ * })
382
+ * const hash = await Actions.earn.createStack(client, {
383
+ * deploymentId: '0x...',
384
+ * engine: '0x...',
385
+ * factory: '0x...',
386
+ * })
387
+ * ```
388
+ *
389
+ * @param client - Client.
390
+ * @param parameters - Parameters.
391
+ * @returns The transaction hash.
392
+ */
393
+ export async function createStack<
394
+ chain extends Chain | undefined,
395
+ account extends Account | undefined,
396
+ >(
397
+ client: Client<Transport, chain, account>,
398
+ parameters: createStack.Parameters<chain, account>,
399
+ ): Promise<createStack.ReturnValue> {
400
+ return createStack.inner(writeContract, client, parameters)
401
+ }
402
+
403
+ export namespace createStack {
404
+ export type Args = {
405
+ /** Initial Earn vault controls. */
406
+ controls?: EarnVaultControls | undefined
407
+ /** Stable deterministic deployment identifier. */
408
+ deploymentId: Hex.Hex
409
+ /** Optional protected fee distributor. */
410
+ distributor?: EarnDistributorConfiguration | undefined
411
+ /** Engine address. */
412
+ engine: Address
413
+ /** Reviewed `EarnFactory` address. */
414
+ factory: Address
415
+ /** Initial fee configuration. Omit for fee-free deployment. */
416
+ fees?: EarnFeeConfiguration | undefined
417
+ /** Final stack owner and operator. */
418
+ owner: Address
419
+ /** Existing simple whitelist policy. Zero selects always-allow. @default 0 */
420
+ transferPolicyId?: bigint | undefined
421
+ }
422
+
423
+ export type Parameters<
424
+ chain extends Chain | undefined = Chain | undefined,
425
+ account extends Account | undefined = Account | undefined,
426
+ > = WriteParameters<chain, account> &
427
+ Omit<Args, 'owner'> & {
428
+ /** Final stack owner and operator. @default `account.address` */
429
+ owner?: Account | Address | undefined
430
+ }
431
+
432
+ export type ReturnValue = WriteContractReturnType
433
+
434
+ // TODO: exhaustive error type
435
+ export type ErrorType = BaseErrorType
436
+
437
+ /** @internal */
438
+ export async function inner<
439
+ action extends typeof writeContract | typeof writeContractSync,
440
+ chain extends Chain | undefined,
441
+ account extends Account | undefined,
442
+ >(
443
+ action: action,
444
+ client: Client<Transport, chain, account>,
445
+ parameters: Parameters<chain, account>,
446
+ ): Promise<ReturnType<action>> {
447
+ const {
448
+ account = client.account,
449
+ chain = client.chain,
450
+ controls,
451
+ deploymentId,
452
+ distributor,
453
+ engine,
454
+ factory,
455
+ fees,
456
+ owner: owner_ = account,
457
+ transferPolicyId,
458
+ ...rest
459
+ } = parameters
460
+ if (!account) throw new AccountNotFoundError()
461
+ if (!owner_) throw new Error('`owner` is required.')
462
+ return (await action(client, {
463
+ ...rest,
464
+ account,
465
+ chain,
466
+ ...createStack.call({
467
+ controls,
468
+ deploymentId,
469
+ distributor,
470
+ engine,
471
+ factory,
472
+ fees,
473
+ owner: parseAccount(owner_).address,
474
+ transferPolicyId,
475
+ }),
476
+ } as never)) as never
477
+ }
478
+
479
+ /**
480
+ * Defines a call to `EarnFactory.deploy`.
481
+ *
482
+ * Can be passed as a parameter to:
483
+ * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
484
+ * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
485
+ * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls
486
+ *
487
+ * @example
488
+ * ```ts
489
+ * import { createClient, http, walletActions } from 'viem'
490
+ * import { tempoModerato } from 'viem/chains'
491
+ * import { Actions } from 'viem/tempo'
492
+ *
493
+ * const client = createClient({ chain: tempoModerato, transport: http() })
494
+ * .extend(walletActions)
495
+ * await client.sendTransaction({
496
+ * calls: [Actions.earn.createStack.call({
497
+ * deploymentId: '0x...',
498
+ * engine: '0x...',
499
+ * factory: '0x...',
500
+ * owner: '0x...',
501
+ * })],
502
+ * })
503
+ * ```
504
+ *
505
+ * @param args - Arguments.
506
+ * @returns The call.
507
+ */
508
+ export function call(args: Args) {
509
+ validateDeploymentId(args.deploymentId)
510
+ return defineCall({
511
+ address: args.factory,
512
+ abi: Abis.earnFactory,
513
+ functionName: 'deploy',
514
+ args: [toDeployParameters(args)],
515
+ })
516
+ }
517
+
518
+ /**
519
+ * Predicts the deterministic EarnShare and EarnFees addresses.
520
+ *
521
+ * @param client - Client.
522
+ * @param args - Stack deployment arguments.
523
+ * @returns The predicted EarnShare and EarnFees addresses.
524
+ */
525
+ export async function predict<chain extends Chain | undefined>(
526
+ client: Client<Transport, chain>,
527
+ args: Args,
528
+ ) {
529
+ validateDeploymentId(args.deploymentId)
530
+ const parameters = toDeployParameters(args)
531
+ const [earnShare, earnFees] = await Promise.all([
532
+ readContract(client, {
533
+ address: args.factory,
534
+ abi: Abis.earnFactory,
535
+ functionName: 'predictEarnShare',
536
+ args: [parameters],
537
+ }),
538
+ readContract(client, {
539
+ address: args.factory,
540
+ abi: Abis.earnFactory,
541
+ functionName: 'predictEarnFees',
542
+ args: [parameters],
543
+ }),
544
+ ])
545
+ return { earnFees, earnShare }
546
+ }
547
+
548
+ /**
549
+ * Extracts the `EarnStackDeployed` event from factory logs.
550
+ *
551
+ * @param logs - The logs.
552
+ * @param parameters - Factory address used to filter the logs.
553
+ * @returns The deployment event.
554
+ */
555
+ export function extractEvent(logs: Log[], parameters: { factory: Address }) {
556
+ const [log] = parseEventLogs({
557
+ abi: Abis.earnFactory,
558
+ eventName: 'EarnStackDeployed',
559
+ logs: logs.filter((log) =>
560
+ isAddressEqual(log.address, parameters.factory),
561
+ ),
562
+ strict: true,
563
+ })
564
+ if (!log) throw new Error('`EarnStackDeployed` event not found.')
565
+ return log
566
+ }
567
+ }
568
+
569
+ /**
570
+ * Creates an Earn core stack and waits for confirmation.
571
+ *
572
+ * @experimental
573
+ *
574
+ * @example
575
+ * ```ts
576
+ * import { createClient, http } from 'viem'
577
+ * import { privateKeyToAccount } from 'viem/accounts'
578
+ * import { tempoModerato } from 'viem/chains'
579
+ * import { Actions } from 'viem/tempo'
580
+ *
581
+ * const client = createClient({
582
+ * account: privateKeyToAccount('0x...'),
583
+ * chain: tempoModerato,
584
+ * transport: http(),
585
+ * })
586
+ * const result = await Actions.earn.createStackSync(client, {
587
+ * deploymentId: '0x...',
588
+ * engine: '0x...',
589
+ * factory: '0x...',
590
+ * })
591
+ * ```
592
+ *
593
+ * @param client - Client.
594
+ * @param parameters - Parameters.
595
+ * @returns The receipt and deployed stack addresses.
596
+ */
597
+ export async function createStackSync<
598
+ chain extends Chain | undefined,
599
+ account extends Account | undefined,
600
+ >(
601
+ client: Client<Transport, chain, account>,
602
+ parameters: createStackSync.Parameters<chain, account>,
603
+ ): Promise<createStackSync.ReturnValue> {
604
+ const { factory, throwOnReceiptRevert = true } = parameters
605
+ const receipt = await createStack.inner(writeContractSync, client, {
606
+ ...parameters,
607
+ throwOnReceiptRevert,
608
+ } as never)
609
+ const { args } = createStack.extractEvent(receipt.logs, { factory })
610
+ return { ...args, receipt }
611
+ }
612
+
613
+ export namespace createStackSync {
614
+ export type Args = createStack.Args
615
+ export type Parameters<
616
+ chain extends Chain | undefined = Chain | undefined,
617
+ account extends Account | undefined = Account | undefined,
618
+ > = createStack.Parameters<chain, account> &
619
+ WriteSyncParameters<chain, account>
620
+ export type ReturnValue = Compute<
621
+ GetEventArgs<
622
+ typeof Abis.earnFactory,
623
+ 'EarnStackDeployed',
624
+ { IndexedOnly: false; Required: true }
625
+ > & { receipt: TransactionReceipt }
626
+ >
627
+ // TODO: exhaustive error type
628
+ export type ErrorType = BaseErrorType
629
+ }
630
+
631
+ /**
632
+ * Permanently binds an ERC-4626 engine to its EarnVault.
633
+ *
634
+ * @experimental
635
+ *
636
+ * @example
637
+ * ```ts
638
+ * import { createClient, http } from 'viem'
639
+ * import { privateKeyToAccount } from 'viem/accounts'
640
+ * import { tempoModerato } from 'viem/chains'
641
+ * import { Actions } from 'viem/tempo'
642
+ *
643
+ * const client = createClient({
644
+ * account: privateKeyToAccount('0x...'),
645
+ * chain: tempoModerato,
646
+ * transport: http(),
647
+ * })
648
+ * const hash = await Actions.earn.bindErc4626Engine(client, {
649
+ * engine: '0x...',
650
+ * vault: '0x...',
651
+ * })
652
+ * ```
653
+ *
654
+ * @param client - Client controlled by the final engine owner.
655
+ * @param parameters - Parameters.
656
+ * @returns The transaction hash.
657
+ */
658
+ export async function bindErc4626Engine<
659
+ chain extends Chain | undefined,
660
+ account extends Account | undefined,
661
+ >(
662
+ client: Client<Transport, chain, account>,
663
+ parameters: bindErc4626Engine.Parameters<chain, account>,
664
+ ): Promise<bindErc4626Engine.ReturnValue> {
665
+ return bindErc4626Engine.inner(writeContract, client, parameters)
666
+ }
667
+
668
+ export namespace bindErc4626Engine {
669
+ export type Args = {
670
+ /** ERC-4626 engine address. */
671
+ engine: Address
672
+ /** Factory-created EarnVault address. */
673
+ vault: Address
674
+ }
675
+ export type Parameters<
676
+ chain extends Chain | undefined = Chain | undefined,
677
+ account extends Account | undefined = Account | undefined,
678
+ > = WriteParameters<chain, account> & Args
679
+ export type ReturnValue = WriteContractReturnType
680
+ // TODO: exhaustive error type
681
+ export type ErrorType = BaseErrorType
682
+
683
+ /** @internal */
684
+ export async function inner<
685
+ action extends typeof writeContract | typeof writeContractSync,
686
+ chain extends Chain | undefined,
687
+ account extends Account | undefined,
688
+ >(
689
+ action: action,
690
+ client: Client<Transport, chain, account>,
691
+ parameters: Parameters<chain, account>,
692
+ ): Promise<ReturnType<action>> {
693
+ const { engine, vault, ...rest } = parameters
694
+ return (await action(client, {
695
+ ...rest,
696
+ ...bindErc4626Engine.call({ engine, vault }),
697
+ } as never)) as never
698
+ }
699
+
700
+ /**
701
+ * Defines a call to `ERC4626Engine.initializeEarnVault`.
702
+ *
703
+ * Can be passed as a parameter to:
704
+ * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
705
+ * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
706
+ * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls
707
+ *
708
+ * @example
709
+ * ```ts
710
+ * import { createClient, http, walletActions } from 'viem'
711
+ * import { tempoModerato } from 'viem/chains'
712
+ * import { Actions } from 'viem/tempo'
713
+ *
714
+ * const client = createClient({ chain: tempoModerato, transport: http() })
715
+ * .extend(walletActions)
716
+ * await client.sendTransaction({
717
+ * calls: [Actions.earn.bindErc4626Engine.call({
718
+ * engine: '0x...',
719
+ * vault: '0x...',
720
+ * })],
721
+ * })
722
+ * ```
723
+ *
724
+ * @param args - Arguments.
725
+ * @returns The call.
726
+ */
727
+ export function call(args: Args) {
728
+ return defineCall({
729
+ address: args.engine,
730
+ abi: Abis.erc4626Engine,
731
+ functionName: 'initializeEarnVault',
732
+ args: [args.vault],
733
+ })
734
+ }
735
+
736
+ /**
737
+ * Extracts the `EarnVaultInitialized` event from engine logs.
738
+ *
739
+ * @param logs - The logs.
740
+ * @param parameters - Engine address used to filter the logs.
741
+ * @returns The initialization event.
742
+ */
743
+ export function extractEvent(logs: Log[], parameters: { engine: Address }) {
744
+ const [log] = parseEventLogs({
745
+ abi: Abis.erc4626Engine,
746
+ eventName: 'EarnVaultInitialized',
747
+ logs: logs.filter((log) =>
748
+ isAddressEqual(log.address, parameters.engine),
749
+ ),
750
+ strict: true,
751
+ })
752
+ if (!log) throw new Error('`EarnVaultInitialized` event not found.')
753
+ return log
754
+ }
755
+ }
756
+
757
+ /**
758
+ * Binds an ERC-4626 engine and waits for confirmation.
759
+ *
760
+ * @experimental
761
+ *
762
+ * @example
763
+ * ```ts
764
+ * import { createClient, http } from 'viem'
765
+ * import { privateKeyToAccount } from 'viem/accounts'
766
+ * import { tempoModerato } from 'viem/chains'
767
+ * import { Actions } from 'viem/tempo'
768
+ *
769
+ * const client = createClient({
770
+ * account: privateKeyToAccount('0x...'),
771
+ * chain: tempoModerato,
772
+ * transport: http(),
773
+ * })
774
+ * const result = await Actions.earn.bindErc4626EngineSync(client, {
775
+ * engine: '0x...',
776
+ * vault: '0x...',
777
+ * })
778
+ * ```
779
+ *
780
+ * @param client - Client controlled by the final engine owner.
781
+ * @param parameters - Parameters.
782
+ * @returns The receipt and bound addresses.
783
+ */
784
+ export async function bindErc4626EngineSync<
785
+ chain extends Chain | undefined,
786
+ account extends Account | undefined,
787
+ >(
788
+ client: Client<Transport, chain, account>,
789
+ parameters: bindErc4626EngineSync.Parameters<chain, account>,
790
+ ): Promise<bindErc4626EngineSync.ReturnValue> {
791
+ const { engine, throwOnReceiptRevert = true } = parameters
792
+ const receipt = await bindErc4626Engine.inner(writeContractSync, client, {
793
+ ...parameters,
794
+ throwOnReceiptRevert,
795
+ } as never)
796
+ const { args } = bindErc4626Engine.extractEvent(receipt.logs, { engine })
797
+ return { engine, vault: args.earnVault, receipt }
798
+ }
799
+
800
+ export namespace bindErc4626EngineSync {
801
+ export type Args = bindErc4626Engine.Args
802
+ export type Parameters<
803
+ chain extends Chain | undefined = Chain | undefined,
804
+ account extends Account | undefined = Account | undefined,
805
+ > = bindErc4626Engine.Parameters<chain, account> &
806
+ WriteSyncParameters<chain, account>
807
+ export type ReturnValue = {
808
+ /** ERC-4626 engine address. */
809
+ engine: Address
810
+ /** Transaction receipt. */
811
+ receipt: TransactionReceipt
812
+ /** Bound EarnVault address. */
813
+ vault: Address
814
+ }
815
+ // TODO: exhaustive error type
816
+ export type ErrorType = BaseErrorType
817
+ }
818
+
819
+ export type DeployErc4626StackErrorType = DeployErc4626StackError & {
820
+ name: 'DeployErc4626StackError'
821
+ }
822
+
823
+ /**
824
+ * Error thrown after a partially completed Earn stack deployment.
825
+ *
826
+ * @experimental
827
+ */
828
+ export class DeployErc4626StackError extends BaseError {
829
+ receipts: deployErc4626StackSync.Receipts
830
+ stage: deployErc4626StackSync.Stage
831
+ state: deployErc4626StackSync.State
832
+
833
+ constructor(
834
+ cause: Error,
835
+ parameters: {
836
+ receipts: deployErc4626StackSync.Receipts
837
+ stage: deployErc4626StackSync.Stage
838
+ state: deployErc4626StackSync.State
839
+ },
840
+ ) {
841
+ super(`ERC-4626 Earn deployment failed during ${parameters.stage}.`, {
842
+ cause,
843
+ name: 'DeployErc4626StackError',
844
+ })
845
+ this.receipts = parameters.receipts
846
+ this.stage = parameters.stage
847
+ this.state = parameters.state
848
+ }
849
+ }
850
+
851
+ /**
852
+ * Deploys and binds a complete ERC-4626 Earn stack through sequential,
853
+ * resumable transactions.
854
+ *
855
+ * @experimental
856
+ *
857
+ * @example
858
+ * ```ts
859
+ * import { createClient, http } from 'viem'
860
+ * import { privateKeyToAccount } from 'viem/accounts'
861
+ * import { tempoModerato } from 'viem/chains'
862
+ * import { Actions } from 'viem/tempo'
863
+ *
864
+ * const client = createClient({
865
+ * account: privateKeyToAccount('0x...'),
866
+ * chain: tempoModerato,
867
+ * transport: http(),
868
+ * })
869
+ * const result = await Actions.earn.deployErc4626StackSync(client, {
870
+ * deploymentId: '0x...',
871
+ * factories: { earn: '0x...', erc4626Engine: '0x...' },
872
+ * venue: '0x...',
873
+ * })
874
+ * ```
875
+ *
876
+ * @param client - Client.
877
+ * @param parameters - Parameters.
878
+ * @returns The deployed addresses and receipts created by this run.
879
+ */
880
+ export async function deployErc4626StackSync<
881
+ chain extends Chain | undefined,
882
+ account extends Account | undefined,
883
+ >(
884
+ client: Client<Transport, chain, account>,
885
+ parameters: deployErc4626StackSync.Parameters<chain, account>,
886
+ ): Promise<deployErc4626StackSync.ReturnValue> {
887
+ const deployer = parameters.account ?? client.account
888
+ if (!deployer) throw new AccountNotFoundError()
889
+ const owner_ = parameters.owner ?? deployer
890
+ const owner = parseAccount(owner_).address
891
+ const deployerAddress = parseAccount(deployer).address
892
+ const bindingAccount = (() => {
893
+ if (parameters.bindingAccount) return parameters.bindingAccount
894
+ if (typeof owner_ !== 'string') return owner_
895
+ if (isAddressEqual(owner, deployerAddress)) return deployer
896
+ return undefined
897
+ })()
898
+ if (
899
+ bindingAccount &&
900
+ !isAddressEqual(parseAccount(bindingAccount).address, owner)
901
+ )
902
+ throw new Error('`bindingAccount` must match `owner`.')
903
+
904
+ validateDeploymentId(parameters.deploymentId)
905
+ if (
906
+ parameters.resume &&
907
+ parameters.resume.deploymentId.toLowerCase() !==
908
+ parameters.deploymentId.toLowerCase()
909
+ )
910
+ throw new Error('The resumed deployment ID does not match `deploymentId`.')
911
+ await validateContracts(client, {
912
+ factories: parameters.factories,
913
+ venue: parameters.venue,
914
+ })
915
+
916
+ const {
917
+ gas: _,
918
+ keyAuthorization: __,
919
+ nonce: ___,
920
+ ...sharedWriteParameters
921
+ } = pickWriteParameters(parameters as never)
922
+ const writeParameters = {
923
+ ...sharedWriteParameters,
924
+ ...pickWriteSyncParameters(parameters as never),
925
+ account: deployer,
926
+ throwOnReceiptRevert: true,
927
+ }
928
+ const engineArgs = {
929
+ deploymentId: parameters.deploymentId,
930
+ factory: parameters.factories.erc4626Engine,
931
+ name: parameters.name,
932
+ owner,
933
+ symbol: parameters.symbol,
934
+ venue: parameters.venue,
935
+ } satisfies createErc4626Engine.Args
936
+ const predictedEngine = await createErc4626Engine.predict(client, engineArgs)
937
+ const state: deployErc4626StackSync.State = {
938
+ deploymentId: parameters.deploymentId,
939
+ earnShare: parameters.resume?.earnShare,
940
+ engine: predictedEngine,
941
+ fees: parameters.resume?.fees,
942
+ vault: parameters.resume?.vault,
943
+ }
944
+ if (
945
+ parameters.resume?.engine &&
946
+ !isAddressEqual(parameters.resume.engine, predictedEngine)
947
+ )
948
+ throw new Error('The resumed engine does not match the factory prediction.')
949
+
950
+ const engineExists = await hasCode(client, predictedEngine)
951
+ if (!bindingAccount) {
952
+ const boundVault = engineExists
953
+ ? await readContract(client, {
954
+ address: predictedEngine,
955
+ abi: Abis.erc4626Engine,
956
+ functionName: 'earnVault',
957
+ })
958
+ : zeroAddress
959
+ if (isAddressEqual(boundVault, zeroAddress))
960
+ throw new Error(
961
+ '`bindingAccount` is required when the final owner differs from the deployment account.',
962
+ )
963
+ }
964
+
965
+ const receipts: deployErc4626StackSync.Receipts = {}
966
+ try {
967
+ if (!engineExists) {
968
+ await simulateContract(client, {
969
+ ...sharedWriteParameters,
970
+ account: deployer,
971
+ ...createErc4626Engine.call(engineArgs),
972
+ } as never)
973
+ const receipt = await createErc4626Engine.inner(
974
+ writeContractSync,
975
+ client,
976
+ {
977
+ ...writeParameters,
978
+ ...engineArgs,
979
+ } as never,
980
+ )
981
+ receipts.engine = receipt
982
+ createErc4626Engine.extractEvent(receipt.logs, {
983
+ factory: parameters.factories.erc4626Engine,
984
+ })
985
+ }
986
+ await verifyEngine(client, engineArgs, predictedEngine)
987
+ } catch (error) {
988
+ throw deploymentError(error, 'engine', state, receipts)
989
+ }
990
+
991
+ const stackArgs = {
992
+ controls: parameters.controls,
993
+ deploymentId: parameters.deploymentId,
994
+ distributor: parameters.distributor,
995
+ engine: predictedEngine,
996
+ factory: parameters.factories.earn,
997
+ fees: parameters.fees,
998
+ owner,
999
+ transferPolicyId: parameters.transferPolicyId,
1000
+ } satisfies createStack.Args
1001
+
1002
+ try {
1003
+ const predicted = await createStack.predict(client, stackArgs)
1004
+ if (
1005
+ parameters.resume?.earnShare &&
1006
+ !isAddressEqual(parameters.resume.earnShare, predicted.earnShare)
1007
+ )
1008
+ throw new Error(
1009
+ 'The resumed EarnShare does not match the factory prediction.',
1010
+ )
1011
+ if (
1012
+ parameters.resume?.fees &&
1013
+ !isAddressEqual(parameters.resume.fees, predicted.earnFees)
1014
+ )
1015
+ throw new Error(
1016
+ 'The resumed EarnFees does not match the factory prediction.',
1017
+ )
1018
+ state.earnShare = predicted.earnShare
1019
+ state.fees = predicted.earnFees
1020
+
1021
+ if (!(await hasCode(client, predicted.earnShare))) {
1022
+ if (await hasCode(client, predicted.earnFees))
1023
+ throw new Error(
1024
+ 'Predicted EarnFees exists without the predicted EarnShare.',
1025
+ )
1026
+ await simulateContract(client, {
1027
+ ...sharedWriteParameters,
1028
+ account: deployer,
1029
+ ...createStack.call(stackArgs),
1030
+ } as never)
1031
+ const receipt = await createStack.inner(writeContractSync, client, {
1032
+ ...writeParameters,
1033
+ ...stackArgs,
1034
+ } as never)
1035
+ receipts.stack = receipt
1036
+ const { args } = createStack.extractEvent(receipt.logs, {
1037
+ factory: parameters.factories.earn,
1038
+ })
1039
+ state.vault = args.earnVault
1040
+ if (
1041
+ parameters.resume?.vault &&
1042
+ !isAddressEqual(parameters.resume.vault, args.earnVault)
1043
+ )
1044
+ throw new Error(
1045
+ 'The resumed EarnVault does not match the factory deployment.',
1046
+ )
1047
+ } else {
1048
+ if (!(await hasCode(client, predicted.earnFees)))
1049
+ throw new Error(
1050
+ 'Predicted EarnShare exists without the predicted EarnFees.',
1051
+ )
1052
+ state.vault = parameters.resume?.vault
1053
+ if (!state.vault) {
1054
+ const event = await findStackDeployment(client, {
1055
+ earnShare: predicted.earnShare,
1056
+ factory: parameters.factories.earn,
1057
+ fromBlock: parameters.fromBlock,
1058
+ })
1059
+ state.vault = event.args.earnVault
1060
+ if (!isAddressEqual(event.args.earnFees, predicted.earnFees))
1061
+ throw new Error(
1062
+ 'Recovered EarnFees does not match the factory prediction.',
1063
+ )
1064
+ }
1065
+ }
1066
+ if (!state.vault) throw new Error('EarnVault address was not recovered.')
1067
+ await verifyStack(client, {
1068
+ engine: predictedEngine,
1069
+ fees: predicted.earnFees,
1070
+ owner,
1071
+ share: predicted.earnShare,
1072
+ vault: state.vault,
1073
+ })
1074
+ } catch (error) {
1075
+ throw deploymentError(error, 'stack', state, receipts)
1076
+ }
1077
+
1078
+ try {
1079
+ const vault = state.vault!
1080
+ const boundVault = await readContract(client, {
1081
+ address: predictedEngine,
1082
+ abi: Abis.erc4626Engine,
1083
+ functionName: 'earnVault',
1084
+ })
1085
+ if (isAddressEqual(boundVault, zeroAddress)) {
1086
+ if (!bindingAccount)
1087
+ throw new Error(
1088
+ '`bindingAccount` is required when the final owner differs from the deployment account.',
1089
+ )
1090
+ const bindingParameters = {
1091
+ ...writeParameters,
1092
+ account: bindingAccount,
1093
+ engine: predictedEngine,
1094
+ vault,
1095
+ }
1096
+ await simulateContract(client, {
1097
+ ...sharedWriteParameters,
1098
+ account: bindingAccount,
1099
+ ...bindErc4626Engine.call({ engine: predictedEngine, vault }),
1100
+ } as never)
1101
+ const receipt = await bindErc4626Engine.inner(
1102
+ writeContractSync,
1103
+ client,
1104
+ bindingParameters as never,
1105
+ )
1106
+ receipts.binding = receipt
1107
+ bindErc4626Engine.extractEvent(receipt.logs, { engine: predictedEngine })
1108
+ } else if (!isAddressEqual(boundVault, vault)) {
1109
+ throw new Error(`Engine is already bound to ${boundVault}.`)
1110
+ }
1111
+ const verified = await readContract(client, {
1112
+ address: predictedEngine,
1113
+ abi: Abis.erc4626Engine,
1114
+ functionName: 'earnVault',
1115
+ })
1116
+ if (!isAddressEqual(verified, vault))
1117
+ throw new Error('Engine binding verification failed.')
1118
+ } catch (error) {
1119
+ throw deploymentError(error, 'binding', state, receipts)
1120
+ }
1121
+
1122
+ return {
1123
+ deploymentId: parameters.deploymentId,
1124
+ earnShare: state.earnShare!,
1125
+ engine: predictedEngine,
1126
+ fees: state.fees!,
1127
+ receipts,
1128
+ vault: state.vault!,
1129
+ }
1130
+ }
1131
+
1132
+ export namespace deployErc4626StackSync {
1133
+ export type Parameters<
1134
+ chain extends Chain | undefined = Chain | undefined,
1135
+ account extends Account | undefined = Account | undefined,
1136
+ > = Omit<
1137
+ WriteParameters<chain, account>,
1138
+ 'gas' | 'keyAuthorization' | 'nonce' | 'throwOnReceiptRevert'
1139
+ > &
1140
+ WriteSyncParameters<chain, account> & {
1141
+ /** Account used only for the final owner binding. */
1142
+ bindingAccount?: Account | Address | undefined
1143
+ /** Initial Earn vault controls. */
1144
+ controls?: EarnVaultControls | undefined
1145
+ /** Stable deterministic deployment identifier. */
1146
+ deploymentId: Hex.Hex
1147
+ /** Optional protected fee distributor. */
1148
+ distributor?: EarnDistributorConfiguration | undefined
1149
+ /** Reviewed factory pair from one Earn release. */
1150
+ factories: EarnFactoryAddresses
1151
+ /** Initial fee configuration. Omit for fee-free deployment. */
1152
+ fees?: EarnFeeConfiguration | undefined
1153
+ /** First block to search for a prior factory deployment event. */
1154
+ fromBlock?: bigint | undefined
1155
+ /** Optional engine name override. */
1156
+ name?: string | undefined
1157
+ /** Final stack owner and operator. @default `account.address` */
1158
+ owner?: Account | Address | undefined
1159
+ /** Previously persisted deployment state. */
1160
+ resume?: State | undefined
1161
+ /** Optional engine symbol override. */
1162
+ symbol?: string | undefined
1163
+ /** Existing simple whitelist policy. Zero selects always-allow. */
1164
+ transferPolicyId?: bigint | undefined
1165
+ /** ERC-4626 venue address. */
1166
+ venue: Address
1167
+ }
1168
+
1169
+ export type Stage = 'binding' | 'engine' | 'stack'
1170
+
1171
+ export type State = {
1172
+ deploymentId: Hex.Hex
1173
+ earnShare?: Address | undefined
1174
+ engine: Address
1175
+ fees?: Address | undefined
1176
+ vault?: Address | undefined
1177
+ }
1178
+
1179
+ export type Receipts = {
1180
+ binding?: TransactionReceipt | undefined
1181
+ engine?: TransactionReceipt | undefined
1182
+ stack?: TransactionReceipt | undefined
1183
+ }
1184
+
1185
+ export type ReturnValue = {
1186
+ deploymentId: Hex.Hex
1187
+ earnShare: Address
1188
+ engine: Address
1189
+ fees: Address
1190
+ receipts: Receipts
1191
+ vault: Address
1192
+ }
1193
+
1194
+ export type ErrorType = DeployErc4626StackErrorType | BaseErrorType
1195
+ }
1196
+
1197
+ function validateDeploymentId(deploymentId: Hex.Hex) {
1198
+ if (Hex.size(deploymentId) !== 32 || BigInt(deploymentId) === 0n)
1199
+ throw new Error('`deploymentId` must be a nonzero 32-byte hex value.')
1200
+ }
1201
+
1202
+ function toDeployParameters(args: createStack.Args) {
1203
+ const fixedFees = args.fees?.fixedFees ?? []
1204
+ if (fixedFees.length > 4)
1205
+ throw new Error('Earn supports at most four fixed fee recipients.')
1206
+ const seen = new Set<string>()
1207
+ let totalRateBps = 0
1208
+ for (const fee of fixedFees) {
1209
+ validateFeeRate(fee.rateBps, '`fixedFees[].rateBps`', false)
1210
+ if (isAddressEqual(fee.account, zeroAddress))
1211
+ throw new Error('Fixed fee recipients cannot be the zero address.')
1212
+ const account = fee.account.toLowerCase()
1213
+ if (seen.has(account))
1214
+ throw new Error('Fixed fee recipients must be unique.')
1215
+ seen.add(account)
1216
+ totalRateBps += fee.rateBps
1217
+ }
1218
+ if (totalRateBps > 10_000)
1219
+ throw new Error('The total fixed fee rate cannot exceed 10,000 bps.')
1220
+
1221
+ const excess = args.fees?.excess
1222
+ if (excess) {
1223
+ validateFeeRate(excess.annualTargetRateBps, '`annualTargetRateBps`', true)
1224
+ validateFeeRate(excess.rateBps, '`excess.rateBps`', false)
1225
+ if (isAddressEqual(excess.account, zeroAddress))
1226
+ throw new Error('The excess fee recipient cannot be the zero address.')
1227
+ }
1228
+ const zeroFee = { account: zeroAddress, rateBps: 0 } as const
1229
+ const distributor = args.distributor?.distributor ?? zeroAddress
1230
+ const updateDelay = args.distributor?.updateDelay ?? 0
1231
+ if (args.distributor) {
1232
+ if (isAddressEqual(distributor, zeroAddress))
1233
+ throw new Error('An enabled distributor cannot be the zero address.')
1234
+ if (updateDelay <= 0)
1235
+ throw new Error(
1236
+ 'An enabled distributor requires a positive update delay.',
1237
+ )
1238
+ if (fixedFees.length === 0)
1239
+ throw new Error('An enabled distributor requires at least one fixed fee.')
1240
+ }
1241
+ if (!Number.isSafeInteger(updateDelay) || updateDelay > 0xffffffffff)
1242
+ throw new Error('`updateDelay` must fit into uint40 seconds.')
1243
+ const transferPolicyId = args.transferPolicyId ?? 0n
1244
+ if (transferPolicyId < 0n || transferPolicyId > maxUint64)
1245
+ throw new Error('`transferPolicyId` must fit into uint64.')
1246
+ const maxManagedAssets = args.controls?.maxManagedAssets ?? 0n
1247
+ if (maxManagedAssets < 0n || maxManagedAssets > maxUint256)
1248
+ throw new Error('`maxManagedAssets` must fit into uint256.')
1249
+
1250
+ return {
1251
+ controls: {
1252
+ asyncJanitor: args.controls?.asyncJanitor ?? zeroAddress,
1253
+ emergencyGuardian: args.controls?.emergencyGuardian ?? zeroAddress,
1254
+ maxManagedAssets,
1255
+ migrationMode:
1256
+ (args.controls?.migrationMode ?? 'userOnly') === 'userOnly' ? 0 : 1,
1257
+ },
1258
+ deploymentId: args.deploymentId,
1259
+ distributorConfig: { distributor, updateDelay },
1260
+ engine: args.engine,
1261
+ fees: {
1262
+ excess: excess
1263
+ ? {
1264
+ account: excess.account,
1265
+ annualTargetRateBps: excess.annualTargetRateBps,
1266
+ enabled: true,
1267
+ excessFeeRateBps: excess.rateBps,
1268
+ }
1269
+ : {
1270
+ account: zeroAddress,
1271
+ annualTargetRateBps: 0,
1272
+ enabled: false,
1273
+ excessFeeRateBps: 0,
1274
+ },
1275
+ fixedFeeCount: fixedFees.length,
1276
+ fixedFees: [
1277
+ fixedFees[0] ?? zeroFee,
1278
+ fixedFees[1] ?? zeroFee,
1279
+ fixedFees[2] ?? zeroFee,
1280
+ fixedFees[3] ?? zeroFee,
1281
+ ],
1282
+ },
1283
+ owner: args.owner,
1284
+ transferPolicyId,
1285
+ } as const
1286
+ }
1287
+
1288
+ function validateFeeRate(rate: number, name: string, allowZero: boolean) {
1289
+ if (!Number.isInteger(rate) || rate < (allowZero ? 0 : 1) || rate > 10_000)
1290
+ throw new Error(
1291
+ `${name} must be an integer between ${allowZero ? 0 : 1} and 10,000.`,
1292
+ )
1293
+ }
1294
+
1295
+ async function hasCode(
1296
+ client: Client<Transport, Chain | undefined>,
1297
+ address: Address,
1298
+ ) {
1299
+ const code = await getCode(client, { address })
1300
+ return code !== undefined && code !== '0x'
1301
+ }
1302
+
1303
+ async function validateContracts(
1304
+ client: Client<Transport, Chain | undefined>,
1305
+ parameters: { factories: EarnFactoryAddresses; venue: Address },
1306
+ ) {
1307
+ const [engineFactory, earnFactory, venue] = await Promise.all([
1308
+ hasCode(client, parameters.factories.erc4626Engine),
1309
+ hasCode(client, parameters.factories.earn),
1310
+ hasCode(client, parameters.venue),
1311
+ ])
1312
+ if (!engineFactory) throw new Error('ERC4626EngineFactory has no code.')
1313
+ if (!earnFactory) throw new Error('EarnFactory has no code.')
1314
+ if (!venue) throw new Error('ERC-4626 venue has no code.')
1315
+ const tip20Factory = await readContract(client, {
1316
+ address: parameters.factories.earn,
1317
+ abi: Abis.earnFactory,
1318
+ functionName: 'tip20Factory',
1319
+ })
1320
+ if (!isAddressEqual(tip20Factory, Addresses.tip20Factory))
1321
+ throw new Error('EarnFactory uses an unexpected TIP-20 factory.')
1322
+ }
1323
+
1324
+ async function verifyEngine(
1325
+ client: Client<Transport, Chain | undefined>,
1326
+ args: createErc4626Engine.Args,
1327
+ engine: Address,
1328
+ ) {
1329
+ const [venue, owner] = await Promise.all([
1330
+ readContract(client, {
1331
+ address: engine,
1332
+ abi: Abis.erc4626Engine,
1333
+ functionName: 'vault',
1334
+ }),
1335
+ readContract(client, {
1336
+ address: engine,
1337
+ abi: Abis.erc4626Engine,
1338
+ functionName: 'owner',
1339
+ }),
1340
+ ])
1341
+ if (!isAddressEqual(venue, args.venue))
1342
+ throw new Error('Engine venue verification failed.')
1343
+ if (!isAddressEqual(owner, args.owner))
1344
+ throw new Error('Engine owner verification failed.')
1345
+ }
1346
+
1347
+ async function verifyStack(
1348
+ client: Client<Transport, Chain | undefined>,
1349
+ parameters: {
1350
+ engine: Address
1351
+ fees: Address
1352
+ owner: Address
1353
+ share: Address
1354
+ vault: Address
1355
+ },
1356
+ ) {
1357
+ const [engine, fees, operator, share] = await Promise.all([
1358
+ readContract(client, {
1359
+ address: parameters.vault,
1360
+ abi: Abis.earnVault,
1361
+ functionName: 'engine',
1362
+ }),
1363
+ readContract(client, {
1364
+ address: parameters.vault,
1365
+ abi: Abis.earnVault,
1366
+ functionName: 'earnFees',
1367
+ }),
1368
+ readContract(client, {
1369
+ address: parameters.vault,
1370
+ abi: Abis.earnVault,
1371
+ functionName: 'operator',
1372
+ }),
1373
+ readContract(client, {
1374
+ address: parameters.vault,
1375
+ abi: Abis.earnVault,
1376
+ functionName: 'earnShare',
1377
+ }),
1378
+ ])
1379
+ if (!isAddressEqual(engine, parameters.engine))
1380
+ throw new Error('EarnVault engine verification failed.')
1381
+ if (!isAddressEqual(fees, parameters.fees))
1382
+ throw new Error('EarnVault fees verification failed.')
1383
+ if (!isAddressEqual(operator, parameters.owner))
1384
+ throw new Error('EarnVault operator verification failed.')
1385
+ if (!isAddressEqual(share, parameters.share))
1386
+ throw new Error('EarnVault share verification failed.')
1387
+ }
1388
+
1389
+ async function findStackDeployment(
1390
+ client: Client<Transport, Chain | undefined>,
1391
+ parameters: {
1392
+ earnShare: Address
1393
+ factory: Address
1394
+ fromBlock?: bigint | undefined
1395
+ },
1396
+ ) {
1397
+ const event = getAbiItem({
1398
+ abi: Abis.earnFactory,
1399
+ name: 'EarnStackDeployed',
1400
+ })
1401
+ const logs = await getLogs(client, {
1402
+ address: parameters.factory,
1403
+ args: { earnShare: parameters.earnShare },
1404
+ event,
1405
+ fromBlock: parameters.fromBlock ?? 0n,
1406
+ strict: true,
1407
+ toBlock: 'latest',
1408
+ })
1409
+ const log = logs.at(-1)
1410
+ if (!log) throw new Error('Prior `EarnStackDeployed` event not found.')
1411
+ return log
1412
+ }
1413
+
1414
+ function deploymentError(
1415
+ error: unknown,
1416
+ stage: deployErc4626StackSync.Stage,
1417
+ state: deployErc4626StackSync.State,
1418
+ receipts: deployErc4626StackSync.Receipts,
1419
+ ) {
1420
+ if (error instanceof DeployErc4626StackError) return error
1421
+ if (error instanceof TransactionReceiptRevertedError)
1422
+ receipts[stage] = error.receipt
1423
+ return new DeployErc4626StackError(
1424
+ error instanceof Error ? error : new Error(String(error)),
1425
+ { receipts: { ...receipts }, stage, state: { ...state } },
1426
+ )
1427
+ }