viem 3.0.0-next.13 → 3.0.0-next.14

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 (194) hide show
  1. package/dist/chains/definitions/arcTestnet.d.ts +5 -5
  2. package/dist/chains/definitions/arcTestnet.d.ts.map +1 -1
  3. package/dist/chains/definitions/arcTestnet.js +11 -8
  4. package/dist/chains/definitions/arcTestnet.js.map +1 -1
  5. package/dist/chains/definitions/qmsTestnet.d.ts +27 -0
  6. package/dist/chains/definitions/qmsTestnet.d.ts.map +1 -0
  7. package/dist/chains/definitions/qmsTestnet.js +23 -0
  8. package/dist/chains/definitions/qmsTestnet.js.map +1 -0
  9. package/dist/chains/definitions/tempo.d.ts +1 -1
  10. package/dist/chains/definitions/tempo.js +1 -1
  11. package/dist/chains/definitions/tempoModerato.d.ts +2 -2
  12. package/dist/chains/definitions/tempoModerato.js +2 -2
  13. package/dist/chains/index.d.ts +1 -0
  14. package/dist/chains/index.d.ts.map +1 -1
  15. package/dist/chains/index.js +1 -0
  16. package/dist/chains/index.js.map +1 -1
  17. package/dist/core/actions/transaction/waitForReceipt.d.ts +1 -1
  18. package/dist/core/actions/transaction/waitForReceipt.d.ts.map +1 -1
  19. package/dist/core/actions/transaction/waitForReceipt.js +5 -2
  20. package/dist/core/actions/transaction/waitForReceipt.js.map +1 -1
  21. package/dist/core/actions/wallet/waitForCallsStatus.d.ts.map +1 -1
  22. package/dist/core/actions/wallet/waitForCallsStatus.js +24 -1
  23. package/dist/core/actions/wallet/waitForCallsStatus.js.map +1 -1
  24. package/dist/internal/inference.d.ts +3 -3
  25. package/dist/internal/inference.d.ts.map +1 -1
  26. package/dist/tempo/Abis.d.ts +1935 -899
  27. package/dist/tempo/Abis.d.ts.map +1 -1
  28. package/dist/tempo/Abis.js +215 -0
  29. package/dist/tempo/Abis.js.map +1 -1
  30. package/dist/tempo/Account.d.ts +30 -30
  31. package/dist/tempo/Account.d.ts.map +1 -1
  32. package/dist/tempo/Account.js +43 -43
  33. package/dist/tempo/Account.js.map +1 -1
  34. package/dist/tempo/Addresses.d.ts +2 -0
  35. package/dist/tempo/Addresses.d.ts.map +1 -1
  36. package/dist/tempo/Addresses.js +2 -0
  37. package/dist/tempo/Addresses.js.map +1 -1
  38. package/dist/tempo/Decorator.d.ts +33 -10
  39. package/dist/tempo/Decorator.d.ts.map +1 -1
  40. package/dist/tempo/Decorator.js +9 -8
  41. package/dist/tempo/Decorator.js.map +1 -1
  42. package/dist/tempo/ExecutionError.js +1 -1
  43. package/dist/tempo/ExecutionError.js.map +1 -1
  44. package/dist/tempo/KeyAuthorizationManager.d.ts +24 -13
  45. package/dist/tempo/KeyAuthorizationManager.d.ts.map +1 -1
  46. package/dist/tempo/KeyAuthorizationManager.js +44 -25
  47. package/dist/tempo/KeyAuthorizationManager.js.map +1 -1
  48. package/dist/tempo/Relay.d.ts +61 -12
  49. package/dist/tempo/Relay.d.ts.map +1 -1
  50. package/dist/tempo/Relay.js +48 -8
  51. package/dist/tempo/Relay.js.map +1 -1
  52. package/dist/tempo/Selectors.d.ts +12 -0
  53. package/dist/tempo/Selectors.d.ts.map +1 -1
  54. package/dist/tempo/Selectors.js +12 -0
  55. package/dist/tempo/Selectors.js.map +1 -1
  56. package/dist/tempo/Transport.d.ts +19 -6
  57. package/dist/tempo/Transport.d.ts.map +1 -1
  58. package/dist/tempo/Transport.js +18 -12
  59. package/dist/tempo/Transport.js.map +1 -1
  60. package/dist/tempo/{multisig → accounts}/Config.d.ts +8 -8
  61. package/dist/tempo/accounts/Config.d.ts.map +1 -0
  62. package/dist/tempo/{multisig → accounts}/Config.js +14 -14
  63. package/dist/tempo/{multisig → accounts}/Config.js.map +1 -1
  64. package/dist/tempo/{multisig → accounts}/Operation.d.ts +8 -8
  65. package/dist/tempo/{multisig → accounts}/Operation.d.ts.map +1 -1
  66. package/dist/tempo/{multisig → accounts}/Operation.js +17 -17
  67. package/dist/tempo/{multisig → accounts}/Operation.js.map +1 -1
  68. package/dist/tempo/{multisig → accounts}/Signature.d.ts +1 -1
  69. package/dist/tempo/accounts/Signature.d.ts.map +1 -0
  70. package/dist/tempo/accounts/Signature.js +9 -0
  71. package/dist/tempo/accounts/Signature.js.map +1 -0
  72. package/dist/tempo/actions/accessKey/index.d.ts +1 -0
  73. package/dist/tempo/actions/accessKey/index.d.ts.map +1 -1
  74. package/dist/tempo/actions/accessKey/index.js +1 -0
  75. package/dist/tempo/actions/accessKey/index.js.map +1 -1
  76. package/dist/tempo/actions/accessKey/prepareAuthorization.d.ts +4 -4
  77. package/dist/tempo/actions/accessKey/prepareAuthorization.d.ts.map +1 -1
  78. package/dist/tempo/actions/accessKey/prepareAuthorization.js +10 -10
  79. package/dist/tempo/actions/accessKey/prepareAuthorization.js.map +1 -1
  80. package/dist/tempo/actions/accessKey/saveAuthorization.d.ts +40 -0
  81. package/dist/tempo/actions/accessKey/saveAuthorization.d.ts.map +1 -0
  82. package/dist/tempo/actions/accessKey/saveAuthorization.js +32 -0
  83. package/dist/tempo/actions/accessKey/saveAuthorization.js.map +1 -0
  84. package/dist/tempo/actions/accessKey/signAuthorization.d.ts +17 -13
  85. package/dist/tempo/actions/accessKey/signAuthorization.d.ts.map +1 -1
  86. package/dist/tempo/actions/accessKey/signAuthorization.js +43 -30
  87. package/dist/tempo/actions/accessKey/signAuthorization.js.map +1 -1
  88. package/dist/tempo/actions/{multisig → accounts}/getConfig.d.ts +5 -5
  89. package/dist/tempo/actions/accounts/getConfig.d.ts.map +1 -0
  90. package/dist/tempo/actions/{multisig → accounts}/getConfig.js +5 -5
  91. package/dist/tempo/actions/accounts/getConfig.js.map +1 -0
  92. package/dist/tempo/actions/{multisig → accounts}/getConfigCommitment.d.ts +3 -3
  93. package/dist/tempo/actions/{multisig → accounts}/getConfigCommitment.d.ts.map +1 -1
  94. package/dist/tempo/actions/{multisig → accounts}/getConfigCommitment.js +2 -2
  95. package/dist/tempo/actions/{multisig → accounts}/getConfigCommitment.js.map +1 -1
  96. package/dist/tempo/actions/{multisig → accounts}/getOperation.d.ts +4 -4
  97. package/dist/tempo/actions/{multisig → accounts}/getOperation.d.ts.map +1 -1
  98. package/dist/tempo/actions/{multisig → accounts}/getOperation.js +4 -4
  99. package/dist/tempo/actions/accounts/getOperation.js.map +1 -0
  100. package/dist/tempo/actions/{multisig → accounts}/index.d.ts.map +1 -1
  101. package/dist/tempo/actions/{multisig → accounts}/index.js.map +1 -1
  102. package/dist/tempo/actions/{multisig → accounts}/updateConfig.d.ts +12 -12
  103. package/dist/tempo/actions/{multisig → accounts}/updateConfig.d.ts.map +1 -1
  104. package/dist/tempo/actions/{multisig → accounts}/updateConfig.js +16 -21
  105. package/dist/tempo/actions/{multisig → accounts}/updateConfig.js.map +1 -1
  106. package/dist/tempo/actions/{multisig → accounts}/updateConfigSync.d.ts +6 -6
  107. package/dist/tempo/actions/{multisig → accounts}/updateConfigSync.d.ts.map +1 -1
  108. package/dist/tempo/actions/{multisig → accounts}/updateConfigSync.js +6 -6
  109. package/dist/tempo/actions/{multisig → accounts}/updateConfigSync.js.map +1 -1
  110. package/dist/tempo/actions/index.d.ts +2 -2
  111. package/dist/tempo/actions/index.d.ts.map +1 -1
  112. package/dist/tempo/actions/index.js +2 -2
  113. package/dist/tempo/actions/index.js.map +1 -1
  114. package/dist/tempo/chainConfig.d.ts +16 -16
  115. package/dist/tempo/chainConfig.d.ts.map +1 -1
  116. package/dist/tempo/chainConfig.js +52 -52
  117. package/dist/tempo/chainConfig.js.map +1 -1
  118. package/dist/tempo/index.d.ts +2 -2
  119. package/dist/tempo/index.d.ts.map +1 -1
  120. package/dist/tempo/index.js +2 -2
  121. package/dist/tempo/index.js.map +1 -1
  122. package/dist/tempo/internal/relay/accounts.d.ts +15 -0
  123. package/dist/tempo/internal/relay/accounts.d.ts.map +1 -0
  124. package/dist/tempo/internal/relay/{multisig.js → accounts.js} +96 -101
  125. package/dist/tempo/internal/relay/accounts.js.map +1 -0
  126. package/dist/tempo/internal/relay/feePayer.js +5 -5
  127. package/dist/tempo/internal/relay/feePayer.js.map +1 -1
  128. package/dist/tempo/internal/relay/keyAuthorization.d.ts +25 -0
  129. package/dist/tempo/internal/relay/keyAuthorization.d.ts.map +1 -0
  130. package/dist/tempo/internal/relay/keyAuthorization.js +339 -0
  131. package/dist/tempo/internal/relay/keyAuthorization.js.map +1 -0
  132. package/dist/tempo/internal/relay/plugin.d.ts +6 -3
  133. package/dist/tempo/internal/relay/plugin.d.ts.map +1 -1
  134. package/dist/tempo/internal/relay/plugin.js +15 -6
  135. package/dist/tempo/internal/relay/plugin.js.map +1 -1
  136. package/dist/tempo/internal/relay/utils.d.ts +8 -0
  137. package/dist/tempo/internal/relay/utils.d.ts.map +1 -1
  138. package/dist/utils/RpcClient.d.ts.map +1 -1
  139. package/dist/utils/RpcClient.js +36 -19
  140. package/dist/utils/RpcClient.js.map +1 -1
  141. package/dist/version.d.ts +1 -1
  142. package/dist/version.js +1 -1
  143. package/package.json +2 -2
  144. package/src/chains/definitions/arcTestnet.ts +11 -8
  145. package/src/chains/definitions/qmsTestnet.ts +23 -0
  146. package/src/chains/definitions/tempo.ts +1 -1
  147. package/src/chains/definitions/tempoModerato.ts +2 -2
  148. package/src/chains/index.ts +1 -0
  149. package/src/core/actions/transaction/waitForReceipt.ts +5 -3
  150. package/src/core/actions/wallet/waitForCallsStatus.ts +25 -1
  151. package/src/internal/inference.ts +3 -3
  152. package/src/tempo/Abis.ts +216 -0
  153. package/src/tempo/Account.ts +97 -87
  154. package/src/tempo/Addresses.ts +2 -0
  155. package/src/tempo/Decorator.ts +55 -28
  156. package/src/tempo/ExecutionError.ts +1 -1
  157. package/src/tempo/KeyAuthorizationManager.ts +53 -31
  158. package/src/tempo/Relay.ts +69 -14
  159. package/src/tempo/Selectors.ts +13 -0
  160. package/src/tempo/Transport.ts +46 -20
  161. package/src/tempo/{multisig → accounts}/Config.ts +21 -21
  162. package/src/tempo/{multisig → accounts}/Operation.ts +26 -26
  163. package/src/tempo/{multisig → accounts}/Signature.ts +5 -3
  164. package/src/tempo/actions/accessKey/index.ts +1 -0
  165. package/src/tempo/actions/accessKey/prepareAuthorization.ts +16 -15
  166. package/src/tempo/actions/accessKey/saveAuthorization.ts +51 -0
  167. package/src/tempo/actions/accessKey/signAuthorization.ts +73 -51
  168. package/src/tempo/actions/{multisig → accounts}/getConfig.ts +7 -7
  169. package/src/tempo/actions/{multisig → accounts}/getConfigCommitment.ts +3 -3
  170. package/src/tempo/actions/{multisig → accounts}/getOperation.ts +6 -6
  171. package/src/tempo/actions/{multisig → accounts}/updateConfig.ts +21 -26
  172. package/src/tempo/actions/{multisig → accounts}/updateConfigSync.ts +7 -7
  173. package/src/tempo/actions/index.ts +2 -3
  174. package/src/tempo/chainConfig.ts +87 -85
  175. package/src/tempo/index.ts +4 -4
  176. package/src/tempo/internal/relay/{multisig.ts → accounts.ts} +121 -121
  177. package/src/tempo/internal/relay/feePayer.ts +5 -5
  178. package/src/tempo/internal/relay/keyAuthorization.ts +419 -0
  179. package/src/tempo/internal/relay/plugin.ts +20 -7
  180. package/src/utils/RpcClient.ts +38 -21
  181. package/src/version.ts +1 -1
  182. package/dist/tempo/actions/multisig/getConfig.d.ts.map +0 -1
  183. package/dist/tempo/actions/multisig/getConfig.js.map +0 -1
  184. package/dist/tempo/actions/multisig/getOperation.js.map +0 -1
  185. package/dist/tempo/internal/relay/multisig.d.ts +0 -4
  186. package/dist/tempo/internal/relay/multisig.d.ts.map +0 -1
  187. package/dist/tempo/internal/relay/multisig.js.map +0 -1
  188. package/dist/tempo/multisig/Config.d.ts.map +0 -1
  189. package/dist/tempo/multisig/Signature.d.ts.map +0 -1
  190. package/dist/tempo/multisig/Signature.js +0 -9
  191. package/dist/tempo/multisig/Signature.js.map +0 -1
  192. /package/dist/tempo/actions/{multisig → accounts}/index.d.ts +0 -0
  193. /package/dist/tempo/actions/{multisig → accounts}/index.js +0 -0
  194. /package/src/tempo/actions/{multisig → accounts}/index.ts +0 -0
@@ -150,8 +150,8 @@ export function create(options: Relay.feePayer.Options): Relay.Plugin {
150
150
  if (!sponsored) return Request.fill(client, { ...base, feePayer: false })
151
151
 
152
152
  const defer =
153
- typeof normalized.multisigSimulation === 'object' &&
154
- normalized.multisigSimulation !== null
153
+ typeof normalized.accountSimulation === 'object' &&
154
+ normalized.accountSimulation !== null
155
155
 
156
156
  const sponsor = sponsored
157
157
  ? external
@@ -197,8 +197,8 @@ export function create(options: Relay.feePayer.Options): Relay.Plugin {
197
197
  typeof parameters.feePayer === 'string' ||
198
198
  parameters.feePayer === false ||
199
199
  !result.capabilities?.sponsored ||
200
- (typeof parameters.multisigSimulation === 'object' &&
201
- parameters.multisigSimulation !== null)
200
+ (typeof parameters.accountSimulation === 'object' &&
201
+ parameters.accountSimulation !== null)
202
202
  )
203
203
  return undefined
204
204
  const transaction = Utils.normalizeTempoTransaction(result.tx)
@@ -420,7 +420,7 @@ async function handleRawTransaction(options: handleRawTransaction.Options) {
420
420
  })
421
421
 
422
422
  const transaction = Transaction.deserialize(serialized)
423
- // Prefer sender recovered from raw envelope; multisig finalize path supplies fallback sender.
423
+ // Prefer sender recovered from raw envelope; configurable account finalize path supplies fallback sender.
424
424
  const sender = transaction.from ?? options.sender
425
425
 
426
426
  // Sponsorship only applies after sender has signed original transaction.
@@ -0,0 +1,419 @@
1
+ import { Address, type Hex, RpcResponse } from 'ox'
2
+ import {
3
+ AccountConfig,
4
+ AccountOperation,
5
+ KeyAuthorization,
6
+ SignatureEnvelope,
7
+ } from 'ox/tempo'
8
+ import { getNumber as getBlockNumber } from '../../../core/actions/block/getNumber.js'
9
+ import type { Client } from '../../../core/Client.js'
10
+ import { BaseError } from '../../../core/Errors.js'
11
+ import { getMetadata } from '../../actions/accessKey/getMetadata.js'
12
+ import { isWitnessBurned } from '../../actions/accessKey/isWitnessBurned.js'
13
+ import type * as Relay from '../../Relay.js'
14
+ import * as Store from '../../Store.js'
15
+ import { selectApprovals, validateConfig } from './accounts.js'
16
+ import * as Plugin from './plugin.js'
17
+
18
+ /** Maximum compare-and-set attempts before a write is abandoned. */
19
+ const maxWriteAttempts = 32
20
+
21
+ /** Maximum time a pending key authorization is kept. */
22
+ const maxTtl = 30 * 24 * 60 * 60 * 1_000
23
+
24
+ /** Creates middleware that stores pending key authorizations and attaches them to fills. @internal */
25
+ export function create(
26
+ options: Relay.keyAuthorization.Options = {},
27
+ ): Relay.keyAuthorization.ReturnType {
28
+ const { store = Store.memory() } = options
29
+ if (!store.compareAndSet)
30
+ throw new RpcResponse.InvalidParamsError({
31
+ message:
32
+ 'Key authorization storage requires a store with atomic `compareAndSet`.',
33
+ })
34
+ return Plugin.keyAuthorization({
35
+ async handleRequest(context, next) {
36
+ const { request } = context
37
+
38
+ if (request.method === 'relay_saveKeyAuthorization') {
39
+ const keyAuthorization = parse(request.params?.[0])
40
+ const chainId = Number(keyAuthorization.chainId)
41
+ if (
42
+ context.options.chainId !== undefined &&
43
+ context.options.chainId !== chainId
44
+ )
45
+ throw new RpcResponse.InvalidParamsError({
46
+ message: 'Conflicting chain ids.',
47
+ })
48
+ const client = context.getClient(chainId)
49
+ const account = await check(client, keyAuthorization)
50
+ if (!account) return null
51
+ // Drop an unusable pending authorization so it cannot block this one.
52
+ await read(client, store, {
53
+ account,
54
+ accessKey: keyAuthorization.address,
55
+ chainId,
56
+ })
57
+ if (!(await write(store, { account, keyAuthorization })))
58
+ throw new RpcResponse.InvalidParamsError({
59
+ message:
60
+ 'A pending key authorization for this access key expires later.',
61
+ })
62
+ return null
63
+ }
64
+
65
+ if (request.method === 'account_approveKeyAuthorization') {
66
+ await next()
67
+ const operation = AccountOperation.fromRpc(
68
+ context.result as AccountOperation.Rpc,
69
+ )
70
+ if (
71
+ operation.type !== 'keyAuthorization' ||
72
+ operation.status !== 'success'
73
+ )
74
+ return
75
+ const keyAuthorization = deserialize(operation.keyAuthorization)
76
+ if (!keyAuthorization) return
77
+ // Quorum may land after expiry, and a stored success may predate a config
78
+ // rotation. The approval succeeded, so failed checks skip storage instead
79
+ // of failing it.
80
+ const chainId = Number(keyAuthorization.chainId)
81
+ const client = context.getClient(chainId)
82
+ const account = await check(client, keyAuthorization).catch((error) => {
83
+ if (error instanceof RpcResponse.InvalidParamsError) return undefined
84
+ throw error
85
+ })
86
+ if (!account) return
87
+ await read(client, store, {
88
+ account,
89
+ accessKey: keyAuthorization.address,
90
+ chainId,
91
+ })
92
+ await write(store, { account, keyAuthorization })
93
+ return
94
+ }
95
+
96
+ if (request.method !== 'eth_fillTransaction') return next()
97
+
98
+ const { chainId } = context.options
99
+ const parameters = request.params?.[0] as Record<string, unknown>
100
+ const { from: account, keyId: accessKey } = parameters
101
+ if (
102
+ chainId === undefined ||
103
+ parameters.keyAuthorization ||
104
+ typeof account !== 'string' ||
105
+ typeof accessKey !== 'string' ||
106
+ !Address.validate(account, { strict: false }) ||
107
+ !Address.validate(accessKey, { strict: false })
108
+ )
109
+ return next()
110
+
111
+ const pending = await read(context.client, store, {
112
+ account,
113
+ accessKey,
114
+ chainId,
115
+ })
116
+ if (!pending) return next()
117
+ const metadata = await getMetadata(context.client, {
118
+ account,
119
+ accessKey,
120
+ })
121
+ // The key already landed onchain, so the authorization is spent.
122
+ if (isActive(metadata, accessKey)) {
123
+ await remove(store, pending)
124
+ return next()
125
+ }
126
+
127
+ // Attach before forwarding so gas estimation and later plugins see the authorization.
128
+ context.request = {
129
+ ...request,
130
+ params: [
131
+ {
132
+ ...parameters,
133
+ keyAuthorization: KeyAuthorization.toRpc(pending.keyAuthorization),
134
+ },
135
+ ],
136
+ }
137
+ return next()
138
+ },
139
+ })
140
+ }
141
+
142
+ /**
143
+ * Removes a stored authorization only while it still holds the value that was read,
144
+ * so a concurrently saved replacement survives. The empty value expires immediately.
145
+ * @internal
146
+ */
147
+ export async function remove(
148
+ store: Store.Atomic,
149
+ options: { key: string; value: string },
150
+ ) {
151
+ const { key, value } = options
152
+ await store.compareAndSet(key, value, '', { expiresAt: Date.now() })
153
+ }
154
+
155
+ /**
156
+ * Stores an authorization unless a pending authorization for the same key expires
157
+ * later, so a replayed older authorization cannot replace a newer one.
158
+ * @internal
159
+ */
160
+ export async function write(
161
+ store: Store.Atomic,
162
+ options: {
163
+ account: Address.Address
164
+ keyAuthorization: KeyAuthorization.Signed
165
+ },
166
+ ): Promise<boolean> {
167
+ const { account, keyAuthorization } = options
168
+ const key = getKey({
169
+ account,
170
+ accessKey: keyAuthorization.address,
171
+ chainId: Number(keyAuthorization.chainId),
172
+ })
173
+ const value = KeyAuthorization.serialize(keyAuthorization)
174
+ const expiresAt = Math.min(
175
+ Date.now() + maxTtl,
176
+ getExpiry(keyAuthorization) * 1_000,
177
+ )
178
+ for (let attempt = 0; attempt < maxWriteAttempts; attempt++) {
179
+ const current = (await store.getItem(key)) ?? null
180
+ const existing = current ? deserialize(current) : undefined
181
+ if (existing && current?.toLowerCase() === value.toLowerCase()) return true
182
+ if (existing && getExpiry(existing) >= getExpiry(keyAuthorization))
183
+ return false
184
+ // A lost compare-and-set means another writer replaced the value, so compare again.
185
+ if (await store.compareAndSet(key, current, value, { expiresAt }))
186
+ return true
187
+ }
188
+ throw new StoreConflictError()
189
+ }
190
+
191
+ /** Parses a signed key authorization from RPC parameters. */
192
+ function parse(value: unknown): KeyAuthorization.Signed {
193
+ const keyAuthorization = (() => {
194
+ try {
195
+ if (typeof value === 'string') return deserialize(value)
196
+ return KeyAuthorization.fromRpc(value as KeyAuthorization.Rpc)
197
+ } catch {
198
+ return undefined
199
+ }
200
+ })()
201
+ if (!keyAuthorization?.signature)
202
+ throw new RpcResponse.InvalidParamsError({
203
+ message: 'Expected a signed key authorization.',
204
+ })
205
+ return keyAuthorization
206
+ }
207
+
208
+ /** Deserializes a signed key authorization, or returns `undefined` when malformed or unsigned. */
209
+ function deserialize(value: string): KeyAuthorization.Signed | undefined {
210
+ try {
211
+ const { signature, ...rest } = KeyAuthorization.deserialize(
212
+ value as Hex.Hex,
213
+ )
214
+ if (!signature) return undefined
215
+ return { ...rest, signature }
216
+ } catch {
217
+ return undefined
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Validates an authorization and returns the account it authorizes, or `undefined`
223
+ * when the access key is already active onchain.
224
+ */
225
+ async function check(
226
+ client: Client,
227
+ keyAuthorization: KeyAuthorization.Signed,
228
+ ): Promise<Address.Address | undefined> {
229
+ if (isExpired(keyAuthorization))
230
+ throw new RpcResponse.InvalidParamsError({
231
+ message: 'Key authorization has expired.',
232
+ })
233
+ const account = await verify(client, keyAuthorization)
234
+ if (await isBurned(client, { account, keyAuthorization }))
235
+ throw new RpcResponse.InvalidParamsError({
236
+ message: 'Key authorization witness has been burned.',
237
+ })
238
+ // Authorizations for keys that are already active would only be removed on the next fill.
239
+ const metadata = await getMetadata(client, {
240
+ account,
241
+ accessKey: keyAuthorization.address,
242
+ })
243
+ if (isActive(metadata, keyAuthorization.address)) return undefined
244
+ return account
245
+ }
246
+
247
+ /** Verifies the authorization signature and returns the account it authorizes. */
248
+ async function verify(
249
+ client: Client,
250
+ keyAuthorization: KeyAuthorization.Signed,
251
+ ): Promise<Address.Address> {
252
+ const { signature, ...unsigned } = keyAuthorization
253
+ const payload = KeyAuthorization.getSignPayload(unsigned)
254
+
255
+ if (signature.type === 'configurable') {
256
+ if (
257
+ !keyAuthorization.account ||
258
+ !Address.isEqual(keyAuthorization.account, signature.account)
259
+ )
260
+ throw new RpcResponse.InvalidParamsError({
261
+ message:
262
+ 'Configurable account key authorization account does not match its signature.',
263
+ })
264
+ const config = AccountConfig.from(signature.config)
265
+ await validateConfig({
266
+ account: signature.account,
267
+ blockNumber: await getBlockNumber(client, { cacheTime: 0 }),
268
+ client,
269
+ config,
270
+ })
271
+ const approvals = await selectApprovals({
272
+ account: signature.account,
273
+ approvals: signature.signatures.map((signature) =>
274
+ SignatureEnvelope.serialize(signature),
275
+ ),
276
+ config,
277
+ hash: AccountConfig.getSignPayload({
278
+ account: signature.account,
279
+ config,
280
+ payload,
281
+ }),
282
+ })
283
+ if (approvals.weight < approvals.threshold)
284
+ throw new RpcResponse.InvalidParamsError({
285
+ message:
286
+ 'Configurable account key authorization has not reached quorum.',
287
+ })
288
+ return signature.account
289
+ }
290
+
291
+ const signer = (() => {
292
+ try {
293
+ const signer = SignatureEnvelope.extractAddress({ payload, signature })
294
+ if (SignatureEnvelope.verify(signature, { address: signer, payload }))
295
+ return signer
296
+ } catch {}
297
+ throw new RpcResponse.InvalidParamsError({
298
+ message: 'Invalid key authorization signature.',
299
+ })
300
+ })()
301
+
302
+ // Admin-signed authorizations can only be submitted by the admin key itself,
303
+ // so only authorizations signed by the account can be attached for the new key.
304
+ if (
305
+ keyAuthorization.account &&
306
+ !Address.isEqual(keyAuthorization.account, signer)
307
+ )
308
+ throw new RpcResponse.InvalidParamsError({
309
+ message: 'Key authorization must be signed by its account.',
310
+ })
311
+ return signer
312
+ }
313
+
314
+ /**
315
+ * Reads the pending authorization for an access key. Entries that are malformed,
316
+ * expired, no longer verify, or whose witness has been burned are removed.
317
+ */
318
+ async function read(
319
+ client: Client,
320
+ store: Store.Atomic,
321
+ options: {
322
+ account: Address.Address
323
+ accessKey: Address.Address
324
+ chainId: number
325
+ },
326
+ ) {
327
+ const { account } = options
328
+ const key = getKey(options)
329
+ const value = await store.getItem(key)
330
+ if (!value) return undefined
331
+ const keyAuthorization = deserialize(value)
332
+ if (
333
+ keyAuthorization &&
334
+ !isExpired(keyAuthorization) &&
335
+ (await isVerified(client, keyAuthorization)) &&
336
+ !(await isBurned(client, { account, keyAuthorization }))
337
+ )
338
+ return { key, keyAuthorization, value }
339
+ await remove(store, { key, value })
340
+ return undefined
341
+ }
342
+
343
+ /** Returns whether the authorization still verifies. Operational failures propagate. */
344
+ async function isVerified(
345
+ client: Client,
346
+ keyAuthorization: KeyAuthorization.Signed,
347
+ ) {
348
+ try {
349
+ await verify(client, keyAuthorization)
350
+ return true
351
+ } catch (error) {
352
+ if (error instanceof RpcResponse.InvalidParamsError) return false
353
+ throw error
354
+ }
355
+ }
356
+
357
+ /** Returns whether the authorization's witness has been burned onchain. */
358
+ async function isBurned(
359
+ client: Client,
360
+ options: {
361
+ account: Address.Address
362
+ keyAuthorization: KeyAuthorization.Signed
363
+ },
364
+ ) {
365
+ const { account, keyAuthorization } = options
366
+ if (!keyAuthorization.witness) return false
367
+ return isWitnessBurned(client, {
368
+ account,
369
+ witness: keyAuthorization.witness,
370
+ })
371
+ }
372
+
373
+ /** Returns the authorization expiry in seconds, treating no expiry as unbounded. */
374
+ function getExpiry(keyAuthorization: KeyAuthorization.KeyAuthorization) {
375
+ const { expiry } = keyAuthorization
376
+ return expiry === undefined || expiry === null
377
+ ? Number.POSITIVE_INFINITY
378
+ : Number(expiry)
379
+ }
380
+
381
+ /** Returns whether the access key is active onchain. */
382
+ function isActive(
383
+ metadata: getMetadata.ReturnType,
384
+ accessKey: Address.Address,
385
+ ) {
386
+ return (
387
+ Address.isEqual(metadata.address, accessKey) &&
388
+ !metadata.isRevoked &&
389
+ metadata.expiry > BigInt(Math.floor(Date.now() / 1000))
390
+ )
391
+ }
392
+
393
+ function isExpired(keyAuthorization: KeyAuthorization.KeyAuthorization) {
394
+ return getExpiry(keyAuthorization) <= Math.floor(Date.now() / 1000)
395
+ }
396
+
397
+ function getKey(options: {
398
+ account: Address.Address
399
+ accessKey: Address.Address
400
+ chainId: number
401
+ }) {
402
+ const { account, accessKey, chainId } = options
403
+ return [
404
+ 'keyAuthorization',
405
+ chainId,
406
+ account.toLowerCase(),
407
+ accessKey.toLowerCase(),
408
+ ].join(':')
409
+ }
410
+
411
+ /** Thrown when a pending key authorization cannot be written after repeated conflicts. */
412
+ class StoreConflictError extends BaseError {
413
+ override name = 'Relay.KeyAuthorization.StoreConflictError'
414
+ constructor() {
415
+ super(
416
+ 'Pending key authorization could not be written after repeated conflicts.',
417
+ )
418
+ }
419
+ }
@@ -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
  }
@@ -140,8 +140,9 @@ export function http<schema extends RpcSchema.Schema = RpcSchema.Default>(
140
140
  const { headers, method, signal: signal_ } = fetchOptions
141
141
 
142
142
  try {
143
- const response = await promise.withTimeout(
143
+ const { response, responseBody } = await promise.withTimeout(
144
144
  async ({ signal }) => {
145
+ const timeoutSignal = timeout > 0 ? signal : null
145
146
  const init: RequestInit = {
146
147
  ...fetchOptions,
147
148
  body: stringify(body),
@@ -151,14 +152,24 @@ export function http<schema extends RpcSchema.Schema = RpcSchema.Default>(
151
152
  ...headers,
152
153
  },
153
154
  method: method || 'POST',
154
- signal: signal_ || (timeout > 0 ? signal : null),
155
+ // A caller `signal` must not disable the timeout.
156
+ signal:
157
+ signal_ && timeoutSignal
158
+ ? AbortSignal.any([signal_, timeoutSignal])
159
+ : (signal_ ?? timeoutSignal),
155
160
  }
156
161
  const request = new Request(url, init)
157
162
  const overrides = (await onRequest?.(request, init)) ?? {
158
163
  ...init,
159
164
  url,
160
165
  }
161
- return fetchFn(overrides.url ?? url, overrides)
166
+ const response = await fetchFn(overrides.url ?? url, overrides)
167
+ if (onResponse) await onResponse(response)
168
+ // Read the body within the timeout so a stalled body cannot hang.
169
+ const responseBody = await readResponseBody(response, {
170
+ maxResponseBodySize,
171
+ })
172
+ return { response, responseBody }
162
173
  },
163
174
  {
164
175
  errorInstance: new TimeoutError({ body: errorBody, url }),
@@ -167,12 +178,7 @@ export function http<schema extends RpcSchema.Schema = RpcSchema.Default>(
167
178
  },
168
179
  )
169
180
 
170
- if (onResponse) await onResponse(response)
171
-
172
181
  let data: any
173
- const responseBody = await readResponseBody(response, {
174
- maxResponseBodySize,
175
- })
176
182
  if (
177
183
  response.headers.get('Content-Type')?.startsWith('application/json')
178
184
  )
@@ -556,6 +562,19 @@ export async function fromSocket<
556
562
  else errorQueue.push(error)
557
563
  }
558
564
 
565
+ // Reassigned when a reconnect re-subscribes with a new id.
566
+ let subscriptionId: Hex.Hex | undefined
567
+ let unsubscribed = false
568
+ function unsubscribe() {
569
+ unsubscribed = true
570
+ return new Promise<Socket.Response>((resolve) =>
571
+ requestInternal({
572
+ body: { method: 'eth_unsubscribe', params: [subscriptionId] },
573
+ onResponse: resolve,
574
+ }),
575
+ ) as Promise<RpcResponse.RpcResponse<boolean>>
576
+ }
577
+
559
578
  return new Promise<Socket.subscribe.ReturnType>((resolve, reject) => {
560
579
  requestInternal({
561
580
  body: { method: 'eth_subscribe', params },
@@ -571,9 +590,17 @@ export async function fromSocket<
571
590
  return
572
591
  }
573
592
  if (typeof response.id === 'number') {
574
- const subscriptionId = response.result as Hex.Hex
593
+ const resubscribed = subscriptionId !== undefined
594
+ subscriptionId = response.result as Hex.Hex
595
+ if (resubscribed) {
596
+ // Re-subscribed while an unsubscribe was in flight.
597
+ if (unsubscribed) unsubscribe()
598
+ return
599
+ }
575
600
  resolve({
576
- subscriptionId,
601
+ get subscriptionId() {
602
+ return subscriptionId!
603
+ },
577
604
  onData(listener) {
578
605
  onDataFn = listener
579
606
  while (dataQueue.length) listener(dataQueue.shift()!)
@@ -582,17 +609,7 @@ export async function fromSocket<
582
609
  onErrorFn = listener
583
610
  while (errorQueue.length) listener(errorQueue.shift()!)
584
611
  },
585
- unsubscribe() {
586
- return new Promise<Socket.Response>((resolve) =>
587
- requestInternal({
588
- body: {
589
- method: 'eth_unsubscribe',
590
- params: [subscriptionId],
591
- },
592
- onResponse: resolve,
593
- }),
594
- ) as Promise<RpcResponse.RpcResponse<boolean>>
595
- },
612
+ unsubscribe,
596
613
  })
597
614
  return
598
615
  }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = '3.0.0-next.13'
2
+ export const version = '3.0.0-next.14'
@@ -1 +0,0 @@
1
- {"version":3,"file":"getConfig.d.ts","sourceRoot":"","sources":["../../../../src/tempo/actions/multisig/getConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,KAAK,MAAM,MAAM,yBAAyB,CAAA;AAEtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EACrB,UAAU,EAAE,SAAS,CAAC,OAAO,GAC5B,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,CAM/B;AAED,MAAM,CAAC,OAAO,WAAW,SAAS,CAAC;IACjC,wCAAwC;IACxC,KAAY,OAAO,GAAG;QACpB,gCAAgC;QAChC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAA;KACzB,CAAA;IAED,0CAA0C;IAC1C,KAAY,UAAU,GAAG,cAAc,CAAC,MAAM,GAAG,IAAI,CAAA;IAErD,wCAAwC;IACxC,KAAY,SAAS,GAAG,MAAM,CAAC,eAAe,CAAA;CAC/C"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"getConfig.js","sourceRoot":"","sources":["../../../../src/tempo/actions/multisig/getConfig.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAGzC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAqB,EACrB,UAA6B;IAE7B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QAClC,MAAM,EAAE,oBAAoB;QAC5B,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC;KAC1C,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,MAA4B,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAC7E,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"getOperation.js","sourceRoot":"","sources":["../../../../src/tempo/actions/multisig/getOperation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAG5C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,UAAgC;IAEhC,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QACrC,MAAM,EAAE,uBAAuB;QAC/B,MAAM,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;KAC1B,CAAC,CAAA;IACF,OAAO,SAAS;QACd,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,SAAkC,CAAC;QAC/D,CAAC,CAAC,IAAI,CAAA;AACV,CAAC"}
@@ -1,4 +0,0 @@
1
- import type * as Relay from '../../Relay.js';
2
- /** Creates middleware for multisig approval coordination. @internal */
3
- export declare function create(options: Relay.multisig.Options): Relay.multisig.ReturnType;
4
- //# sourceMappingURL=multisig.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"multisig.d.ts","sourceRoot":"","sources":["../../../../src/tempo/internal/relay/multisig.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,KAAK,KAAK,MAAM,gBAAgB,CAAA;AAO5C,uEAAuE;AACvE,wBAAgB,MAAM,CACpB,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,OAAO,GAC9B,KAAK,CAAC,QAAQ,CAAC,UAAU,CAiL3B"}