viem 2.57.4 → 2.57.5

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 (223) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/_cjs/actions/public/waitForTransactionReceipt.js +5 -2
  3. package/_cjs/actions/public/waitForTransactionReceipt.js.map +1 -1
  4. package/_cjs/actions/wallet/waitForCallsStatus.js +25 -2
  5. package/_cjs/actions/wallet/waitForCallsStatus.js.map +1 -1
  6. package/_cjs/chains/definitions/arcTestnet.js +11 -8
  7. package/_cjs/chains/definitions/arcTestnet.js.map +1 -1
  8. package/_cjs/chains/definitions/qmsTestnet.js +30 -0
  9. package/_cjs/chains/definitions/qmsTestnet.js.map +1 -0
  10. package/_cjs/chains/definitions/tempo.js +1 -1
  11. package/_cjs/chains/definitions/tempoModerato.js +2 -2
  12. package/_cjs/chains/index.js +7 -5
  13. package/_cjs/chains/index.js.map +1 -1
  14. package/_cjs/clients/transports/ipc.js +19 -10
  15. package/_cjs/clients/transports/ipc.js.map +1 -1
  16. package/_cjs/clients/transports/webSocket.js +19 -10
  17. package/_cjs/clients/transports/webSocket.js.map +1 -1
  18. package/_cjs/errors/version.js +1 -1
  19. package/_cjs/tempo/Account.js +40 -40
  20. package/_cjs/tempo/Account.js.map +1 -1
  21. package/_cjs/tempo/Decorator.js +9 -8
  22. package/_cjs/tempo/Decorator.js.map +1 -1
  23. package/_cjs/tempo/ExecutionError.js +1 -1
  24. package/_cjs/tempo/ExecutionError.js.map +1 -1
  25. package/_cjs/tempo/Formatters.js +8 -7
  26. package/_cjs/tempo/Formatters.js.map +1 -1
  27. package/_cjs/tempo/KeyAuthorizationManager.js +25 -16
  28. package/_cjs/tempo/KeyAuthorizationManager.js.map +1 -1
  29. package/_cjs/tempo/Relay.js +9 -4
  30. package/_cjs/tempo/Relay.js.map +1 -1
  31. package/_cjs/tempo/Transaction.js +9 -9
  32. package/_cjs/tempo/Transaction.js.map +1 -1
  33. package/_cjs/tempo/Transport.js +21 -11
  34. package/_cjs/tempo/Transport.js.map +1 -1
  35. package/_cjs/tempo/{multisig → accounts}/Config.js +8 -8
  36. package/_cjs/tempo/{multisig → accounts}/Config.js.map +1 -1
  37. package/_cjs/tempo/{multisig → accounts}/Operation.js +10 -10
  38. package/_cjs/tempo/{multisig → accounts}/Operation.js.map +1 -1
  39. package/_cjs/tempo/{multisig → accounts}/Signature.js +2 -2
  40. package/_cjs/tempo/accounts/Signature.js.map +1 -0
  41. package/_cjs/tempo/actions/accessKey.js +60 -37
  42. package/_cjs/tempo/actions/accessKey.js.map +1 -1
  43. package/_cjs/tempo/actions/{multisig.js → accounts.js} +13 -18
  44. package/_cjs/tempo/actions/accounts.js.map +1 -0
  45. package/_cjs/tempo/actions/index.js +2 -2
  46. package/_cjs/tempo/actions/index.js.map +1 -1
  47. package/_cjs/tempo/chainConfig.js +37 -37
  48. package/_cjs/tempo/chainConfig.js.map +1 -1
  49. package/_cjs/tempo/index.js +3 -3
  50. package/_cjs/tempo/index.js.map +1 -1
  51. package/_cjs/tempo/internal/relay/{multisig.js → accounts.js} +90 -93
  52. package/_cjs/tempo/internal/relay/accounts.js.map +1 -0
  53. package/_cjs/tempo/internal/relay/keyAuthorization.js +296 -0
  54. package/_cjs/tempo/internal/relay/keyAuthorization.js.map +1 -0
  55. package/_cjs/tempo/internal/relay/plugin.js +17 -7
  56. package/_cjs/tempo/internal/relay/plugin.js.map +1 -1
  57. package/_cjs/utils/hash/normalizeSignature.js +1 -1
  58. package/_cjs/utils/hash/normalizeSignature.js.map +1 -1
  59. package/_cjs/utils/rpc/http.js +66 -31
  60. package/_cjs/utils/rpc/http.js.map +1 -1
  61. package/_cjs/utils/rpc/socket.js +1 -1
  62. package/_cjs/utils/rpc/socket.js.map +1 -1
  63. package/_esm/actions/public/waitForTransactionReceipt.js +5 -2
  64. package/_esm/actions/public/waitForTransactionReceipt.js.map +1 -1
  65. package/_esm/actions/wallet/waitForCallsStatus.js +39 -2
  66. package/_esm/actions/wallet/waitForCallsStatus.js.map +1 -1
  67. package/_esm/chains/definitions/arcTestnet.js +11 -8
  68. package/_esm/chains/definitions/arcTestnet.js.map +1 -1
  69. package/_esm/chains/definitions/qmsTestnet.js +27 -0
  70. package/_esm/chains/definitions/qmsTestnet.js.map +1 -0
  71. package/_esm/chains/definitions/tempo.js +1 -1
  72. package/_esm/chains/definitions/tempoModerato.js +2 -2
  73. package/_esm/chains/index.js +1 -0
  74. package/_esm/chains/index.js.map +1 -1
  75. package/_esm/clients/transports/ipc.js +21 -10
  76. package/_esm/clients/transports/ipc.js.map +1 -1
  77. package/_esm/clients/transports/webSocket.js +21 -10
  78. package/_esm/clients/transports/webSocket.js.map +1 -1
  79. package/_esm/errors/version.js +1 -1
  80. package/_esm/tempo/Account.js +42 -42
  81. package/_esm/tempo/Account.js.map +1 -1
  82. package/_esm/tempo/Client.js +3 -3
  83. package/_esm/tempo/Decorator.js +9 -8
  84. package/_esm/tempo/Decorator.js.map +1 -1
  85. package/_esm/tempo/ExecutionError.js +1 -1
  86. package/_esm/tempo/ExecutionError.js.map +1 -1
  87. package/_esm/tempo/Formatters.js +11 -10
  88. package/_esm/tempo/Formatters.js.map +1 -1
  89. package/_esm/tempo/KeyAuthorizationManager.js +58 -16
  90. package/_esm/tempo/KeyAuthorizationManager.js.map +1 -1
  91. package/_esm/tempo/Relay.js +49 -8
  92. package/_esm/tempo/Relay.js.map +1 -1
  93. package/_esm/tempo/Transaction.js +11 -11
  94. package/_esm/tempo/Transaction.js.map +1 -1
  95. package/_esm/tempo/Transport.js +22 -12
  96. package/_esm/tempo/Transport.js.map +1 -1
  97. package/_esm/tempo/{multisig → accounts}/Config.js +14 -14
  98. package/_esm/tempo/{multisig → accounts}/Config.js.map +1 -1
  99. package/_esm/tempo/{multisig → accounts}/Operation.js +17 -17
  100. package/_esm/tempo/{multisig → accounts}/Operation.js.map +1 -1
  101. package/_esm/tempo/accounts/Signature.js +9 -0
  102. package/_esm/tempo/accounts/Signature.js.map +1 -0
  103. package/_esm/tempo/actions/accessKey.js +93 -38
  104. package/_esm/tempo/actions/accessKey.js.map +1 -1
  105. package/_esm/tempo/actions/{multisig.js → accounts.js} +31 -36
  106. package/_esm/tempo/actions/accounts.js.map +1 -0
  107. package/_esm/tempo/actions/index.js +1 -1
  108. package/_esm/tempo/actions/index.js.map +1 -1
  109. package/_esm/tempo/chainConfig.js +38 -38
  110. package/_esm/tempo/chainConfig.js.map +1 -1
  111. package/_esm/tempo/index.js +2 -1
  112. package/_esm/tempo/index.js.map +1 -1
  113. package/_esm/tempo/internal/relay/{multisig.js → accounts.js} +93 -98
  114. package/_esm/tempo/internal/relay/accounts.js.map +1 -0
  115. package/_esm/tempo/internal/relay/feePayer.js +1 -1
  116. package/_esm/tempo/internal/relay/feePayer.js.map +1 -1
  117. package/_esm/tempo/internal/relay/keyAuthorization.js +334 -0
  118. package/_esm/tempo/internal/relay/keyAuthorization.js.map +1 -0
  119. package/_esm/tempo/internal/relay/plugin.js +15 -6
  120. package/_esm/tempo/internal/relay/plugin.js.map +1 -1
  121. package/_esm/utils/hash/normalizeSignature.js +3 -2
  122. package/_esm/utils/hash/normalizeSignature.js.map +1 -1
  123. package/_esm/utils/rpc/http.js +74 -31
  124. package/_esm/utils/rpc/http.js.map +1 -1
  125. package/_esm/utils/rpc/socket.js +3 -1
  126. package/_esm/utils/rpc/socket.js.map +1 -1
  127. package/_types/actions/public/waitForTransactionReceipt.d.ts.map +1 -1
  128. package/_types/actions/wallet/waitForCallsStatus.d.ts.map +1 -1
  129. package/_types/chains/definitions/arcTestnet.d.ts +5 -5
  130. package/_types/chains/definitions/arcTestnet.d.ts.map +1 -1
  131. package/_types/chains/definitions/qmsTestnet.d.ts +50 -0
  132. package/_types/chains/definitions/qmsTestnet.d.ts.map +1 -0
  133. package/_types/chains/definitions/tempo.d.ts +2 -2
  134. package/_types/chains/definitions/tempoModerato.d.ts +4 -4
  135. package/_types/chains/index.d.ts +1 -0
  136. package/_types/chains/index.d.ts.map +1 -1
  137. package/_types/clients/transports/ipc.d.ts.map +1 -1
  138. package/_types/clients/transports/webSocket.d.ts.map +1 -1
  139. package/_types/errors/version.d.ts +1 -1
  140. package/_types/tempo/Account.d.ts +28 -28
  141. package/_types/tempo/Account.d.ts.map +1 -1
  142. package/_types/tempo/Client.d.ts +3 -3
  143. package/_types/tempo/Decorator.d.ts +63 -37
  144. package/_types/tempo/Decorator.d.ts.map +1 -1
  145. package/_types/tempo/Formatters.d.ts.map +1 -1
  146. package/_types/tempo/KeyAuthorizationManager.d.ts +45 -2
  147. package/_types/tempo/KeyAuthorizationManager.d.ts.map +1 -1
  148. package/_types/tempo/Relay.d.ts +62 -12
  149. package/_types/tempo/Relay.d.ts.map +1 -1
  150. package/_types/tempo/Transaction.d.ts +14 -11
  151. package/_types/tempo/Transaction.d.ts.map +1 -1
  152. package/_types/tempo/Transport.d.ts +20 -7
  153. package/_types/tempo/Transport.d.ts.map +1 -1
  154. package/_types/tempo/{multisig → accounts}/Config.d.ts +7 -7
  155. package/_types/tempo/accounts/Config.d.ts.map +1 -0
  156. package/_types/tempo/{multisig → accounts}/Operation.d.ts +7 -7
  157. package/_types/tempo/accounts/Operation.d.ts.map +1 -0
  158. package/_types/tempo/{multisig → accounts}/Signature.d.ts +1 -1
  159. package/_types/tempo/accounts/Signature.d.ts.map +1 -0
  160. package/_types/tempo/actions/accessKey.d.ts +59 -17
  161. package/_types/tempo/actions/accessKey.d.ts.map +1 -1
  162. package/_types/tempo/actions/{multisig.d.ts → accounts.d.ts} +28 -28
  163. package/_types/tempo/actions/{multisig.d.ts.map → accounts.d.ts.map} +1 -1
  164. package/_types/tempo/actions/index.d.ts +1 -1
  165. package/_types/tempo/actions/index.d.ts.map +1 -1
  166. package/_types/tempo/index.d.ts +3 -2
  167. package/_types/tempo/index.d.ts.map +1 -1
  168. package/_types/tempo/internal/relay/accounts.d.ts +15 -0
  169. package/_types/tempo/internal/relay/accounts.d.ts.map +1 -0
  170. package/_types/tempo/internal/relay/keyAuthorization.d.ts +25 -0
  171. package/_types/tempo/internal/relay/keyAuthorization.d.ts.map +1 -0
  172. package/_types/tempo/internal/relay/plugin.d.ts +6 -3
  173. package/_types/tempo/internal/relay/plugin.d.ts.map +1 -1
  174. package/_types/utils/hash/normalizeSignature.d.ts.map +1 -1
  175. package/_types/utils/rpc/http.d.ts.map +1 -1
  176. package/_types/utils/rpc/socket.d.ts.map +1 -1
  177. package/actions/public/waitForTransactionReceipt.ts +8 -2
  178. package/actions/wallet/waitForCallsStatus.ts +41 -2
  179. package/chains/definitions/arcTestnet.ts +11 -8
  180. package/chains/definitions/qmsTestnet.ts +27 -0
  181. package/chains/definitions/tempo.ts +1 -1
  182. package/chains/definitions/tempoModerato.ts +2 -2
  183. package/chains/index.ts +1 -0
  184. package/clients/transports/ipc.ts +38 -29
  185. package/clients/transports/webSocket.ts +42 -33
  186. package/errors/version.ts +1 -1
  187. package/package.json +2 -2
  188. package/tempo/Account.ts +95 -84
  189. package/tempo/Client.ts +3 -3
  190. package/tempo/Decorator.ts +86 -57
  191. package/tempo/ExecutionError.ts +1 -1
  192. package/tempo/Formatters.ts +14 -13
  193. package/tempo/KeyAuthorizationManager.ts +71 -19
  194. package/tempo/Relay.ts +70 -14
  195. package/tempo/Transaction.ts +31 -23
  196. package/tempo/Transport.ts +49 -21
  197. package/tempo/{multisig → accounts}/Config.ts +21 -21
  198. package/tempo/{multisig → accounts}/Operation.ts +26 -26
  199. package/tempo/{multisig → accounts}/Signature.ts +5 -3
  200. package/tempo/actions/accessKey.ts +179 -79
  201. package/tempo/actions/{multisig.ts → accounts.ts} +47 -56
  202. package/tempo/actions/index.ts +1 -1
  203. package/tempo/chainConfig.ts +52 -52
  204. package/tempo/index.ts +5 -4
  205. package/tempo/internal/relay/{multisig.ts → accounts.ts} +118 -118
  206. package/tempo/internal/relay/feePayer.ts +1 -1
  207. package/tempo/internal/relay/keyAuthorization.ts +415 -0
  208. package/tempo/internal/relay/plugin.ts +20 -7
  209. package/utils/hash/normalizeSignature.ts +3 -2
  210. package/utils/rpc/http.ts +76 -30
  211. package/utils/rpc/socket.ts +3 -1
  212. package/_cjs/tempo/actions/multisig.js.map +0 -1
  213. package/_cjs/tempo/internal/relay/multisig.js.map +0 -1
  214. package/_cjs/tempo/multisig/Signature.js.map +0 -1
  215. package/_esm/tempo/actions/multisig.js.map +0 -1
  216. package/_esm/tempo/internal/relay/multisig.js.map +0 -1
  217. package/_esm/tempo/multisig/Signature.js +0 -9
  218. package/_esm/tempo/multisig/Signature.js.map +0 -1
  219. package/_types/tempo/internal/relay/multisig.d.ts +0 -4
  220. package/_types/tempo/internal/relay/multisig.d.ts.map +0 -1
  221. package/_types/tempo/multisig/Config.d.ts.map +0 -1
  222. package/_types/tempo/multisig/Operation.d.ts.map +0 -1
  223. package/_types/tempo/multisig/Signature.d.ts.map +0 -1
@@ -419,7 +419,7 @@ async function handleRawTransaction(options: handleRawTransaction.Options) {
419
419
  })
420
420
 
421
421
  const transaction = Transaction.deserialize(serialized)
422
- // Prefer sender recovered from raw envelope; multisig finalize path supplies fallback sender.
422
+ // Prefer sender recovered from raw envelope; configurable account finalize path supplies fallback sender.
423
423
  const sender = transaction.from ?? options.sender
424
424
 
425
425
  // Sponsorship only applies after sender has signed original transaction.
@@ -0,0 +1,415 @@
1
+ import type { Address } from 'abitype'
2
+ import * as RpcResponse from 'ox/RpcResponse'
3
+ import {
4
+ AccountConfig,
5
+ AccountOperation,
6
+ KeyAuthorization,
7
+ SignatureEnvelope,
8
+ } from 'ox/tempo'
9
+ import { getBlockNumber } from '../../../actions/public/getBlockNumber.js'
10
+ import type { Client } from '../../../clients/createClient.js'
11
+ import { BaseError } from '../../../errors/base.js'
12
+ import { isAddressEqual } from '../../../utils/address/isAddressEqual.js'
13
+ import * as Actions from '../../actions/index.js'
14
+ import type * as Relay from '../../Relay.js'
15
+ import * as Store from '../../Store.js'
16
+ import { selectApprovals, validateConfig } from './accounts.js'
17
+ import * as Plugin from './plugin.js'
18
+
19
+ /** Creates middleware that stores pending key authorizations and attaches them to fills. @internal */
20
+ export function create(
21
+ options: Relay.keyAuthorization.Options = {},
22
+ ): Relay.keyAuthorization.ReturnType {
23
+ const { store = Store.memory() } = options
24
+ if (!store.compareAndSet)
25
+ throw new RpcResponse.InvalidParamsError({
26
+ message:
27
+ 'Key authorization storage requires a store with atomic `compareAndSet`.',
28
+ })
29
+ return Plugin.keyAuthorization({
30
+ async handleRequest(context, next) {
31
+ const { request } = context
32
+
33
+ if (request.method === 'relay_saveKeyAuthorization') {
34
+ const keyAuthorization = parse(request.params?.[0])
35
+ const chainId = Number(keyAuthorization.chainId)
36
+ if (
37
+ context.options.chainId !== undefined &&
38
+ context.options.chainId !== chainId
39
+ )
40
+ throw new RpcResponse.InvalidParamsError({
41
+ message: 'Conflicting chain ids.',
42
+ })
43
+ const client = context.getClient(chainId)
44
+ const account = await check(client, keyAuthorization)
45
+ if (!account) return null
46
+ // Drop an unusable pending authorization so it cannot block this one.
47
+ await read(client, store, {
48
+ account,
49
+ accessKey: keyAuthorization.address,
50
+ chainId,
51
+ })
52
+ if (!(await write(store, { account, keyAuthorization })))
53
+ throw new RpcResponse.InvalidParamsError({
54
+ message:
55
+ 'A pending key authorization for this access key expires later.',
56
+ })
57
+ return null
58
+ }
59
+
60
+ if (request.method === 'account_approveKeyAuthorization') {
61
+ await next()
62
+ const operation = AccountOperation.fromRpc(
63
+ context.result as AccountOperation.Rpc,
64
+ ) as AccountOperation.KeyAuthorizationOperation
65
+ if (operation.status !== 'success') return
66
+ const keyAuthorization = KeyAuthorization.deserialize(
67
+ operation.keyAuthorization,
68
+ ) as KeyAuthorization.Signed
69
+ // Re-check completed operations: quorum may land after expiry, and a
70
+ // stored success may predate a config rotation. The approval itself
71
+ // succeeded, so an authorization that fails these checks, or loses to a
72
+ // newer pending one, is skipped rather than reported. Operational
73
+ // failures propagate so callers know storage did not complete.
74
+ const chainId = Number(keyAuthorization.chainId)
75
+ const client = context.getClient(chainId)
76
+ const account = await check(client, keyAuthorization).catch((error) => {
77
+ if (error instanceof RpcResponse.InvalidParamsError) return undefined
78
+ throw error
79
+ })
80
+ if (!account) return
81
+ await read(client, store, {
82
+ account,
83
+ accessKey: keyAuthorization.address,
84
+ chainId,
85
+ })
86
+ await write(store, { account, keyAuthorization })
87
+ return
88
+ }
89
+
90
+ if (request.method !== 'eth_fillTransaction') return next()
91
+
92
+ const parameters = request.params?.[0] as Record<string, unknown>
93
+ const { from: account, keyId: accessKey } = parameters
94
+ if (
95
+ parameters.keyAuthorization ||
96
+ typeof account !== 'string' ||
97
+ typeof accessKey !== 'string'
98
+ )
99
+ return next()
100
+
101
+ const pending = await read(context.client, store, {
102
+ account: account as Address,
103
+ accessKey: accessKey as Address,
104
+ chainId: context.options.chainId!,
105
+ })
106
+ if (!pending) return next()
107
+ const metadata = await Actions.accessKey.getMetadata(context.client, {
108
+ account: account as Address,
109
+ accessKey: accessKey as Address,
110
+ })
111
+ // The key already landed onchain, so the authorization is spent.
112
+ if (isActive(metadata, accessKey as Address)) {
113
+ await remove(store, pending)
114
+ return next()
115
+ }
116
+ const keyAuthorization = KeyAuthorization.toRpc(pending.keyAuthorization)
117
+
118
+ // Attach before forwarding so gas estimation and later plugins see the authorization.
119
+ context.request = {
120
+ ...request,
121
+ params: [{ ...parameters, keyAuthorization }],
122
+ }
123
+ return next()
124
+ },
125
+ })
126
+ }
127
+
128
+ /** Parses a signed key authorization from RPC parameters. */
129
+ function parse(value: unknown) {
130
+ const keyAuthorization = (() => {
131
+ try {
132
+ if (typeof value === 'string')
133
+ return KeyAuthorization.deserialize(value as `0x${string}`)
134
+ return KeyAuthorization.fromRpc(value as KeyAuthorization.Rpc)
135
+ } catch {
136
+ return undefined
137
+ }
138
+ })()
139
+ if (!keyAuthorization?.signature)
140
+ throw new RpcResponse.InvalidParamsError({
141
+ message: 'Expected a signed key authorization.',
142
+ })
143
+ return keyAuthorization as KeyAuthorization.Signed
144
+ }
145
+
146
+ /**
147
+ * Validates an authorization and returns the account it authorizes, or `undefined`
148
+ * when the access key is already active onchain.
149
+ */
150
+ async function check(
151
+ client: Client,
152
+ keyAuthorization: KeyAuthorization.Signed,
153
+ ): Promise<Address | undefined> {
154
+ if (isExpired(keyAuthorization))
155
+ throw new RpcResponse.InvalidParamsError({
156
+ message: 'Key authorization has expired.',
157
+ })
158
+ const account = await verify(client, keyAuthorization)
159
+ if (await isBurned(client, { account, keyAuthorization }))
160
+ throw new RpcResponse.InvalidParamsError({
161
+ message: 'Key authorization witness has been burned.',
162
+ })
163
+ // Authorizations for keys that are already active would only be removed on the next fill.
164
+ const metadata = await Actions.accessKey.getMetadata(client, {
165
+ account,
166
+ accessKey: keyAuthorization.address,
167
+ })
168
+ if (isActive(metadata, keyAuthorization.address)) return undefined
169
+ return account
170
+ }
171
+
172
+ /** Verifies the authorization signature and returns the account it authorizes. */
173
+ async function verify(
174
+ client: Client,
175
+ keyAuthorization: KeyAuthorization.Signed,
176
+ ): Promise<Address> {
177
+ const { signature, ...unsigned } = keyAuthorization
178
+ const payload = KeyAuthorization.getSignPayload(unsigned)
179
+
180
+ if (signature.type === 'configurable') {
181
+ if (
182
+ !keyAuthorization.account ||
183
+ !isAddressEqual(keyAuthorization.account, signature.account)
184
+ )
185
+ throw new RpcResponse.InvalidParamsError({
186
+ message:
187
+ 'Configurable account key authorization account does not match its signature.',
188
+ })
189
+ const config = AccountConfig.from(signature.config)
190
+ await validateConfig({
191
+ account: signature.account,
192
+ blockNumber: await getBlockNumber(client, { cacheTime: 0 }),
193
+ client: client as never,
194
+ config,
195
+ })
196
+ const approvals = await selectApprovals({
197
+ account: signature.account,
198
+ approvals: signature.signatures.map((signature) =>
199
+ SignatureEnvelope.serialize(signature),
200
+ ),
201
+ config,
202
+ hash: AccountConfig.getSignPayload({
203
+ account: signature.account,
204
+ config,
205
+ payload,
206
+ }),
207
+ })
208
+ if (approvals.weight < approvals.threshold)
209
+ throw new RpcResponse.InvalidParamsError({
210
+ message:
211
+ 'Configurable account key authorization has not reached quorum.',
212
+ })
213
+ return signature.account
214
+ }
215
+
216
+ const signer = (() => {
217
+ try {
218
+ const signer = SignatureEnvelope.extractAddress({ payload, signature })
219
+ if (SignatureEnvelope.verify(signature, { address: signer, payload }))
220
+ return signer
221
+ } catch {}
222
+ throw new RpcResponse.InvalidParamsError({
223
+ message: 'Invalid key authorization signature.',
224
+ })
225
+ })()
226
+
227
+ // Admin-signed authorizations can only be submitted by the admin key itself,
228
+ // so only authorizations signed by the account can be attached for the new key.
229
+ if (
230
+ keyAuthorization.account &&
231
+ !isAddressEqual(keyAuthorization.account, signer)
232
+ )
233
+ throw new RpcResponse.InvalidParamsError({
234
+ message: 'Key authorization must be signed by its account.',
235
+ })
236
+ return signer
237
+ }
238
+
239
+ /**
240
+ * Reads the pending authorization for an access key. Entries that are malformed,
241
+ * expired, or whose witness has been burned are removed.
242
+ */
243
+ async function read(
244
+ client: Client,
245
+ store: Store.Atomic,
246
+ options: { account: Address; accessKey: Address; chainId: number },
247
+ ) {
248
+ const { account } = options
249
+ const key = getKey(options)
250
+ const value = await store.getItem(key)
251
+ if (!value) return undefined
252
+ const keyAuthorization = (() => {
253
+ try {
254
+ return KeyAuthorization.deserialize(
255
+ value as `0x${string}`,
256
+ ) as KeyAuthorization.Signed
257
+ } catch {
258
+ return undefined
259
+ }
260
+ })()
261
+ if (
262
+ keyAuthorization &&
263
+ !isExpired(keyAuthorization) &&
264
+ (await isVerified(client, keyAuthorization)) &&
265
+ !(await isBurned(client, { account, keyAuthorization }))
266
+ )
267
+ return { key, keyAuthorization, value }
268
+ await remove(store, { key, value })
269
+ return undefined
270
+ }
271
+
272
+ /**
273
+ * Removes a stored authorization only while it still holds the value that was read,
274
+ * so a concurrently saved replacement survives. The empty value expires immediately.
275
+ * @internal
276
+ */
277
+ export async function remove(
278
+ store: Store.Atomic,
279
+ options: { key: string; value: string },
280
+ ) {
281
+ const { key, value } = options
282
+ await store.compareAndSet(key, value, '', { expiresAt: Date.now() })
283
+ }
284
+
285
+ /**
286
+ * Returns whether the authorization still verifies, such as after a configurable
287
+ * account rotates its config. Operational failures propagate.
288
+ */
289
+ async function isVerified(
290
+ client: Client,
291
+ keyAuthorization: KeyAuthorization.Signed,
292
+ ) {
293
+ try {
294
+ await verify(client, keyAuthorization)
295
+ return true
296
+ } catch (error) {
297
+ if (error instanceof RpcResponse.InvalidParamsError) return false
298
+ throw error
299
+ }
300
+ }
301
+
302
+ /** Returns whether the authorization's witness has been burned onchain. */
303
+ async function isBurned(
304
+ client: Client,
305
+ options: { account: Address; keyAuthorization: KeyAuthorization.Signed },
306
+ ) {
307
+ const { account, keyAuthorization } = options
308
+ if (!keyAuthorization.witness) return false
309
+ return Actions.accessKey.isWitnessBurned(client, {
310
+ account,
311
+ witness: keyAuthorization.witness,
312
+ })
313
+ }
314
+
315
+ /**
316
+ * Stores an authorization unless a pending authorization for the same key expires
317
+ * later, so a replayed older authorization cannot replace a newer one.
318
+ * @internal
319
+ */
320
+ export async function write(
321
+ store: Store.Atomic,
322
+ options: {
323
+ account: Address
324
+ keyAuthorization: KeyAuthorization.Signed
325
+ },
326
+ ): Promise<boolean> {
327
+ const { account, keyAuthorization } = options
328
+ const key = getKey({
329
+ account,
330
+ accessKey: keyAuthorization.address,
331
+ chainId: Number(keyAuthorization.chainId),
332
+ })
333
+ const value = KeyAuthorization.serialize(keyAuthorization)
334
+ const expiresAt = Math.min(
335
+ Date.now() + maxTtl,
336
+ getExpiry(keyAuthorization) * 1_000,
337
+ )
338
+ for (let attempt = 0; attempt < maxWriteAttempts; attempt++) {
339
+ const current = (await store.getItem(key)) ?? null
340
+ const existing = (() => {
341
+ if (!current) return undefined
342
+ try {
343
+ return KeyAuthorization.deserialize(current as `0x${string}`)
344
+ } catch {
345
+ return undefined
346
+ }
347
+ })()
348
+ if (existing && current!.toLowerCase() === value.toLowerCase()) return true
349
+ if (existing && getExpiry(existing) >= getExpiry(keyAuthorization))
350
+ return false
351
+ // A lost compare-and-set means another writer replaced the value, so compare again.
352
+ if (await store.compareAndSet(key, current, value, { expiresAt }))
353
+ return true
354
+ }
355
+ throw new StoreConflictError()
356
+ }
357
+
358
+ /** Maximum compare-and-set attempts before a write is abandoned. */
359
+ const maxWriteAttempts = 32
360
+
361
+ /** Maximum time a pending key authorization is kept. */
362
+ const maxTtl = 30 * 24 * 60 * 60 * 1_000
363
+
364
+ /** Returns the authorization expiry in seconds, treating no expiry as unbounded. */
365
+ function getExpiry(keyAuthorization: KeyAuthorization.KeyAuthorization) {
366
+ const { expiry } = keyAuthorization
367
+ return expiry === undefined || expiry === null
368
+ ? Number.POSITIVE_INFINITY
369
+ : Number(expiry)
370
+ }
371
+
372
+ /** Returns whether the access key is active onchain. */
373
+ function isActive(
374
+ metadata: Actions.accessKey.getMetadata.ReturnValue,
375
+ accessKey: Address,
376
+ ) {
377
+ return (
378
+ isAddressEqual(metadata.address, accessKey) &&
379
+ !metadata.isRevoked &&
380
+ metadata.expiry > BigInt(Math.floor(Date.now() / 1000))
381
+ )
382
+ }
383
+
384
+ function isExpired(keyAuthorization: KeyAuthorization.KeyAuthorization) {
385
+ const { expiry } = keyAuthorization
386
+ return (
387
+ expiry !== undefined &&
388
+ expiry !== null &&
389
+ BigInt(expiry) <= BigInt(Math.floor(Date.now() / 1000))
390
+ )
391
+ }
392
+
393
+ function getKey(options: {
394
+ account: Address
395
+ accessKey: Address
396
+ chainId: number
397
+ }) {
398
+ const { account, accessKey, chainId } = options
399
+ return [
400
+ 'keyAuthorization',
401
+ chainId,
402
+ account.toLowerCase(),
403
+ accessKey.toLowerCase(),
404
+ ].join(':')
405
+ }
406
+
407
+ /** Thrown when a pending key authorization cannot be written after repeated conflicts. */
408
+ class StoreConflictError extends BaseError {
409
+ constructor() {
410
+ super(
411
+ 'Pending key authorization could not be written after repeated conflicts.',
412
+ { name: 'Relay.KeyAuthorization.StoreConflictError' },
413
+ )
414
+ }
415
+ }
@@ -1,13 +1,26 @@
1
1
  import type * as Relay from '../../Relay.js'
2
2
 
3
- const multisigPlugins = new WeakSet<Relay.Plugin>()
3
+ const accountsPlugins = new WeakSet<Relay.Plugin>()
4
+ const keyAuthorizationPlugins = new WeakSet<Relay.Plugin>()
4
5
 
5
- /** Registers the built-in multisig plugin for transport capability detection. */
6
- export function multisig(plugin: Relay.Plugin): Relay.multisig.ReturnType {
7
- multisigPlugins.add(plugin)
8
- return plugin as Relay.multisig.ReturnType
6
+ /** Registers the built-in accounts plugin for transport capability detection. */
7
+ export function accounts(plugin: Relay.Plugin): Relay.accounts.ReturnType {
8
+ accountsPlugins.add(plugin)
9
+ return plugin as Relay.accounts.ReturnType
9
10
  }
10
11
 
11
- export function isMultisig(plugin: Relay.Plugin): boolean {
12
- return multisigPlugins.has(plugin)
12
+ export function isAccounts(plugin: Relay.Plugin): boolean {
13
+ return accountsPlugins.has(plugin)
14
+ }
15
+
16
+ /** Registers the built-in key authorization plugin for transport capability detection. */
17
+ export function keyAuthorization(
18
+ plugin: Relay.Plugin,
19
+ ): Relay.keyAuthorization.ReturnType {
20
+ keyAuthorizationPlugins.add(plugin)
21
+ return plugin as Relay.keyAuthorization.ReturnType
22
+ }
23
+
24
+ export function isKeyAuthorization(plugin: Relay.Plugin): boolean {
25
+ return keyAuthorizationPlugins.has(plugin)
13
26
  }
@@ -46,8 +46,9 @@ export function normalizeSignature(
46
46
 
47
47
  // Ignore spaces
48
48
  if (char === ' ') {
49
- // If the previous character is a separator, and the current section isn't empty, we want to deactivate.
50
- if (signature[i - 1] !== ',' && current !== ',' && current !== ',(') {
49
+ // A space directly after a `(` or `,` separator is leading whitespace.
50
+ // Otherwise, it ends the type, so we want to deactivate.
51
+ if (!current.endsWith(',') && !current.endsWith('(')) {
51
52
  current = ''
52
53
  active = false
53
54
  }
package/utils/rpc/http.ts CHANGED
@@ -115,35 +115,56 @@ export function getHttpRpcClient(
115
115
  const { headers, method, signal: signal_ } = fetchOptions
116
116
 
117
117
  try {
118
- const response = await withTimeout(
119
- async ({ signal }) => {
120
- const init: RequestInit = {
121
- ...fetchOptions,
122
- body: Array.isArray(body)
123
- ? stringify(
124
- body.map((body) => ({
118
+ const { response, responseBody } = await withTimeout(
119
+ async ({ signal: timeoutSignal }) => {
120
+ // Abort on whichever fires first so that a `signal` does not
121
+ // disable the timeout.
122
+ const { signal, cleanup } = anySignal([
123
+ signal_,
124
+ timeout > 0 ? timeoutSignal : null,
125
+ ])
126
+ try {
127
+ const init: RequestInit = {
128
+ ...fetchOptions,
129
+ body: Array.isArray(body)
130
+ ? stringify(
131
+ body.map((body) => ({
132
+ jsonrpc: '2.0',
133
+ id: body.id ?? idCache.take(),
134
+ ...body,
135
+ })),
136
+ )
137
+ : stringify({
125
138
  jsonrpc: '2.0',
126
139
  id: body.id ?? idCache.take(),
127
140
  ...body,
128
- })),
129
- )
130
- : stringify({
131
- jsonrpc: '2.0',
132
- id: body.id ?? idCache.take(),
133
- ...body,
134
- }),
135
- headers: {
136
- ...headers_url,
137
- 'Content-Type': 'application/json',
138
- ...headers,
139
- },
140
- method: method || 'POST',
141
- signal: signal_ || (timeout > 0 ? signal : null),
141
+ }),
142
+ headers: {
143
+ ...headers_url,
144
+ 'Content-Type': 'application/json',
145
+ ...headers,
146
+ },
147
+ method: method || 'POST',
148
+ signal,
149
+ }
150
+ const request = new Request(url, init)
151
+ const args = (await onRequest?.(request, init)) ?? {
152
+ ...init,
153
+ url,
154
+ }
155
+ const response = await fetchFn(args.url ?? url, args)
156
+
157
+ if (onResponse) await onResponse(response)
158
+
159
+ // Read the body within the timeout too, so a response that stalls
160
+ // after its headers cannot hang the request.
161
+ const responseBody = await readResponseBody(response, {
162
+ maxResponseBodySize,
163
+ })
164
+ return { response, responseBody }
165
+ } finally {
166
+ cleanup()
142
167
  }
143
- const request = new Request(url, init)
144
- const args = (await onRequest?.(request, init)) ?? { ...init, url }
145
- const response = await fetchFn(args.url ?? url, args)
146
- return response
147
168
  },
148
169
  {
149
170
  errorInstance: new TimeoutError({ body, url }),
@@ -152,12 +173,7 @@ export function getHttpRpcClient(
152
173
  },
153
174
  )
154
175
 
155
- if (onResponse) await onResponse(response)
156
-
157
176
  let data: any
158
- const responseBody = await readResponseBody(response, {
159
- maxResponseBodySize,
160
- })
161
177
  if (
162
178
  response.headers.get('Content-Type')?.startsWith('application/json')
163
179
  )
@@ -207,6 +223,36 @@ export function getHttpRpcClient(
207
223
  }
208
224
  }
209
225
 
226
+ /**
227
+ * Returns a signal that aborts as soon as any of `signals` aborts, and a
228
+ * `cleanup` function that detaches the listeners added to `signals`.
229
+ */
230
+ function anySignal(signals: readonly (AbortSignal | null | undefined)[]) {
231
+ const signals_ = signals.filter((signal) => !!signal)
232
+ if (signals_.length < 2)
233
+ return { signal: signals_[0] ?? null, cleanup: () => {} }
234
+
235
+ const controller = new AbortController()
236
+ const onAbort = () => {
237
+ cleanup()
238
+ controller.abort(signals_.find((signal) => signal.aborted)?.reason)
239
+ }
240
+ const cleanup = () => {
241
+ for (const signal of signals_) signal.removeEventListener('abort', onAbort)
242
+ }
243
+
244
+ for (const signal of signals_) {
245
+ if (signal.aborted) {
246
+ cleanup()
247
+ controller.abort(signal.reason)
248
+ break
249
+ }
250
+ signal.addEventListener('abort', onAbort)
251
+ }
252
+
253
+ return { signal: controller.signal, cleanup }
254
+ }
255
+
210
256
  async function readResponseBody(
211
257
  response: Response,
212
258
  { maxResponseBodySize }: { maxResponseBodySize: number | false },
@@ -260,8 +260,10 @@ export async function getSocketRpcClient<socket extends {}>(
260
260
  body.method === 'eth_subscribe' &&
261
261
  typeof response.result === 'string'
262
262
  )
263
+ // Store `onResponse` rather than `callback` (which filters by this request's
264
+ // id) so the re-subscribe on reconnect delivers the new id to the subscriber.
263
265
  subscriptions.set(response.result, {
264
- onResponse: callback,
266
+ onResponse,
265
267
  onError,
266
268
  body,
267
269
  })
@@ -1 +0,0 @@
1
- {"version":3,"file":"multisig.js","sourceRoot":"","sources":["../../../tempo/actions/multisig.ts"],"names":[],"mappings":"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"}