viem 2.54.1 → 2.54.3

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 (141) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/_cjs/account-abstraction/actions/bundler/estimateUserOperationGas.js +1 -2
  3. package/_cjs/account-abstraction/actions/bundler/estimateUserOperationGas.js.map +1 -1
  4. package/_cjs/chains/definitions/marsCredit.js +23 -0
  5. package/_cjs/chains/definitions/marsCredit.js.map +1 -0
  6. package/_cjs/chains/index.js +10 -8
  7. package/_cjs/chains/index.js.map +1 -1
  8. package/_cjs/clients/createClient.js +1 -0
  9. package/_cjs/clients/createClient.js.map +1 -1
  10. package/_cjs/errors/encoding.js +13 -1
  11. package/_cjs/errors/encoding.js.map +1 -1
  12. package/_cjs/errors/version.js +1 -1
  13. package/_cjs/index.js +8 -6
  14. package/_cjs/index.js.map +1 -1
  15. package/_cjs/tempo/Abis.js +49 -1
  16. package/_cjs/tempo/Abis.js.map +1 -1
  17. package/_cjs/tempo/Addresses.js +3 -1
  18. package/_cjs/tempo/Addresses.js.map +1 -1
  19. package/_cjs/tempo/Decorator.js.map +1 -1
  20. package/_cjs/tempo/Formatters.js +5 -1
  21. package/_cjs/tempo/Formatters.js.map +1 -1
  22. package/_cjs/tempo/Selectors.js +8 -1
  23. package/_cjs/tempo/Selectors.js.map +1 -1
  24. package/_cjs/tempo/Transaction.js.map +1 -1
  25. package/_cjs/tempo/actions/receivePolicy.js +29 -5
  26. package/_cjs/tempo/actions/receivePolicy.js.map +1 -1
  27. package/_cjs/tempo/actions/virtualAddress.js +2 -5
  28. package/_cjs/tempo/actions/virtualAddress.js.map +1 -1
  29. package/_cjs/tempo/actions/zone.js +69 -48
  30. package/_cjs/tempo/actions/zone.js.map +1 -1
  31. package/_cjs/tempo/chainConfig.js +11 -2
  32. package/_cjs/tempo/chainConfig.js.map +1 -1
  33. package/_cjs/utils/abi/decodeAbiParameters.js +11 -2
  34. package/_cjs/utils/abi/decodeAbiParameters.js.map +1 -1
  35. package/_cjs/utils/abi/encodeAbiParameters.js +26 -11
  36. package/_cjs/utils/abi/encodeAbiParameters.js.map +1 -1
  37. package/_cjs/utils/encoding/fromRlp.js +9 -0
  38. package/_cjs/utils/encoding/fromRlp.js.map +1 -1
  39. package/_esm/account-abstraction/actions/bundler/estimateUserOperationGas.js +1 -2
  40. package/_esm/account-abstraction/actions/bundler/estimateUserOperationGas.js.map +1 -1
  41. package/_esm/chains/definitions/marsCredit.js +20 -0
  42. package/_esm/chains/definitions/marsCredit.js.map +1 -0
  43. package/_esm/chains/index.js +1 -0
  44. package/_esm/chains/index.js.map +1 -1
  45. package/_esm/clients/createClient.js +5 -3
  46. package/_esm/clients/createClient.js.map +1 -1
  47. package/_esm/errors/encoding.js +10 -0
  48. package/_esm/errors/encoding.js.map +1 -1
  49. package/_esm/errors/version.js +1 -1
  50. package/_esm/index.js +1 -1
  51. package/_esm/index.js.map +1 -1
  52. package/_esm/tempo/Abis.js +48 -0
  53. package/_esm/tempo/Abis.js.map +1 -1
  54. package/_esm/tempo/Addresses.js +2 -0
  55. package/_esm/tempo/Addresses.js.map +1 -1
  56. package/_esm/tempo/Decorator.js.map +1 -1
  57. package/_esm/tempo/Formatters.js +7 -1
  58. package/_esm/tempo/Formatters.js.map +1 -1
  59. package/_esm/tempo/Selectors.js +7 -0
  60. package/_esm/tempo/Selectors.js.map +1 -1
  61. package/_esm/tempo/Transaction.js.map +1 -1
  62. package/_esm/tempo/actions/receivePolicy.js +32 -5
  63. package/_esm/tempo/actions/receivePolicy.js.map +1 -1
  64. package/_esm/tempo/actions/virtualAddress.js +2 -6
  65. package/_esm/tempo/actions/virtualAddress.js.map +1 -1
  66. package/_esm/tempo/actions/zone.js +96 -49
  67. package/_esm/tempo/actions/zone.js.map +1 -1
  68. package/_esm/tempo/chainConfig.js +11 -2
  69. package/_esm/tempo/chainConfig.js.map +1 -1
  70. package/_esm/utils/abi/decodeAbiParameters.js +18 -2
  71. package/_esm/utils/abi/decodeAbiParameters.js.map +1 -1
  72. package/_esm/utils/abi/encodeAbiParameters.js +30 -12
  73. package/_esm/utils/abi/encodeAbiParameters.js.map +1 -1
  74. package/_esm/utils/encoding/fromRlp.js +12 -1
  75. package/_esm/utils/encoding/fromRlp.js.map +1 -1
  76. package/_types/account-abstraction/actions/bundler/estimateUserOperationGas.d.ts.map +1 -1
  77. package/_types/chains/definitions/marsCredit.d.ts +50 -0
  78. package/_types/chains/definitions/marsCredit.d.ts.map +1 -0
  79. package/_types/chains/definitions/tempo.d.ts +42 -0
  80. package/_types/chains/definitions/tempo.d.ts.map +1 -1
  81. package/_types/chains/definitions/tempoDevnet.d.ts +42 -0
  82. package/_types/chains/definitions/tempoDevnet.d.ts.map +1 -1
  83. package/_types/chains/definitions/tempoLocalnet.d.ts +42 -0
  84. package/_types/chains/definitions/tempoLocalnet.d.ts.map +1 -1
  85. package/_types/chains/definitions/tempoModerato.d.ts +42 -0
  86. package/_types/chains/definitions/tempoModerato.d.ts.map +1 -1
  87. package/_types/chains/index.d.ts +1 -0
  88. package/_types/chains/index.d.ts.map +1 -1
  89. package/_types/clients/createClient.d.ts +4 -3
  90. package/_types/clients/createClient.d.ts.map +1 -1
  91. package/_types/errors/encoding.d.ts +17 -0
  92. package/_types/errors/encoding.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 +119 -0
  97. package/_types/tempo/Abis.d.ts.map +1 -1
  98. package/_types/tempo/Addresses.d.ts +2 -0
  99. package/_types/tempo/Addresses.d.ts.map +1 -1
  100. package/_types/tempo/Decorator.d.ts +4 -0
  101. package/_types/tempo/Decorator.d.ts.map +1 -1
  102. package/_types/tempo/Formatters.d.ts.map +1 -1
  103. package/_types/tempo/Selectors.d.ts +7 -0
  104. package/_types/tempo/Selectors.d.ts.map +1 -1
  105. package/_types/tempo/Transaction.d.ts +11 -0
  106. package/_types/tempo/Transaction.d.ts.map +1 -1
  107. package/_types/tempo/actions/receivePolicy.d.ts +1 -1
  108. package/_types/tempo/actions/receivePolicy.d.ts.map +1 -1
  109. package/_types/tempo/actions/virtualAddress.d.ts.map +1 -1
  110. package/_types/tempo/actions/zone.d.ts +63 -3
  111. package/_types/tempo/actions/zone.d.ts.map +1 -1
  112. package/_types/tempo/chainConfig.d.ts +22 -0
  113. package/_types/tempo/chainConfig.d.ts.map +1 -1
  114. package/_types/tempo/zones/zone.d.ts +126 -0
  115. package/_types/tempo/zones/zone.d.ts.map +1 -1
  116. package/_types/utils/abi/decodeAbiParameters.d.ts.map +1 -1
  117. package/_types/utils/abi/encodeAbiParameters.d.ts +5 -5
  118. package/_types/utils/abi/encodeAbiParameters.d.ts.map +1 -1
  119. package/_types/utils/encoding/fromRlp.d.ts +3 -3
  120. package/_types/utils/encoding/fromRlp.d.ts.map +1 -1
  121. package/account-abstraction/actions/bundler/estimateUserOperationGas.ts +1 -2
  122. package/chains/definitions/marsCredit.ts +20 -0
  123. package/chains/index.ts +1 -0
  124. package/clients/createClient.ts +5 -3
  125. package/errors/encoding.ts +26 -0
  126. package/errors/version.ts +1 -1
  127. package/index.ts +4 -0
  128. package/package.json +3 -3
  129. package/tempo/Abis.ts +49 -0
  130. package/tempo/Addresses.ts +2 -0
  131. package/tempo/Decorator.ts +3 -0
  132. package/tempo/Formatters.ts +8 -0
  133. package/tempo/Selectors.ts +8 -0
  134. package/tempo/Transaction.ts +10 -0
  135. package/tempo/actions/receivePolicy.ts +39 -4
  136. package/tempo/actions/virtualAddress.ts +2 -8
  137. package/tempo/actions/zone.ts +164 -74
  138. package/tempo/chainConfig.ts +11 -2
  139. package/utils/abi/decodeAbiParameters.ts +17 -2
  140. package/utils/abi/encodeAbiParameters.ts +32 -15
  141. package/utils/encoding/fromRlp.ts +18 -1
package/tempo/Abis.ts CHANGED
@@ -2570,6 +2570,54 @@ export const validatorConfig = [
2570
2570
  },
2571
2571
  ] as const
2572
2572
 
2573
+ export const storageCredits = [
2574
+ {
2575
+ type: 'error',
2576
+ name: 'DelegateCallNotAllowed',
2577
+ inputs: [],
2578
+ },
2579
+ {
2580
+ type: 'error',
2581
+ name: 'InvalidMode',
2582
+ inputs: [],
2583
+ },
2584
+ {
2585
+ type: 'function',
2586
+ name: 'balanceOf',
2587
+ stateMutability: 'view',
2588
+ inputs: [{ name: 'account', type: 'address' }],
2589
+ outputs: [{ type: 'uint64' }],
2590
+ },
2591
+ {
2592
+ type: 'function',
2593
+ name: 'modeOf',
2594
+ stateMutability: 'view',
2595
+ inputs: [{ name: 'account', type: 'address' }],
2596
+ outputs: [{ type: 'uint8' }],
2597
+ },
2598
+ {
2599
+ type: 'function',
2600
+ name: 'budgetOf',
2601
+ stateMutability: 'view',
2602
+ inputs: [{ name: 'account', type: 'address' }],
2603
+ outputs: [{ type: 'uint64' }],
2604
+ },
2605
+ {
2606
+ type: 'function',
2607
+ name: 'setMode',
2608
+ stateMutability: 'nonpayable',
2609
+ inputs: [{ name: 'newMode', type: 'uint8' }],
2610
+ outputs: [],
2611
+ },
2612
+ {
2613
+ type: 'function',
2614
+ name: 'setBudget',
2615
+ stateMutability: 'nonpayable',
2616
+ inputs: [{ name: 'creditBudget', type: 'uint64' }],
2617
+ outputs: [],
2618
+ },
2619
+ ] as const
2620
+
2573
2621
  export const validatorConfigV2 = [
2574
2622
  {
2575
2623
  name: 'getActiveValidators',
@@ -2931,6 +2979,7 @@ export const abis = [
2931
2979
  ...receivePolicyGuard,
2932
2980
  ...signatureVerifier,
2933
2981
  ...stablecoinDex,
2982
+ ...storageCredits,
2934
2983
  ...tip20,
2935
2984
  ...tip20ChannelReserve,
2936
2985
  ...tip20Factory,
@@ -9,6 +9,8 @@ export const pathUsd = '0x20c0000000000000000000000000000000000000'
9
9
  export const receivePolicyGuard = '0xB10C000000000000000000000000000000000000'
10
10
  export const signatureVerifier = '0x5165300000000000000000000000000000000000'
11
11
  export const stablecoinDex = '0xdec0000000000000000000000000000000000000'
12
+ export const storageCredits = '0x1060000000000000000000000000000000000000'
13
+ export const tip20ChannelReserve = '0x4d50500000000000000000000000000000000000'
12
14
  export const tip20Factory = '0x20fc000000000000000000000000000000000000'
13
15
  export const tip403Registry = '0x403c000000000000000000000000000000000000'
14
16
  export const validator = '0xcccccccc00000000000000000000000000000000'
@@ -5129,6 +5129,9 @@ type BoundActionHelpers<action> = (action extends { call: infer helper }
5129
5129
  (action extends { estimateGas: infer helper }
5130
5130
  ? { estimateGas: BoundHelper<helper> }
5131
5131
  : {}) &
5132
+ (action extends { prepare: infer helper }
5133
+ ? { prepare: BoundHelper<helper> }
5134
+ : {}) &
5132
5135
  (action extends { simulate: infer helper }
5133
5136
  ? { simulate: BoundHelper<helper> }
5134
5137
  : {}) &
@@ -121,8 +121,12 @@ export function formatTransactionRequest(
121
121
  // `multisig` / `signatures` are client-side only (TIP-1061). They drive
122
122
  // sender derivation, owner signing, and final envelope assembly, but are
123
123
  // never sent as raw RPC fields — the wire payload is the serialized tx.
124
+ // `multisigInit` / `multisigSignatureCount` are wire fields: the node
125
+ // prices multisig gas from them during simulation.
124
126
  const {
125
127
  multisig: _multisig,
128
+ multisigInit,
129
+ multisigSignatureCount,
126
130
  signatures: _signatures,
127
131
  ...rpcRequest
128
132
  } = request
@@ -164,6 +168,10 @@ export function formatTransactionRequest(
164
168
 
165
169
  return {
166
170
  ...rpc,
171
+ ...(multisigInit ? { multisigInit } : {}),
172
+ ...(typeof multisigSignatureCount !== 'undefined'
173
+ ? { multisigSignatureCount }
174
+ : {}),
167
175
  ...(request.capabilities ? { capabilities: request.capabilities } : {}),
168
176
  ...(keyData ? { keyData } : {}),
169
177
  ...(keyId ? { keyId } : {}),
@@ -94,6 +94,14 @@ export const stablecoinDex = {
94
94
  withdraw: '0x08fab167',
95
95
  } as const satisfies FunctionSelectors<typeof Abis.stablecoinDex>
96
96
 
97
+ export const storageCredits = {
98
+ balanceOf: '0x70a08231',
99
+ budgetOf: '0x7865e71f',
100
+ modeOf: '0x13668995',
101
+ setBudget: '0xffe295c3',
102
+ setMode: '0x21175b4a',
103
+ } as const satisfies FunctionSelectors<typeof Abis.storageCredits>
104
+
97
105
  export const tip20 = {
98
106
  BURN_BLOCKED_ROLE: '0x32ad9be8',
99
107
  DOMAIN_SEPARATOR: '0x3644e515',
@@ -126,6 +126,16 @@ export type TransactionRequestTempo<
126
126
  feeToken?: TempoAddress.Address | bigint | undefined
127
127
  keyAuthorization?: KeyAuthorization.Signed<quantity, index> | undefined
128
128
  multisig?: MultisigConfig.Config<index> | undefined
129
+ /** Bootstrap multisig config hint for node-side gas modeling (TIP-1061). Attached automatically when `multisig` is present; the node ignores it for registered senders. */
130
+ multisigInit?:
131
+ | {
132
+ salt: Hex.Hex
133
+ threshold: number
134
+ owners: readonly { owner: Address; weight: number }[]
135
+ }
136
+ | undefined
137
+ /** Modeled owner approval count for node-side gas modeling (TIP-1061). */
138
+ multisigSignatureCount?: number | undefined
129
139
  nonceKey?: 'expiring' | quantity | undefined
130
140
  signatures?: readonly SignatureEnvelope.Serialized[] | undefined
131
141
  validBefore?: index | undefined
@@ -1,5 +1,5 @@
1
1
  import type { Address } from 'abitype'
2
- import type { ReceivePolicyReceipt } from 'ox/tempo'
2
+ import { type ReceivePolicyReceipt, VirtualAddress } from 'ox/tempo'
3
3
  import type { Account } from '../../accounts/types.js'
4
4
  import { parseAccount } from '../../accounts/utils/parseAccount.js'
5
5
  import type { ReadContractReturnType } from '../../actions/public/readContract.js'
@@ -57,6 +57,25 @@ export type BlockedReason = ReceivePolicyReceipt.BlockedReason
57
57
  /** @internal */
58
58
  const blockedReasons = ['none', 'tokenFilter', 'receivePolicy'] as const
59
59
 
60
+ /** @internal 12-byte prefix shared by TIP-20 token addresses. */
61
+ const tip20Prefix = '0x20c000000000000000000000'
62
+
63
+ /** @internal */
64
+ const systemPrecompiles = [
65
+ Addresses.accountKeychain,
66
+ Addresses.accountRegistrar,
67
+ Addresses.addressRegistry,
68
+ Addresses.feeManager,
69
+ Addresses.nonceManager,
70
+ Addresses.receivePolicyGuard,
71
+ Addresses.signatureVerifier,
72
+ Addresses.stablecoinDex,
73
+ Addresses.tip20ChannelReserve,
74
+ Addresses.tip20Factory,
75
+ Addresses.tip403Registry,
76
+ Addresses.validator,
77
+ ] as const
78
+
60
79
  /**
61
80
  * Claimer authorized to reclaim blocked funds.
62
81
  *
@@ -1253,9 +1272,25 @@ function toPolicyRef(id: bigint): PolicyRef {
1253
1272
 
1254
1273
  /** @internal */
1255
1274
  function resolveClaimer(claimer: Claimer, self: Address): Address {
1256
- if (claimer === 'sender') return zeroAddress
1257
- if (claimer === 'self') return self
1258
- return claimer
1275
+ const recoveryAuthority =
1276
+ claimer === 'sender' ? zeroAddress : claimer === 'self' ? self : claimer
1277
+ assertValidRecoveryAuthority(recoveryAuthority)
1278
+ return recoveryAuthority
1279
+ }
1280
+
1281
+ /** @internal */
1282
+ function assertValidRecoveryAuthority(recoveryAuthority: Address) {
1283
+ if (isAddressEqual(recoveryAuthority, zeroAddress)) return
1284
+ if (VirtualAddress.isVirtual(recoveryAuthority))
1285
+ throw new Error('Recovery authority cannot be a TIP-1022 virtual address.')
1286
+ if (recoveryAuthority.toLowerCase().startsWith(tip20Prefix))
1287
+ throw new Error('Recovery authority cannot be a TIP-20 token address.')
1288
+ if (
1289
+ systemPrecompiles.some((address) =>
1290
+ isAddressEqual(address, recoveryAuthority),
1291
+ )
1292
+ )
1293
+ throw new Error('Recovery authority cannot be a Tempo system precompile.')
1259
1294
  }
1260
1295
 
1261
1296
  /** @internal */
@@ -1,5 +1,6 @@
1
1
  import type { Address } from 'abitype'
2
2
  import * as Hex from 'ox/Hex'
3
+ import { VirtualAddress } from 'ox/tempo'
3
4
  import type { Account } from '../../accounts/types.js'
4
5
  import { readContract } from '../../actions/public/readContract.js'
5
6
  import type { WriteContractReturnType } from '../../actions/wallet/writeContract.js'
@@ -121,7 +122,7 @@ export async function resolve<
121
122
  client: Client<Transport, chain, account>,
122
123
  parameters: resolve.Parameters,
123
124
  ): Promise<resolve.ReturnValue> {
124
- if (!isVirtual(parameters.address)) return parameters.address
125
+ if (!VirtualAddress.isVirtual(parameters.address)) return parameters.address
125
126
 
126
127
  const masterId = Hex.slice(parameters.address, 0, 4)
127
128
  return getMasterAddress(client, { ...parameters, masterId })
@@ -328,10 +329,3 @@ export namespace registerMasterSync {
328
329
  // TODO: exhaustive error type
329
330
  export type ErrorType = BaseErrorType
330
331
  }
331
-
332
- const virtualMagic = '0xfdfdfdfdfdfdfdfdfdfd'
333
-
334
- /** @internal */
335
- function isVirtual(address: string): boolean {
336
- return Hex.slice(address as Hex.Hex, 4, 14).toLowerCase() === virtualMagic
337
- }
@@ -26,7 +26,7 @@ import type {
26
26
  ReadParameters,
27
27
  WriteParameters,
28
28
  } from '../internal/types.js'
29
- import { defineCall } from '../internal/utils.js'
29
+ import { defineCall, pickWriteParameters } from '../internal/utils.js'
30
30
  import * as Storage from '../Storage.js'
31
31
  import type { TransactionReceipt } from '../Transaction.js'
32
32
  import * as ZoneAbis from '../zones/Abis.js'
@@ -40,6 +40,23 @@ export type EncryptedPayload = {
40
40
  tag: Hex.Hex
41
41
  }
42
42
 
43
+ export type PreparedEncryptedDeposit = {
44
+ /** Amount of tokens to deposit. */
45
+ amount: bigint
46
+ /** Parent chain ID (e.g. `42431` for moderato). */
47
+ chainId: number
48
+ /** Encrypted deposit payload. */
49
+ encrypted: EncryptedPayload
50
+ /** Encryption key index from the portal contract. */
51
+ keyIndex: bigint
52
+ /** Zone portal address on the parent chain. */
53
+ portalAddress: Address
54
+ /** Token address or ID to deposit. */
55
+ token: TokenId.TokenIdOrAddress
56
+ /** Zone ID (e.g. `7`). */
57
+ zoneId: number
58
+ }
59
+
43
60
  /**
44
61
  * Deposits tokens into a zone on the parent Tempo chain.
45
62
  * Batches approve and deposit into a single transaction.
@@ -269,46 +286,31 @@ export async function encryptedDeposit<
269
286
  const account_ = account ? parseAccount(account) : undefined
270
287
  if (!account) throw new Error('`account` is required.')
271
288
 
272
- const recipient = parameters.recipient ?? account_?.address
273
- if (!recipient) throw new Error('`recipient` is required.')
274
-
275
- const portalAddress = getPortalAddress(chainId, parameters.zoneId)
276
-
277
- const [publicKey, keyIndex] = await Promise.all([
278
- readContract(client, {
279
- address: portalAddress,
280
- abi: ZoneAbis.zonePortal,
281
- functionName: 'sequencerEncryptionKey',
282
- }),
283
- readContract(client, {
284
- address: portalAddress,
285
- abi: ZoneAbis.zonePortal,
286
- functionName: 'encryptionKeyCount',
287
- }),
288
- ])
289
-
290
- if (keyIndex === 0n) {
291
- throw new Error('No sequencer encryption key configured.')
289
+ if ('encrypted' in parameters) {
290
+ if (parameters.chainId !== chainId) {
291
+ throw new Error(
292
+ 'Prepared encrypted deposit chain ID does not match client chain.',
293
+ )
294
+ }
295
+ return sendTransaction(client, {
296
+ ...pickWriteParameters(parameters as never),
297
+ calls: encryptedDeposit.calls(parameters),
298
+ } as never) as never
292
299
  }
293
300
 
294
- const encrypted = await encryptDepositPayload(
295
- { x: publicKey[0], yParity: publicKey[1] },
296
- recipient,
297
- portalAddress,
298
- keyIndex - 1n,
299
- parameters.memo,
300
- )
301
+ const recipient = parameters.recipient ?? account_?.address
302
+ if (!recipient) throw new Error('`recipient` is required.')
301
303
 
302
- const args = {
303
- ...parameters,
304
- chainId,
305
- encrypted,
306
- keyIndex: keyIndex - 1n,
304
+ const prepared = await encryptedDeposit.prepare(client, {
305
+ amount: parameters.amount,
306
+ memo: parameters.memo,
307
307
  recipient,
308
- }
308
+ token: parameters.token,
309
+ zoneId: parameters.zoneId,
310
+ })
309
311
  return sendTransaction(client, {
310
312
  ...rest,
311
- calls: encryptedDeposit.calls(args),
313
+ calls: encryptedDeposit.calls(prepared),
312
314
  } as never) as never
313
315
  }
314
316
 
@@ -317,10 +319,13 @@ export namespace encryptedDeposit {
317
319
  chain extends Chain | undefined = Chain | undefined,
318
320
  account extends Account | undefined = Account | undefined,
319
321
  > = WriteParameters<chain, account> &
320
- Omit<Args, 'chainId' | 'encrypted' | 'keyIndex' | 'recipient'> & {
321
- /** Recipient address in the zone. @default `account.address` */
322
- recipient?: Address | undefined
323
- }
322
+ (
323
+ | (Omit<Args, 'chainId' | 'encrypted' | 'keyIndex' | 'recipient'> & {
324
+ /** Recipient address in the zone. @default `account.address` */
325
+ recipient?: Address | undefined
326
+ })
327
+ | PreparedEncryptedDeposit
328
+ )
324
329
 
325
330
  export type Args = {
326
331
  /** Amount of tokens to deposit. */
@@ -346,13 +351,111 @@ export namespace encryptedDeposit {
346
351
  // TODO: exhaustive error type
347
352
  export type ErrorType = BaseErrorType
348
353
 
354
+ /**
355
+ * Prepares an encrypted deposit instruction without broadcasting it.
356
+ *
357
+ * @example
358
+ * ```ts
359
+ * import { createClient, http } from 'viem'
360
+ * import { tempoModerato } from 'viem/chains'
361
+ * import { Actions } from 'viem/tempo'
362
+ *
363
+ * const client = createClient({
364
+ * chain: tempoModerato,
365
+ * transport: http(),
366
+ * })
367
+ *
368
+ * const prepared = await Actions.zone.encryptedDeposit.prepare(client, {
369
+ * token: '0x20c0...0001',
370
+ * amount: 1_000_000n,
371
+ * recipient: '0x...',
372
+ * zoneId: 7,
373
+ * })
374
+ * ```
375
+ *
376
+ * @param client - Public client connected to the parent Tempo chain.
377
+ * @param parameters - Encrypted deposit preparation parameters.
378
+ * @returns A prepared encrypted deposit instruction.
379
+ */
380
+ export async function prepare<
381
+ chain extends Chain | undefined,
382
+ account extends Account | undefined,
383
+ >(
384
+ client: Client<Transport, chain, account>,
385
+ parameters: prepare.Parameters,
386
+ ): Promise<prepare.ReturnValue> {
387
+ const chainId = client.chain?.id
388
+ if (!chainId) throw new Error('`chain` is required.')
389
+
390
+ const { amount, memo, recipient, token, zoneId, ...rest } = parameters
391
+ const portalAddress = getPortalAddress(chainId, zoneId)
392
+
393
+ const [publicKey, keyIndex] = await Promise.all([
394
+ readContract(client, {
395
+ ...rest,
396
+ address: portalAddress,
397
+ abi: ZoneAbis.zonePortal,
398
+ functionName: 'sequencerEncryptionKey',
399
+ }),
400
+ readContract(client, {
401
+ ...rest,
402
+ address: portalAddress,
403
+ abi: ZoneAbis.zonePortal,
404
+ functionName: 'encryptionKeyCount',
405
+ }),
406
+ ])
407
+
408
+ if (keyIndex === 0n) {
409
+ throw new Error('No sequencer encryption key configured.')
410
+ }
411
+
412
+ const encrypted = await encryptDepositPayload(
413
+ { x: publicKey[0], yParity: publicKey[1] },
414
+ recipient,
415
+ portalAddress,
416
+ keyIndex - 1n,
417
+ memo,
418
+ )
419
+
420
+ return {
421
+ amount,
422
+ chainId,
423
+ encrypted,
424
+ keyIndex: keyIndex - 1n,
425
+ portalAddress,
426
+ token,
427
+ zoneId,
428
+ }
429
+ }
430
+
431
+ export namespace prepare {
432
+ export type Parameters = ReadParameters & Args
433
+
434
+ export type Args = {
435
+ /** Amount of tokens to deposit. */
436
+ amount: bigint
437
+ /** Optional deposit memo. @default `0x00...00` */
438
+ memo?: Hex.Hex | undefined
439
+ /** Recipient address in the zone. */
440
+ recipient: Address
441
+ /** Token address or ID to deposit. */
442
+ token: TokenId.TokenIdOrAddress
443
+ /** Zone ID (e.g. `7`). */
444
+ zoneId: number
445
+ }
446
+
447
+ export type ReturnValue = PreparedEncryptedDeposit
448
+
449
+ export type ErrorType = RequestErrorType | BaseErrorType
450
+ }
451
+
349
452
  /**
350
453
  * Defines the calls to approve and deposit tokens into a zone (encrypted).
351
454
  *
352
455
  * @param args - Arguments.
353
456
  * @returns The calls.
354
457
  */
355
- export function calls(args: Args) {
458
+ export function calls(args: Args | PreparedEncryptedDeposit) {
356
459
  const { amount, chainId, encrypted, keyIndex, token, zoneId } = args
357
460
  const portalAddress = getPortalAddress(chainId, zoneId)
358
461
  return [
@@ -430,47 +533,34 @@ export async function encryptedDepositSync<
430
533
  const account_ = account ? parseAccount(account) : undefined
431
534
  if (!account) throw new Error('`account` is required.')
432
535
 
433
- const recipient = parameters.recipient ?? account_?.address
434
- if (!recipient) throw new Error('`recipient` is required.')
435
-
436
- const portalAddress = getPortalAddress(chainId, parameters.zoneId)
437
-
438
- const [publicKey, keyIndex] = await Promise.all([
439
- readContract(client, {
440
- address: portalAddress,
441
- abi: ZoneAbis.zonePortal,
442
- functionName: 'sequencerEncryptionKey',
443
- }),
444
- readContract(client, {
445
- address: portalAddress,
446
- abi: ZoneAbis.zonePortal,
447
- functionName: 'encryptionKeyCount',
448
- }),
449
- ])
450
-
451
- if (keyIndex === 0n) {
452
- throw new Error('No sequencer encryption key configured.')
536
+ if ('encrypted' in parameters) {
537
+ if (parameters.chainId !== chainId) {
538
+ throw new Error(
539
+ 'Prepared encrypted deposit chain ID does not match client chain.',
540
+ )
541
+ }
542
+ const receipt = await sendTransactionSync(client, {
543
+ ...pickWriteParameters(parameters as never),
544
+ throwOnReceiptRevert,
545
+ calls: encryptedDeposit.calls(parameters),
546
+ } as never)
547
+ return { receipt }
453
548
  }
454
549
 
455
- const encrypted = await encryptDepositPayload(
456
- { x: publicKey[0], yParity: publicKey[1] },
457
- recipient,
458
- portalAddress,
459
- keyIndex - 1n,
460
- parameters.memo,
461
- )
550
+ const recipient = parameters.recipient ?? account_?.address
551
+ if (!recipient) throw new Error('`recipient` is required.')
462
552
 
463
- const args = {
464
- ...parameters,
465
- chainId,
466
- encrypted,
467
- keyIndex: keyIndex - 1n,
553
+ const prepared = await encryptedDeposit.prepare(client, {
554
+ amount: parameters.amount,
555
+ memo: parameters.memo,
468
556
  recipient,
469
- }
557
+ token: parameters.token,
558
+ zoneId: parameters.zoneId,
559
+ })
470
560
  const receipt = await sendTransactionSync(client, {
471
561
  ...rest,
472
562
  throwOnReceiptRevert,
473
- calls: encryptedDeposit.calls(args),
563
+ calls: encryptedDeposit.calls(prepared),
474
564
  } as never)
475
565
  return { receipt }
476
566
  }
@@ -83,8 +83,17 @@ export const chainConfig = {
83
83
  ? (request.account as MultisigAccount).config
84
84
  : undefined)
85
85
  if (multisig) {
86
- request.multisig = multisig
87
- request.from = MultisigConfig.getAddress(multisig)
86
+ const config = MultisigConfig.from(multisig)
87
+ request.multisig = config
88
+ request.from = MultisigConfig.getAddress(config)
89
+ request.multisigInit = {
90
+ salt: config.salt ?? MultisigConfig.zeroSalt,
91
+ threshold: Number(config.threshold),
92
+ owners: config.owners.map((owner) => ({
93
+ owner: owner.owner,
94
+ weight: Number(owner.weight),
95
+ })),
96
+ }
88
97
  // A non-multisig `account` (e.g. the client's default) isn't the sender,
89
98
  // so drop it: core then fills nonce/gas/fees for the multisig sender via
90
99
  // `request.from`. A multisig account *is* the sender — keep it so the
@@ -75,7 +75,9 @@ export function decodeAbiParameters<
75
75
  const values = []
76
76
  for (let i = 0; i < params.length; ++i) {
77
77
  const param = params[i]
78
- cursor.setPosition(consumed)
78
+ // Zero-width types (e.g. `uint256[0]`, empty tuples) at the end of the
79
+ // data consume no bytes, so the cursor may already be exhausted.
80
+ if (consumed < bytes.length) cursor.setPosition(consumed)
79
81
  const [data, consumed_] = decodeParameter(cursor, param, {
80
82
  staticPosition: 0,
81
83
  })
@@ -146,7 +148,8 @@ function decodeArray(
146
148
  ) {
147
149
  // If the length of the array is not known in advance (dynamic array),
148
150
  // this means we will need to wonder off to the pointer and decode.
149
- if (!length) {
151
+ // Note: zero-length fixed arrays (`T[0]`) are not dynamic.
152
+ if (length === null) {
150
153
  // Dealing with a dynamic type, so get the offset of the array data.
151
154
  const offset = bytesToNumber(cursor.readBytes(sizeOfOffset))
152
155
 
@@ -172,6 +175,12 @@ function decodeArray(
172
175
  })
173
176
  consumed += consumed_
174
177
  value.push(data)
178
+ // Charge zero-width elements against the read limit to bound work
179
+ // on huge lengths of zero-width types (e.g. `uint256[0][]`).
180
+ if (consumed_ === 0) {
181
+ cursor.assertReadLimit()
182
+ cursor._touch()
183
+ }
175
184
  }
176
185
 
177
186
  // As we have gone wondering, restore to the original position + next slot.
@@ -214,6 +223,12 @@ function decodeArray(
214
223
  })
215
224
  consumed += consumed_
216
225
  value.push(data)
226
+ // Charge zero-width elements against the read limit to bound work
227
+ // on huge lengths of zero-width types (e.g. `uint256[0][4294967295]`).
228
+ if (consumed_ === 0) {
229
+ cursor.assertReadLimit()
230
+ cursor._touch()
231
+ }
217
232
  }
218
233
  return [value, consumed]
219
234
  }