viem 2.57.8 → 2.57.10

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 +42 -0
  2. package/_cjs/clients/createClient.js +49 -4
  3. package/_cjs/clients/createClient.js.map +1 -1
  4. package/_cjs/clients/transports/fallback.js +2 -2
  5. package/_cjs/clients/transports/fallback.js.map +1 -1
  6. package/_cjs/errors/version.js +1 -1
  7. package/_cjs/errors/version.js.map +1 -1
  8. package/_cjs/experimental/erc7821/actions/supportsExecutionMode.js +1 -1
  9. package/_cjs/experimental/erc7821/actions/supportsExecutionMode.js.map +1 -1
  10. package/_cjs/op-stack/actions/getPortalVersion.js +4 -1
  11. package/_cjs/op-stack/actions/getPortalVersion.js.map +1 -1
  12. package/_cjs/tempo/Abis.js +84 -15
  13. package/_cjs/tempo/Abis.js.map +1 -1
  14. package/_cjs/tempo/Account.js +13 -11
  15. package/_cjs/tempo/Account.js.map +1 -1
  16. package/_cjs/tempo/Addresses.js +3 -3
  17. package/_cjs/tempo/Addresses.js.map +1 -1
  18. package/_cjs/tempo/Decorator.js +4 -0
  19. package/_cjs/tempo/Decorator.js.map +1 -1
  20. package/_cjs/tempo/ExecutionError.js +25 -2
  21. package/_cjs/tempo/ExecutionError.js.map +1 -1
  22. package/_cjs/tempo/Hardfork.js +12 -1
  23. package/_cjs/tempo/Hardfork.js.map +1 -1
  24. package/_cjs/tempo/Selectors.js +4 -2
  25. package/_cjs/tempo/Selectors.js.map +1 -1
  26. package/_cjs/tempo/accounts/Config.js +4 -2
  27. package/_cjs/tempo/accounts/Config.js.map +1 -1
  28. package/_cjs/tempo/accounts/Operation.js +2 -2
  29. package/_cjs/tempo/accounts/Operation.js.map +1 -1
  30. package/_cjs/tempo/actions/accessKey.js +1 -0
  31. package/_cjs/tempo/actions/accessKey.js.map +1 -1
  32. package/_cjs/tempo/actions/accounts.js +122 -7
  33. package/_cjs/tempo/actions/accounts.js.map +1 -1
  34. package/_cjs/tempo/chainConfig.js +29 -2
  35. package/_cjs/tempo/chainConfig.js.map +1 -1
  36. package/_cjs/tempo/internal/relay/accounts.js +46 -5
  37. package/_cjs/tempo/internal/relay/accounts.js.map +1 -1
  38. package/_cjs/tempo/internal/relay/feePayer.js +4 -4
  39. package/_cjs/tempo/internal/relay/feePayer.js.map +1 -1
  40. package/_cjs/utils/promise/withRetry.js +5 -1
  41. package/_cjs/utils/promise/withRetry.js.map +1 -1
  42. package/_esm/clients/createClient.js +57 -4
  43. package/_esm/clients/createClient.js.map +1 -1
  44. package/_esm/clients/transports/fallback.js +2 -2
  45. package/_esm/clients/transports/fallback.js.map +1 -1
  46. package/_esm/errors/version.js +1 -1
  47. package/_esm/errors/version.js.map +1 -1
  48. package/_esm/experimental/erc7821/actions/supportsExecutionMode.js +2 -1
  49. package/_esm/experimental/erc7821/actions/supportsExecutionMode.js.map +1 -1
  50. package/_esm/op-stack/actions/getPortalVersion.js +4 -1
  51. package/_esm/op-stack/actions/getPortalVersion.js.map +1 -1
  52. package/_esm/tempo/Abis.js +84 -15
  53. package/_esm/tempo/Abis.js.map +1 -1
  54. package/_esm/tempo/Account.js +18 -12
  55. package/_esm/tempo/Account.js.map +1 -1
  56. package/_esm/tempo/Addresses.js +3 -3
  57. package/_esm/tempo/Addresses.js.map +1 -1
  58. package/_esm/tempo/Decorator.js +4 -0
  59. package/_esm/tempo/Decorator.js.map +1 -1
  60. package/_esm/tempo/ExecutionError.js +25 -2
  61. package/_esm/tempo/ExecutionError.js.map +1 -1
  62. package/_esm/tempo/Hardfork.js +17 -2
  63. package/_esm/tempo/Hardfork.js.map +1 -1
  64. package/_esm/tempo/Selectors.js +3 -1
  65. package/_esm/tempo/Selectors.js.map +1 -1
  66. package/_esm/tempo/accounts/Config.js +5 -3
  67. package/_esm/tempo/accounts/Config.js.map +1 -1
  68. package/_esm/tempo/accounts/Operation.js +2 -2
  69. package/_esm/tempo/accounts/Operation.js.map +1 -1
  70. package/_esm/tempo/actions/accessKey.js +1 -0
  71. package/_esm/tempo/actions/accessKey.js.map +1 -1
  72. package/_esm/tempo/actions/accounts.js +279 -8
  73. package/_esm/tempo/actions/accounts.js.map +1 -1
  74. package/_esm/tempo/chainConfig.js +33 -3
  75. package/_esm/tempo/chainConfig.js.map +1 -1
  76. package/_esm/tempo/internal/relay/accounts.js +50 -5
  77. package/_esm/tempo/internal/relay/accounts.js.map +1 -1
  78. package/_esm/tempo/internal/relay/feePayer.js +4 -4
  79. package/_esm/tempo/internal/relay/feePayer.js.map +1 -1
  80. package/_esm/utils/promise/withRetry.js +6 -1
  81. package/_esm/utils/promise/withRetry.js.map +1 -1
  82. package/_types/clients/createClient.d.ts +30 -0
  83. package/_types/clients/createClient.d.ts.map +1 -1
  84. package/_types/clients/transports/fallback.d.ts.map +1 -1
  85. package/_types/errors/version.d.ts +1 -1
  86. package/_types/errors/version.d.ts.map +1 -1
  87. package/_types/experimental/erc7821/actions/supportsExecutionMode.d.ts.map +1 -1
  88. package/_types/op-stack/actions/getPortalVersion.d.ts.map +1 -1
  89. package/_types/tempo/Abis.d.ts +621 -307
  90. package/_types/tempo/Abis.d.ts.map +1 -1
  91. package/_types/tempo/Account.d.ts +24 -4
  92. package/_types/tempo/Account.d.ts.map +1 -1
  93. package/_types/tempo/Addresses.d.ts +3 -3
  94. package/_types/tempo/Addresses.d.ts.map +1 -1
  95. package/_types/tempo/Decorator.d.ts +104 -0
  96. package/_types/tempo/Decorator.d.ts.map +1 -1
  97. package/_types/tempo/ExecutionError.d.ts.map +1 -1
  98. package/_types/tempo/Hardfork.d.ts +6 -2
  99. package/_types/tempo/Hardfork.d.ts.map +1 -1
  100. package/_types/tempo/Selectors.d.ts +3 -1
  101. package/_types/tempo/Selectors.d.ts.map +1 -1
  102. package/_types/tempo/Transaction.d.ts +2 -2
  103. package/_types/tempo/Transaction.d.ts.map +1 -1
  104. package/_types/tempo/actions/accessKey.d.ts +3 -2
  105. package/_types/tempo/actions/accessKey.d.ts.map +1 -1
  106. package/_types/tempo/actions/accounts.d.ts +594 -7
  107. package/_types/tempo/actions/accounts.d.ts.map +1 -1
  108. package/_types/tempo/actions/channel.d.ts +42 -0
  109. package/_types/tempo/actions/channel.d.ts.map +1 -1
  110. package/_types/tempo/actions/reward.d.ts +14 -0
  111. package/_types/tempo/actions/reward.d.ts.map +1 -1
  112. package/_types/tempo/actions/token.d.ts +105 -0
  113. package/_types/tempo/actions/token.d.ts.map +1 -1
  114. package/_types/tempo/chainConfig.d.ts.map +1 -1
  115. package/_types/tempo/internal/relay/accounts.d.ts.map +1 -1
  116. package/_types/utils/promise/withRetry.d.ts.map +1 -1
  117. package/clients/createClient.ts +103 -5
  118. package/clients/transports/fallback.ts +8 -5
  119. package/errors/version.ts +1 -1
  120. package/experimental/erc7821/actions/supportsExecutionMode.ts +2 -1
  121. package/op-stack/actions/getPortalVersion.ts +4 -1
  122. package/package.json +2 -2
  123. package/tempo/Abis.ts +84 -15
  124. package/tempo/Account.ts +52 -16
  125. package/tempo/Addresses.ts +4 -4
  126. package/tempo/Decorator.ts +116 -0
  127. package/tempo/ExecutionError.ts +28 -2
  128. package/tempo/Hardfork.ts +16 -2
  129. package/tempo/Selectors.ts +4 -2
  130. package/tempo/Transaction.ts +2 -2
  131. package/tempo/accounts/Config.ts +5 -3
  132. package/tempo/accounts/Operation.ts +2 -2
  133. package/tempo/actions/accessKey.ts +3 -2
  134. package/tempo/actions/accounts.ts +394 -10
  135. package/tempo/chainConfig.ts +41 -3
  136. package/tempo/internal/relay/accounts.ts +62 -5
  137. package/tempo/internal/relay/feePayer.ts +4 -4
  138. package/utils/promise/withRetry.ts +7 -1
@@ -72,6 +72,7 @@ const signatureTypes = {
72
72
  0: 'secp256k1',
73
73
  1: 'p256',
74
74
  2: 'webAuthn',
75
+ 3: 'configurable',
75
76
  } as const satisfies Record<number, string>
76
77
 
77
78
  /** @internal */
@@ -558,8 +559,8 @@ export namespace getMetadata {
558
559
  export type ReturnValue = {
559
560
  /** The access key address. */
560
561
  address: Address
561
- /** The key type. */
562
- keyType: 'secp256k1' | 'p256' | 'webAuthn'
562
+ /** The key type. `configurable` for a configurable account access key. */
563
+ keyType: 'secp256k1' | 'p256' | 'webAuthn' | 'configurable'
563
564
  /** The expiry timestamp. */
564
565
  expiry: bigint
565
566
  /** The spending policy. */
@@ -13,7 +13,7 @@ import type { Transport } from '../../clients/transports/createTransport.js'
13
13
  import type { BaseErrorType } from '../../errors/base.js'
14
14
  import type { Chain } from '../../types/chain.js'
15
15
  import type { Log } from '../../types/log.js'
16
- import type { Compute } from '../../types/utils.js'
16
+ import type { Compute, OneOf, UnionOmit } from '../../types/utils.js'
17
17
  import { parseEventLogs } from '../../utils/abi/parseEventLogs.js'
18
18
  import * as Abis from '../Abis.js'
19
19
  import { type ConfigurableAccount, fromConfig } from '../Account.js'
@@ -24,6 +24,88 @@ import type { ReadParameters, WriteParameters } from '../internal/types.js'
24
24
  import { defineCall } from '../internal/utils.js'
25
25
  import type * as Transaction from '../Transaction.js'
26
26
 
27
+ /**
28
+ * Derives a configurable account address from an initial config with the
29
+ * configurable account precompile.
30
+ *
31
+ * The precompile validates the config and derives the address with the chain's
32
+ * recovery factory. To derive the address locally, use
33
+ * `AccountConfig.getAddress` or `Account.fromConfig({ address: 'infer' })`.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { createClient, http } from 'viem'
38
+ * import { tempo } from 'viem/chains'
39
+ * import { Actions } from 'viem/tempo'
40
+ *
41
+ * const client = createClient({
42
+ * chain: tempo,
43
+ * transport: http(),
44
+ * })
45
+ *
46
+ * const address = await Actions.accounts.deriveAccount(client, {
47
+ * owners: [{ owner: '0x...', weight: 1 }],
48
+ * threshold: 1,
49
+ * })
50
+ * ```
51
+ *
52
+ * @param client - Client.
53
+ * @param parameters - Parameters.
54
+ * @returns The configurable account address.
55
+ */
56
+ export async function deriveAccount<
57
+ chain extends Chain | undefined,
58
+ account extends Account | undefined,
59
+ >(
60
+ client: Client<Transport, chain, account>,
61
+ parameters: deriveAccount.Parameters,
62
+ ): Promise<deriveAccount.ReturnValue> {
63
+ const { owners, salt, threshold, ...rest } = parameters
64
+ return withExecutionError(readContract)(client, {
65
+ ...rest,
66
+ ...deriveAccount.call({ owners, salt, threshold }),
67
+ })
68
+ }
69
+
70
+ export namespace deriveAccount {
71
+ export type Parameters = ReadParameters & Args
72
+
73
+ export type Args = {
74
+ /** Weighted owners. Normalized into canonical address order. */
75
+ owners: AccountConfig.Config['owners']
76
+ /** Caller-chosen 32-byte salt. Defaults to the zero salt. */
77
+ salt?: Hex.Hex | undefined
78
+ /** Minimum total owner weight required for authorization. */
79
+ threshold: number
80
+ }
81
+
82
+ export type ReturnValue = ReadContractReturnType<
83
+ typeof Abis.configurableAccount,
84
+ 'deriveAccount',
85
+ never
86
+ >
87
+
88
+ /**
89
+ * Defines a call to the `deriveAccount` function.
90
+ *
91
+ * Can be passed to [`multicall`](https://viem.sh/docs/contract/multicall).
92
+ *
93
+ * @param args - Arguments.
94
+ * @returns The call.
95
+ */
96
+ export function call(args: Args) {
97
+ const { owners, salt, threshold } = AccountConfig.from(args)
98
+ return defineCall({
99
+ address: Addresses.configurableAccount,
100
+ abi: Abis.configurableAccount,
101
+ args: [salt, threshold, owners],
102
+ functionName: 'deriveAccount',
103
+ })
104
+ }
105
+
106
+ export type ErrorType = Errors.ExecutionErrorType | BaseErrorType
107
+ }
108
+
27
109
  /**
28
110
  * Gets the current cached config for a configurable account.
29
111
  *
@@ -118,7 +200,7 @@ export namespace getConfigCommitment {
118
200
  }
119
201
 
120
202
  export type ReturnValue = ReadContractReturnType<
121
- typeof Abis.nativeMultisig,
203
+ typeof Abis.configurableAccount,
122
204
  'getConfigCommitment',
123
205
  never
124
206
  >
@@ -133,8 +215,8 @@ export namespace getConfigCommitment {
133
215
  */
134
216
  export function call(args: Args) {
135
217
  return defineCall({
136
- address: Addresses.nativeMultisig,
137
- abi: Abis.nativeMultisig,
218
+ address: Addresses.configurableAccount,
219
+ abi: Abis.configurableAccount,
138
220
  args: [args.account],
139
221
  functionName: 'getConfigCommitment',
140
222
  })
@@ -348,27 +430,28 @@ export namespace updateConfig {
348
430
  const currentConfig = AccountConfig.from(args.currentConfig)
349
431
  const nextConfig = AccountConfig.update(currentConfig, args.nextConfig)
350
432
  return defineCall({
351
- address: Addresses.nativeMultisig,
352
- abi: Abis.nativeMultisig,
433
+ address: Addresses.configurableAccount,
434
+ abi: Abis.configurableAccount,
353
435
  args: [currentConfig, nextConfig.threshold, nextConfig.owners],
354
436
  functionName: 'updateConfig',
355
437
  })
356
438
  }
357
439
 
358
440
  /**
359
- * Extracts the `MultisigConfigUpdated` event from logs.
441
+ * Extracts the `ConfigurableAccountConfigUpdated` event from logs.
360
442
  *
361
443
  * @param logs - Transaction logs.
362
444
  * @returns The configuration update event.
363
445
  */
364
446
  export function extractEvent(logs: Log[]) {
365
447
  const [log] = parseEventLogs({
366
- abi: Abis.nativeMultisig,
448
+ abi: Abis.configurableAccount,
367
449
  logs,
368
- eventName: 'MultisigConfigUpdated',
450
+ eventName: 'ConfigurableAccountConfigUpdated',
369
451
  strict: true,
370
452
  })
371
- if (!log) throw new Error('`MultisigConfigUpdated` event not found.')
453
+ if (!log)
454
+ throw new Error('`ConfigurableAccountConfigUpdated` event not found.')
372
455
  return log
373
456
  }
374
457
  }
@@ -447,3 +530,304 @@ export namespace updateConfigSync {
447
530
 
448
531
  export type ErrorType = updateConfig.ErrorType
449
532
  }
533
+
534
+ /**
535
+ * Upgrades an existing account to a configurable account in place
536
+ * ([TIP-1113](https://tips.sh/1113)).
537
+ *
538
+ * The account keeps its address and installs `owners` and `threshold`. The
539
+ * root key must sign the upgrade, which is sent as the transaction's only
540
+ * call. After the upgrade, the root key can no longer sign for the account.
541
+ *
542
+ * @example
543
+ * ```ts
544
+ * import { createClient, http } from 'viem'
545
+ * import { tempoLocalnet } from 'viem/chains'
546
+ * import { Account, Actions } from 'viem/tempo'
547
+ *
548
+ * const root = Account.fromSecp256k1('0x...')
549
+ * const client = createClient({
550
+ * chain: tempoLocalnet,
551
+ * transport: http(),
552
+ * })
553
+ *
554
+ * const hash = await Actions.accounts.upgrade(client, {
555
+ * account: root,
556
+ * owners: [{ owner: '0x...', weight: 1 }],
557
+ * threshold: 1,
558
+ * })
559
+ * ```
560
+ *
561
+ * @param client - Client.
562
+ * @param parameters - Parameters.
563
+ * @returns The transaction hash.
564
+ */
565
+ export async function upgrade<
566
+ chain extends Chain | undefined,
567
+ account extends Account | undefined,
568
+ >(
569
+ client: Client<Transport, chain, account>,
570
+ parameters: upgrade.Parameters<chain, account>,
571
+ ): Promise<upgrade.ReturnValue> {
572
+ return upgrade.inner(writeContract, client, parameters)
573
+ }
574
+
575
+ export namespace upgrade {
576
+ export type Parameters<
577
+ chain extends Chain | undefined = Chain | undefined,
578
+ account extends Account | undefined = Account | undefined,
579
+ > = UnionOmit<WriteParameters<chain, account>, 'keyAuthorization' | 'owner'> &
580
+ Args
581
+
582
+ export type Args = Pick<AccountConfig.Config, 'owners' | 'threshold'>
583
+
584
+ export type ReturnValue = WriteContractReturnType
585
+
586
+ // TODO: exhaustive error type
587
+ export type ErrorType = Errors.ExecutionErrorType | BaseErrorType
588
+
589
+ /** @internal */
590
+ export async function inner<
591
+ action extends typeof writeContract | typeof writeContractSync,
592
+ chain extends Chain | undefined,
593
+ account extends Account | undefined,
594
+ >(
595
+ action: action,
596
+ client: Client<Transport, chain, account>,
597
+ parameters: Parameters<chain, account>,
598
+ ): Promise<ReturnType<action>> {
599
+ const { owners, threshold, ...rest } = parameters
600
+ for (const key of [
601
+ 'authorizationList',
602
+ 'calls',
603
+ 'data',
604
+ 'keyAuthorization',
605
+ 'to',
606
+ ])
607
+ if ((rest as Record<string, unknown>)[key] !== undefined)
608
+ throw new Error(
609
+ `\`${key}\` is not supported: the upgrade must be the only call, without authorizations.`,
610
+ )
611
+ const account = rest.account ?? client.account
612
+ if (typeof account === 'object') {
613
+ if (account.source === 'configurable')
614
+ throw new Error('The account is already a configurable account.')
615
+ if (account.source === 'accessKey')
616
+ throw new Error(
617
+ 'The root key must sign the upgrade, not an access key.',
618
+ )
619
+ }
620
+ // Always send a Tempo envelope, so relays recognize and cache the upgrade.
621
+ return (await withExecutionError(action)(client, {
622
+ ...rest,
623
+ ...upgrade.call({ owners, threshold }),
624
+ type: 'tempo',
625
+ } as never)) as never
626
+ }
627
+
628
+ /**
629
+ * Defines a call to the `upgradeAccount` function.
630
+ *
631
+ * The call must be the only call in a transaction signed by the account's
632
+ * root key.
633
+ *
634
+ * @example
635
+ * ```ts
636
+ * import { Actions } from 'viem/tempo'
637
+ *
638
+ * const call = Actions.accounts.upgrade.call({
639
+ * owners: [{ owner: '0x...', weight: 1 }],
640
+ * threshold: 1,
641
+ * })
642
+ * ```
643
+ *
644
+ * @param args - Owners and threshold to install.
645
+ * @returns The call.
646
+ */
647
+ export function call(args: Args) {
648
+ const { owners, threshold } = AccountConfig.from(args)
649
+ return defineCall({
650
+ address: Addresses.configurableAccount,
651
+ abi: Abis.configurableAccount,
652
+ args: [threshold, owners],
653
+ functionName: 'upgradeAccount',
654
+ })
655
+ }
656
+
657
+ /**
658
+ * Extracts the `AccountUpgraded` event from logs.
659
+ *
660
+ * @param logs - Transaction logs.
661
+ * @returns The account upgrade event.
662
+ */
663
+ export function extractEvent(logs: Log[]) {
664
+ const [log] = parseEventLogs({
665
+ abi: Abis.configurableAccount,
666
+ logs,
667
+ eventName: 'AccountUpgraded',
668
+ strict: true,
669
+ })
670
+ if (!log) throw new Error('`AccountUpgraded` event not found.')
671
+ return log
672
+ }
673
+ }
674
+
675
+ /**
676
+ * Upgrades an existing account to a configurable account in place
677
+ * ([TIP-1113](https://tips.sh/1113)) and waits for confirmation.
678
+ *
679
+ * @example
680
+ * ```ts
681
+ * import { createClient, http } from 'viem'
682
+ * import { tempoLocalnet } from 'viem/chains'
683
+ * import { Account, Actions } from 'viem/tempo'
684
+ *
685
+ * const root = Account.fromSecp256k1('0x...')
686
+ * const client = createClient({
687
+ * chain: tempoLocalnet,
688
+ * transport: http(),
689
+ * })
690
+ *
691
+ * const { config } = await Actions.accounts.upgradeSync(client, {
692
+ * account: root,
693
+ * owners: [{ owner: '0x...', weight: 1 }],
694
+ * threshold: 1,
695
+ * })
696
+ * ```
697
+ *
698
+ * @param client - Client.
699
+ * @param parameters - Parameters.
700
+ * @returns The installed configuration, its commitment, and the transaction receipt.
701
+ */
702
+ export async function upgradeSync<
703
+ chain extends Chain | undefined,
704
+ account extends Account | undefined,
705
+ >(
706
+ client: Client<Transport, chain, account>,
707
+ parameters: upgradeSync.Parameters<chain, account>,
708
+ ): Promise<upgradeSync.ReturnValue> {
709
+ const { throwOnReceiptRevert = true, ...rest } = parameters
710
+ const receipt = await upgrade.inner(writeContractSync, client, {
711
+ ...rest,
712
+ throwOnReceiptRevert,
713
+ } as never)
714
+ // Pending and (with `throwOnReceiptRevert: false`) reverted receipts carry no upgrade event.
715
+ if ((receipt as Transaction.TransactionReceipt).status !== 'success')
716
+ return { receipt } as never
717
+ const { args } = upgrade.extractEvent(receipt.logs)
718
+ return {
719
+ account: args.account,
720
+ commitment: args.commitment,
721
+ config: AccountConfig.from({
722
+ owners: args.owners,
723
+ salt: args.salt,
724
+ threshold: args.threshold,
725
+ version: 1n,
726
+ }),
727
+ receipt,
728
+ } as never
729
+ }
730
+
731
+ export namespace upgradeSync {
732
+ export type Parameters<
733
+ chain extends Chain | undefined = Chain | undefined,
734
+ account extends Account | undefined = Account | undefined,
735
+ > = upgrade.Parameters<chain, account>
736
+
737
+ export type Args = upgrade.Args
738
+
739
+ /** Pending and reverted receipts carry no installed configuration. */
740
+ export type ReturnValue = OneOf<
741
+ | {
742
+ account: Address
743
+ commitment: Hex.Hex
744
+ config: AccountConfig.Config
745
+ receipt: Transaction.TransactionReceipt
746
+ }
747
+ | { receipt: Transaction.TransactionReceipt }
748
+ >
749
+
750
+ export type ErrorType = upgrade.ErrorType
751
+ }
752
+
753
+ /**
754
+ * Verifies a configurable account signature against the account's current
755
+ * configuration with the signature verifier precompile.
756
+ *
757
+ * Returns `false` when the signature is for another account or a config that
758
+ * is not the account's current config. Throws when the signature is malformed
759
+ * or its approvals do not reach quorum.
760
+ *
761
+ * @example
762
+ * ```ts
763
+ * import { createClient, http } from 'viem'
764
+ * import { tempo } from 'viem/chains'
765
+ * import { Actions } from 'viem/tempo'
766
+ *
767
+ * const client = createClient({
768
+ * chain: tempo,
769
+ * transport: http(),
770
+ * })
771
+ *
772
+ * const valid = await Actions.accounts.verifyHash(client, {
773
+ * account: '0x...',
774
+ * hash: '0x...',
775
+ * signature: '0x...',
776
+ * })
777
+ * ```
778
+ *
779
+ * @param client - Client.
780
+ * @param parameters - Parameters.
781
+ * @returns Whether the signature is valid for the account.
782
+ */
783
+ export async function verifyHash<
784
+ chain extends Chain | undefined,
785
+ account extends Account | undefined,
786
+ >(
787
+ client: Client<Transport, chain, account>,
788
+ parameters: verifyHash.Parameters,
789
+ ): Promise<verifyHash.ReturnValue> {
790
+ const { account, hash, signature, ...rest } = parameters
791
+ return withExecutionError(readContract)(client, {
792
+ ...rest,
793
+ ...verifyHash.call({ account, hash, signature }),
794
+ })
795
+ }
796
+
797
+ export namespace verifyHash {
798
+ export type Parameters = ReadParameters & Args
799
+
800
+ export type Args = {
801
+ /** Configurable account address the signature is expected to belong to. */
802
+ account: Address
803
+ /** Original message hash that was signed. */
804
+ hash: Hex.Hex
805
+ /** Configurable account signature envelope. */
806
+ signature: Hex.Hex
807
+ }
808
+
809
+ export type ReturnValue = ReadContractReturnType<
810
+ typeof Abis.signatureVerifier,
811
+ 'verifyConfigurableAccount',
812
+ never
813
+ >
814
+
815
+ /**
816
+ * Defines a call to the `verifyConfigurableAccount` function on the
817
+ * signature verifier precompile.
818
+ *
819
+ * @param args - Arguments.
820
+ * @returns The call.
821
+ */
822
+ export function call(args: Args) {
823
+ const { account, hash, signature } = args
824
+ return defineCall({
825
+ address: Addresses.signatureVerifier,
826
+ abi: Abis.signatureVerifier,
827
+ args: [account, hash, signature],
828
+ functionName: 'verifyConfigurableAccount',
829
+ })
830
+ }
831
+
832
+ export type ErrorType = Errors.ExecutionErrorType | BaseErrorType
833
+ }
@@ -9,8 +9,11 @@ import {
9
9
  } from 'ox/tempo'
10
10
  import { getCode } from '../actions/public/getCode.js'
11
11
  import { getTransaction } from '../actions/public/getTransaction.js'
12
+ import { readContract } from '../actions/public/readContract.js'
12
13
  import { verifyHash } from '../actions/public/verifyHash.js'
13
14
  import { maxUint256 } from '../constants/number.js'
15
+ import { BaseError } from '../errors/base.js'
16
+ import { ContractFunctionRevertedError } from '../errors/contract.js'
14
17
  import type { Chain, ChainConfig as viem_ChainConfig } from '../types/chain.js'
15
18
  import { isAddressEqual } from '../utils/address/isAddressEqual.js'
16
19
  import { extendSchema } from '../utils/chain/defineChain.js'
@@ -22,7 +25,10 @@ import { keccak256 } from '../utils/hash/keccak256.js'
22
25
  import type { SerializeTransactionFn } from '../utils/transaction/serializeTransaction.js'
23
26
  import type { Account, ConfigurableAccount } from './Account.js'
24
27
  import { getMetadata } from './actions/accessKey.js'
25
- import { getConfig } from './actions/accounts.js'
28
+ import {
29
+ getConfig,
30
+ verifyHash as verifyConfigurableAccount,
31
+ } from './actions/accounts.js'
26
32
  import * as Formatters from './Formatters.js'
27
33
  import type { Hardfork } from './Hardfork.js'
28
34
  import * as Concurrent from './internal/concurrent.js'
@@ -318,10 +324,15 @@ export const chainConfig = {
318
324
  async verifyHash(client, parameters) {
319
325
  const { address, hash, signature, mode } = parameters
320
326
 
327
+ const serialized = (() => {
328
+ if (typeof signature === 'string') return signature
329
+ if (signature instanceof Uint8Array) return Hex.fromBytes(signature)
330
+ return undefined
331
+ })()
321
332
  const envelope = (() => {
322
- if (typeof signature !== 'string') return
333
+ if (!serialized) return
323
334
  try {
324
- return SignatureEnvelope.deserialize(signature)
335
+ return SignatureEnvelope.deserialize(serialized)
325
336
  } catch {
326
337
  return undefined
327
338
  }
@@ -370,6 +381,33 @@ export const chainConfig = {
370
381
  })
371
382
  }
372
383
 
384
+ // Configurable account signatures are verified against the account's
385
+ // current config commitment, which only the chain knows.
386
+ // Reverts (malformed signatures or approvals below quorum) are invalid signatures.
387
+ if (envelope.type === 'configurable')
388
+ return await (
389
+ readContract(client, {
390
+ blockHash: parameters.blockHash,
391
+ blockNumber: parameters.blockNumber,
392
+ blockTag: parameters.blockTag,
393
+ requireCanonical: parameters.requireCanonical,
394
+ ...verifyConfigurableAccount.call({
395
+ account: address,
396
+ hash,
397
+ signature: serialized as Hex.Hex,
398
+ }),
399
+ } as never) as Promise<boolean>
400
+ ).catch((error) => {
401
+ if (
402
+ error instanceof BaseError &&
403
+ error.walk(
404
+ (cause) => cause instanceof ContractFunctionRevertedError,
405
+ )
406
+ )
407
+ return false
408
+ throw error
409
+ })
410
+
373
411
  // Stateless, non-keychain signature envelopes (P256, WebAuthn) can be
374
412
  // verified directly without a network request.
375
413
  if (envelope.type === 'p256' || envelope.type === 'webAuthn') {
@@ -225,8 +225,16 @@ async function submit(options: submit.Options) {
225
225
  if (
226
226
  options.method === 'eth_sendRawTransaction' ||
227
227
  options.method === 'eth_sendRawTransactionSync'
228
- )
229
- return await options.next(options.request, options.requestOptions)
228
+ ) {
229
+ const result = await options.next(options.request, options.requestOptions)
230
+ // Cache after the node accepts the transaction, so only validly signed upgrades are cached.
231
+ // Caching is best-effort: a store failure must not mask an accepted upgrade.
232
+ if (transaction)
233
+ await cacheUpgradeConfig({ store: options.store, transaction }).catch(
234
+ () => {},
235
+ )
236
+ return result
237
+ }
230
238
  throw new RpcResponse.InvalidParamsError({
231
239
  message: 'Expected a configurable account transaction signature.',
232
240
  })
@@ -758,7 +766,7 @@ function assertConfig(options: {
758
766
  if (
759
767
  config.version === 0n &&
760
768
  AccountConfig.getAddress(config, {
761
- factory: Addresses.nativeMultisigFactory,
769
+ factory: Addresses.configurableAccountRecoveryFactory,
762
770
  }).toLowerCase() !== account.toLowerCase()
763
771
  )
764
772
  throw new RpcResponse.InvalidParamsError({
@@ -798,13 +806,16 @@ async function cacheNextConfigs(options: cacheNextConfigs.Options) {
798
806
  for (const call of options.transaction.calls) {
799
807
  if (
800
808
  !call.to ||
801
- !isAddressEqual(call.to, Addresses.nativeMultisig) ||
809
+ !isAddressEqual(call.to, Addresses.configurableAccount) ||
802
810
  !call.data
803
811
  )
804
812
  continue
805
813
  const decoded = (() => {
806
814
  try {
807
- return decodeFunctionData({ abi: Abis.nativeMultisig, data: call.data })
815
+ return decodeFunctionData({
816
+ abi: Abis.configurableAccount,
817
+ data: call.data,
818
+ })
808
819
  } catch {
809
820
  return undefined
810
821
  }
@@ -855,6 +866,52 @@ declare namespace cacheNextConfigs {
855
866
  }
856
867
  }
857
868
 
869
+ /** Caches the config installed by a root-signed account upgrade. */
870
+ // biome-ignore lint/correctness/noUnusedVariables: declaration merge
871
+ async function cacheUpgradeConfig(options: cacheUpgradeConfig.Options) {
872
+ const { calls, from, signature } = options.transaction
873
+ const [call] = calls
874
+ if (
875
+ !from ||
876
+ !signature ||
877
+ signature.type === 'keychain' ||
878
+ calls.length !== 1 ||
879
+ !call?.to ||
880
+ !isAddressEqual(call.to, Addresses.configurableAccount) ||
881
+ !call.data
882
+ )
883
+ return
884
+ const config = (() => {
885
+ try {
886
+ const decoded = decodeFunctionData({
887
+ abi: Abis.configurableAccount,
888
+ data: call.data,
889
+ })
890
+ if (decoded.functionName !== 'upgradeAccount') return undefined
891
+ const [threshold, owners] = decoded.args
892
+ return AccountConfig.fromUpgrade(from, { owners, threshold })
893
+ } catch {
894
+ return undefined
895
+ }
896
+ })()
897
+ if (!config) return
898
+ await ConfigStore.write(options.store, {
899
+ address: from,
900
+ commitment: AccountConfig.getCommitment(config),
901
+ config,
902
+ })
903
+ }
904
+
905
+ declare namespace cacheUpgradeConfig {
906
+ /** Parameters for {@link cacheUpgradeConfig}. */
907
+ export type Options = {
908
+ /** Shared account store. */
909
+ store: Store.Store
910
+ /** Submitted transaction containing a potential account upgrade. */
911
+ transaction: Transaction.TransactionSerializableTempo
912
+ }
913
+ }
914
+
858
915
  /** Validates primitive approvals and translates invalid input into an RPC error. @internal */
859
916
  export async function selectApprovals(
860
917
  options: AccountOperation.selectApprovals.Options,
@@ -174,8 +174,8 @@ export function create(options: Relay.feePayer.Options): Relay.Plugin {
174
174
  Object.assign(filled, { feeToken: token })
175
175
 
176
176
  const defer =
177
- typeof normalized.multisigSimulation === 'object' &&
178
- normalized.multisigSimulation !== null
177
+ typeof normalized.configurableAccountSimulation === 'object' &&
178
+ normalized.configurableAccountSimulation !== null
179
179
 
180
180
  // Bound before validation so the validated envelope is the one signed, and returned signatures expire.
181
181
  const signs = !!options.account && !external && !defer
@@ -237,8 +237,8 @@ export function create(options: Relay.feePayer.Options): Relay.Plugin {
237
237
  typeof parameters.feePayer === 'string' ||
238
238
  parameters.feePayer === false ||
239
239
  !result.capabilities?.sponsored ||
240
- (typeof parameters.multisigSimulation === 'object' &&
241
- parameters.multisigSimulation !== null)
240
+ (typeof parameters.configurableAccountSimulation === 'object' &&
241
+ parameters.configurableAccountSimulation !== null)
242
242
  )
243
243
  return undefined
244
244
  const transaction = Utils.normalizeTempoTransaction(result.tx)
@@ -39,6 +39,10 @@ export function withRetry<data>(
39
39
  }: WithRetryParameters = {},
40
40
  ) {
41
41
  return new Promise<data>((resolve, reject) => {
42
+ // Settles on abort even when `fn` ignores the signal.
43
+ const onAbort = () => reject(getAbortError(signal))
44
+ signal?.addEventListener('abort', onAbort, { once: true })
45
+
42
46
  const attemptRetry = async ({ count = 0 } = {}): Promise<void> => {
43
47
  if (signal?.aborted) {
44
48
  reject(getAbortError(signal))
@@ -79,6 +83,8 @@ export function withRetry<data>(
79
83
  reject(err)
80
84
  }
81
85
  }
82
- void attemptRetry().catch(reject)
86
+ void attemptRetry()
87
+ .catch(reject)
88
+ .finally(() => signal?.removeEventListener('abort', onAbort))
83
89
  })
84
90
  }