viem 2.56.1 → 2.56.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/_cjs/actions/wallet/sendTransaction.js +9 -1
  3. package/_cjs/actions/wallet/sendTransaction.js.map +1 -1
  4. package/_cjs/actions/wallet/sendTransactionSync.js +9 -1
  5. package/_cjs/actions/wallet/sendTransactionSync.js.map +1 -1
  6. package/_cjs/errors/version.js +1 -1
  7. package/_cjs/tempo/Abis.js +11 -23
  8. package/_cjs/tempo/Abis.js.map +1 -1
  9. package/_cjs/tempo/Account.js +66 -61
  10. package/_cjs/tempo/Account.js.map +1 -1
  11. package/_cjs/tempo/Client.js +10 -2
  12. package/_cjs/tempo/Client.js.map +1 -1
  13. package/_cjs/tempo/Decorator.js +2 -1
  14. package/_cjs/tempo/Decorator.js.map +1 -1
  15. package/_cjs/tempo/Formatters.js +29 -9
  16. package/_cjs/tempo/Formatters.js.map +1 -1
  17. package/_cjs/tempo/Multisig.js +1093 -0
  18. package/_cjs/tempo/Multisig.js.map +1 -0
  19. package/_cjs/tempo/Selectors.js +2 -3
  20. package/_cjs/tempo/Selectors.js.map +1 -1
  21. package/_cjs/tempo/{Storage.js → Store.js} +34 -9
  22. package/_cjs/tempo/Store.js.map +1 -0
  23. package/_cjs/tempo/Transaction.js +15 -30
  24. package/_cjs/tempo/Transaction.js.map +1 -1
  25. package/_cjs/tempo/Transport.js +51 -5
  26. package/_cjs/tempo/Transport.js.map +1 -1
  27. package/_cjs/tempo/Zone.js.map +1 -1
  28. package/_cjs/tempo/actions/accessKey.js +102 -8
  29. package/_cjs/tempo/actions/accessKey.js.map +1 -1
  30. package/_cjs/tempo/actions/amm.js +6 -0
  31. package/_cjs/tempo/actions/amm.js.map +1 -1
  32. package/_cjs/tempo/actions/channel.js +12 -0
  33. package/_cjs/tempo/actions/channel.js.map +1 -1
  34. package/_cjs/tempo/actions/dex.js +10 -0
  35. package/_cjs/tempo/actions/dex.js.map +1 -1
  36. package/_cjs/tempo/actions/earn/deployment.js +13 -1
  37. package/_cjs/tempo/actions/earn/deployment.js.map +1 -1
  38. package/_cjs/tempo/actions/earn.js +17 -0
  39. package/_cjs/tempo/actions/earn.js.map +1 -1
  40. package/_cjs/tempo/actions/fee.js +4 -0
  41. package/_cjs/tempo/actions/fee.js.map +1 -1
  42. package/_cjs/tempo/actions/multisig.js +86 -28
  43. package/_cjs/tempo/actions/multisig.js.map +1 -1
  44. package/_cjs/tempo/actions/policy.js +8 -0
  45. package/_cjs/tempo/actions/policy.js.map +1 -1
  46. package/_cjs/tempo/actions/receivePolicy.js +6 -0
  47. package/_cjs/tempo/actions/receivePolicy.js.map +1 -1
  48. package/_cjs/tempo/actions/reward.js +4 -0
  49. package/_cjs/tempo/actions/reward.js.map +1 -1
  50. package/_cjs/tempo/actions/token.js +32 -0
  51. package/_cjs/tempo/actions/token.js.map +1 -1
  52. package/_cjs/tempo/actions/virtualAddress.js +2 -0
  53. package/_cjs/tempo/actions/virtualAddress.js.map +1 -1
  54. package/_cjs/tempo/actions/zone.js +7 -5
  55. package/_cjs/tempo/actions/zone.js.map +1 -1
  56. package/_cjs/tempo/chainConfig.js +180 -69
  57. package/_cjs/tempo/chainConfig.js.map +1 -1
  58. package/_cjs/tempo/index.js +5 -2
  59. package/_cjs/tempo/index.js.map +1 -1
  60. package/_cjs/tempo/internal/utils.js +2 -1
  61. package/_cjs/tempo/internal/utils.js.map +1 -1
  62. package/_cjs/tempo/multisig/Config.js +80 -0
  63. package/_cjs/tempo/multisig/Config.js.map +1 -0
  64. package/_cjs/tempo/multisig/Operation.js +163 -0
  65. package/_cjs/tempo/multisig/Operation.js.map +1 -0
  66. package/_cjs/utils/encoding/fromHex.js +1 -1
  67. package/_cjs/utils/encoding/fromHex.js.map +1 -1
  68. package/_cjs/utils/hash/isHash.js +1 -2
  69. package/_cjs/utils/hash/isHash.js.map +1 -1
  70. package/_esm/actions/wallet/sendTransaction.js +9 -1
  71. package/_esm/actions/wallet/sendTransaction.js.map +1 -1
  72. package/_esm/actions/wallet/sendTransactionSync.js +9 -1
  73. package/_esm/actions/wallet/sendTransactionSync.js.map +1 -1
  74. package/_esm/errors/version.js +1 -1
  75. package/_esm/tempo/Abis.js +11 -23
  76. package/_esm/tempo/Abis.js.map +1 -1
  77. package/_esm/tempo/Account.js +66 -61
  78. package/_esm/tempo/Account.js.map +1 -1
  79. package/_esm/tempo/Client.js +22 -3
  80. package/_esm/tempo/Client.js.map +1 -1
  81. package/_esm/tempo/Decorator.js +2 -1
  82. package/_esm/tempo/Decorator.js.map +1 -1
  83. package/_esm/tempo/Formatters.js +32 -11
  84. package/_esm/tempo/Formatters.js.map +1 -1
  85. package/_esm/tempo/Multisig.js +1126 -0
  86. package/_esm/tempo/Multisig.js.map +1 -0
  87. package/_esm/tempo/Selectors.js +2 -3
  88. package/_esm/tempo/Selectors.js.map +1 -1
  89. package/_esm/tempo/{Storage.js → Store.js} +37 -26
  90. package/_esm/tempo/Store.js.map +1 -0
  91. package/_esm/tempo/Transaction.js +16 -31
  92. package/_esm/tempo/Transaction.js.map +1 -1
  93. package/_esm/tempo/Transport.js +71 -6
  94. package/_esm/tempo/Transport.js.map +1 -1
  95. package/_esm/tempo/Zone.js +1 -2
  96. package/_esm/tempo/Zone.js.map +1 -1
  97. package/_esm/tempo/actions/accessKey.js +104 -20
  98. package/_esm/tempo/actions/accessKey.js.map +1 -1
  99. package/_esm/tempo/actions/amm.js +6 -0
  100. package/_esm/tempo/actions/amm.js.map +1 -1
  101. package/_esm/tempo/actions/channel.js +12 -0
  102. package/_esm/tempo/actions/channel.js.map +1 -1
  103. package/_esm/tempo/actions/dex.js +10 -0
  104. package/_esm/tempo/actions/dex.js.map +1 -1
  105. package/_esm/tempo/actions/earn/deployment.js +13 -1
  106. package/_esm/tempo/actions/earn/deployment.js.map +1 -1
  107. package/_esm/tempo/actions/earn.js +17 -0
  108. package/_esm/tempo/actions/earn.js.map +1 -1
  109. package/_esm/tempo/actions/fee.js +4 -0
  110. package/_esm/tempo/actions/fee.js.map +1 -1
  111. package/_esm/tempo/actions/multisig.js +129 -63
  112. package/_esm/tempo/actions/multisig.js.map +1 -1
  113. package/_esm/tempo/actions/policy.js +8 -0
  114. package/_esm/tempo/actions/policy.js.map +1 -1
  115. package/_esm/tempo/actions/receivePolicy.js +6 -0
  116. package/_esm/tempo/actions/receivePolicy.js.map +1 -1
  117. package/_esm/tempo/actions/reward.js +4 -0
  118. package/_esm/tempo/actions/reward.js.map +1 -1
  119. package/_esm/tempo/actions/token.js +32 -0
  120. package/_esm/tempo/actions/token.js.map +1 -1
  121. package/_esm/tempo/actions/virtualAddress.js +2 -0
  122. package/_esm/tempo/actions/virtualAddress.js.map +1 -1
  123. package/_esm/tempo/actions/zone.js +8 -6
  124. package/_esm/tempo/actions/zone.js.map +1 -1
  125. package/_esm/tempo/chainConfig.js +188 -86
  126. package/_esm/tempo/chainConfig.js.map +1 -1
  127. package/_esm/tempo/index.js +5 -3
  128. package/_esm/tempo/index.js.map +1 -1
  129. package/_esm/tempo/internal/utils.js +2 -1
  130. package/_esm/tempo/internal/utils.js.map +1 -1
  131. package/_esm/tempo/multisig/Config.js +84 -0
  132. package/_esm/tempo/multisig/Config.js.map +1 -0
  133. package/_esm/tempo/multisig/Operation.js +170 -0
  134. package/_esm/tempo/multisig/Operation.js.map +1 -0
  135. package/_esm/utils/encoding/fromHex.js +1 -1
  136. package/_esm/utils/encoding/fromHex.js.map +1 -1
  137. package/_esm/utils/hash/isHash.js +1 -2
  138. package/_esm/utils/hash/isHash.js.map +1 -1
  139. package/_types/actions/wallet/sendTransaction.d.ts.map +1 -1
  140. package/_types/actions/wallet/sendTransactionSync.d.ts.map +1 -1
  141. package/_types/chains/definitions/tempo.d.ts +72 -92
  142. package/_types/chains/definitions/tempo.d.ts.map +1 -1
  143. package/_types/chains/definitions/tempoDevnet.d.ts +72 -92
  144. package/_types/chains/definitions/tempoDevnet.d.ts.map +1 -1
  145. package/_types/chains/definitions/tempoLocalnet.d.ts +72 -92
  146. package/_types/chains/definitions/tempoLocalnet.d.ts.map +1 -1
  147. package/_types/chains/definitions/tempoModerato.d.ts +72 -92
  148. package/_types/chains/definitions/tempoModerato.d.ts.map +1 -1
  149. package/_types/errors/version.d.ts +1 -1
  150. package/_types/tempo/Abis.d.ts +54 -108
  151. package/_types/tempo/Abis.d.ts.map +1 -1
  152. package/_types/tempo/Account.d.ts +47 -20
  153. package/_types/tempo/Account.d.ts.map +1 -1
  154. package/_types/tempo/Client.d.ts +23 -1
  155. package/_types/tempo/Client.d.ts.map +1 -1
  156. package/_types/tempo/Decorator.d.ts +30 -5
  157. package/_types/tempo/Decorator.d.ts.map +1 -1
  158. package/_types/tempo/Formatters.d.ts.map +1 -1
  159. package/_types/tempo/Multisig.d.ts +37 -0
  160. package/_types/tempo/Multisig.d.ts.map +1 -0
  161. package/_types/tempo/Selectors.d.ts +2 -3
  162. package/_types/tempo/Selectors.d.ts.map +1 -1
  163. package/_types/tempo/Store.d.ts +62 -0
  164. package/_types/tempo/Store.d.ts.map +1 -0
  165. package/_types/tempo/Transaction.d.ts +12 -31
  166. package/_types/tempo/Transaction.d.ts.map +1 -1
  167. package/_types/tempo/Transport.d.ts +37 -5
  168. package/_types/tempo/Transport.d.ts.map +1 -1
  169. package/_types/tempo/Zone.d.ts +35 -6799
  170. package/_types/tempo/Zone.d.ts.map +1 -1
  171. package/_types/tempo/actions/accessKey.d.ts +38 -8
  172. package/_types/tempo/actions/accessKey.d.ts.map +1 -1
  173. package/_types/tempo/actions/amm.d.ts.map +1 -1
  174. package/_types/tempo/actions/channel.d.ts.map +1 -1
  175. package/_types/tempo/actions/dex.d.ts.map +1 -1
  176. package/_types/tempo/actions/earn/deployment.d.ts +3 -3
  177. package/_types/tempo/actions/earn/deployment.d.ts.map +1 -1
  178. package/_types/tempo/actions/earn.d.ts.map +1 -1
  179. package/_types/tempo/actions/fee.d.ts.map +1 -1
  180. package/_types/tempo/actions/multisig.d.ts +139 -136
  181. package/_types/tempo/actions/multisig.d.ts.map +1 -1
  182. package/_types/tempo/actions/policy.d.ts.map +1 -1
  183. package/_types/tempo/actions/receivePolicy.d.ts.map +1 -1
  184. package/_types/tempo/actions/reward.d.ts.map +1 -1
  185. package/_types/tempo/actions/token.d.ts.map +1 -1
  186. package/_types/tempo/actions/virtualAddress.d.ts.map +1 -1
  187. package/_types/tempo/actions/zone.d.ts +4 -4
  188. package/_types/tempo/actions/zone.d.ts.map +1 -1
  189. package/_types/tempo/chainConfig.d.ts +38 -48
  190. package/_types/tempo/chainConfig.d.ts.map +1 -1
  191. package/_types/tempo/index.d.ts +5 -5
  192. package/_types/tempo/index.d.ts.map +1 -1
  193. package/_types/tempo/internal/types.d.ts +1 -1
  194. package/_types/tempo/internal/types.d.ts.map +1 -1
  195. package/_types/tempo/internal/utils.d.ts +1 -0
  196. package/_types/tempo/internal/utils.d.ts.map +1 -1
  197. package/_types/tempo/multisig/Config.d.ts +42 -0
  198. package/_types/tempo/multisig/Config.d.ts.map +1 -0
  199. package/_types/tempo/multisig/Operation.d.ts +32 -0
  200. package/_types/tempo/multisig/Operation.d.ts.map +1 -0
  201. package/_types/types/chain.d.ts +10 -0
  202. package/_types/types/chain.d.ts.map +1 -1
  203. package/_types/utils/hash/isHash.d.ts +1 -2
  204. package/_types/utils/hash/isHash.d.ts.map +1 -1
  205. package/actions/wallet/sendTransaction.ts +9 -1
  206. package/actions/wallet/sendTransactionSync.ts +9 -1
  207. package/errors/version.ts +1 -1
  208. package/package.json +2 -2
  209. package/tempo/Abis.ts +11 -23
  210. package/tempo/Account.ts +136 -105
  211. package/tempo/Client.ts +46 -3
  212. package/tempo/Decorator.ts +40 -8
  213. package/tempo/Formatters.ts +39 -21
  214. package/tempo/Multisig.ts +1490 -0
  215. package/tempo/Selectors.ts +2 -3
  216. package/tempo/Store.ts +170 -0
  217. package/tempo/Transaction.ts +39 -64
  218. package/tempo/Transport.ts +125 -10
  219. package/tempo/Zone.ts +30 -4
  220. package/tempo/actions/accessKey.ts +210 -23
  221. package/tempo/actions/amm.ts +7 -0
  222. package/tempo/actions/channel.ts +12 -0
  223. package/tempo/actions/dex.ts +10 -0
  224. package/tempo/actions/earn/deployment.ts +16 -3
  225. package/tempo/actions/earn.ts +19 -0
  226. package/tempo/actions/fee.ts +4 -0
  227. package/tempo/actions/multisig.ts +192 -94
  228. package/tempo/actions/policy.ts +8 -0
  229. package/tempo/actions/receivePolicy.ts +6 -0
  230. package/tempo/actions/reward.ts +5 -0
  231. package/tempo/actions/token.ts +33 -0
  232. package/tempo/actions/virtualAddress.ts +2 -0
  233. package/tempo/actions/zone.ts +10 -8
  234. package/tempo/chainConfig.ts +259 -100
  235. package/tempo/index.ts +5 -4
  236. package/tempo/internal/types.ts +3 -2
  237. package/tempo/internal/utils.ts +2 -0
  238. package/tempo/multisig/Config.ts +131 -0
  239. package/tempo/multisig/Operation.ts +211 -0
  240. package/types/chain.ts +14 -0
  241. package/utils/encoding/fromHex.ts +1 -1
  242. package/utils/hash/isHash.ts +2 -3
  243. package/_cjs/tempo/Storage.js.map +0 -1
  244. package/_cjs/tempo/internal/multisig.js +0 -56
  245. package/_cjs/tempo/internal/multisig.js.map +0 -1
  246. package/_esm/tempo/Storage.js.map +0 -1
  247. package/_esm/tempo/internal/multisig.js +0 -56
  248. package/_esm/tempo/internal/multisig.js.map +0 -1
  249. package/_types/tempo/Storage.d.ts +0 -42
  250. package/_types/tempo/Storage.d.ts.map +0 -1
  251. package/_types/tempo/internal/multisig.d.ts +0 -13
  252. package/_types/tempo/internal/multisig.d.ts.map +0 -1
  253. package/tempo/Storage.ts +0 -115
  254. package/tempo/internal/multisig.ts +0 -72
@@ -0,0 +1,1490 @@
1
+ import type { Address } from 'abitype'
2
+ import * as Address_ from 'ox/Address'
3
+ import * as Hash from 'ox/Hash'
4
+ import * as Hex from 'ox/Hex'
5
+ import * as RpcResponse from 'ox/RpcResponse'
6
+ import {
7
+ KeyAuthorization,
8
+ MultisigConfig,
9
+ MultisigOperation,
10
+ Transaction as ox_Transaction,
11
+ SignatureEnvelope,
12
+ TxEnvelopeTempo,
13
+ } from 'ox/tempo'
14
+ import { getBlockNumber } from '../actions/public/getBlockNumber.js'
15
+ import { createClient } from '../clients/createClient.js'
16
+ import { custom } from '../clients/transports/custom.js'
17
+ import type { EIP1193RequestOptions } from '../types/eip1193.js'
18
+ import { decodeFunctionData } from '../utils/abi/decodeFunctionData.js'
19
+ import { isAddressEqual } from '../utils/address/isAddressEqual.js'
20
+ import * as Abis from './Abis.js'
21
+ import * as Addresses from './Addresses.js'
22
+ import { getConfigCommitment } from './actions/multisig.js'
23
+ import * as ConfigStore from './multisig/Config.js'
24
+ import * as OperationStore from './multisig/Operation.js'
25
+ import type * as Store from './Store.js'
26
+ import * as Transaction from './Transaction.js'
27
+
28
+ const submissionTtl = 30_000
29
+ const pollingInterval = 100
30
+
31
+ /**
32
+ * Creates an RPC request handler that coordinates native multisig approvals.
33
+ *
34
+ * @param next - Downstream RPC request handler.
35
+ * @param parameters - Handler parameters.
36
+ * @returns The multisig-aware RPC request handler.
37
+ */
38
+ export function handleRequest(
39
+ next: handleRequest.Handler,
40
+ parameters: handleRequest.Parameters,
41
+ ): handleRequest.Handler {
42
+ if (!parameters.store.compareAndSet)
43
+ throw new RpcResponse.InvalidParamsError({
44
+ message:
45
+ 'Multisig coordination requires a store with atomic `compareAndSet`.',
46
+ })
47
+ return async (request, requestOptions_) => {
48
+ const requestOptions = await resolveRequestOptions({
49
+ request,
50
+ requestOptions: requestOptions_,
51
+ store: parameters.store,
52
+ })
53
+ const client = createClient({
54
+ transport: custom({
55
+ request: ({ method, params }, options) =>
56
+ next({ method, params }, { ...requestOptions, ...options }),
57
+ }),
58
+ })
59
+
60
+ if (request.method === 'multisig_getConfig') {
61
+ const value = request.params?.[0]
62
+ const address =
63
+ value && typeof value === 'object' && 'address' in value
64
+ ? value.address
65
+ : undefined
66
+ if (
67
+ typeof address !== 'string' ||
68
+ !Address_.validate(address) ||
69
+ Hex.toBigInt(address) === 0n
70
+ )
71
+ throw new RpcResponse.InvalidParamsError({
72
+ message: 'Expected a multisig account address.',
73
+ })
74
+ const blockNumber = await getBlockNumber(client, { cacheTime: 0 })
75
+ const commitment = await getConfigCommitment(client, {
76
+ account: address,
77
+ blockNumber,
78
+ })
79
+ const config = await ConfigStore.read(parameters.store, {
80
+ address,
81
+ commitment,
82
+ })
83
+ if (!config) return null
84
+ return MultisigConfig.toRpc(config)
85
+ }
86
+
87
+ if (request.method === 'multisig_getOperation') {
88
+ const hash = request.params?.[0]
89
+ if (typeof hash !== 'string' || !Hash.validate(hash))
90
+ throw new RpcResponse.InvalidParamsError({
91
+ message: 'Expected a multisig operation hash.',
92
+ })
93
+ const operation = await OperationStore.read(parameters.store, hash)
94
+ return operation ? MultisigOperation.toRpc(operation) : null
95
+ }
96
+
97
+ if (request.method === 'multisig_approveKeyAuthorization')
98
+ return await approveKeyAuthorization({
99
+ client,
100
+ request,
101
+ store: parameters.store,
102
+ })
103
+
104
+ if (
105
+ request.method === 'eth_getTransactionByHash' ||
106
+ request.method === 'eth_getTransactionReceipt'
107
+ ) {
108
+ const hash = request.params?.[0]
109
+ if (typeof hash !== 'string' || !Hash.validate(hash))
110
+ return await next(request, requestOptions)
111
+ const operation = await OperationStore.read(parameters.store, hash)
112
+ if (!operation || operation.type !== 'transaction')
113
+ return await next(request, requestOptions)
114
+ if (request.method === 'eth_getTransactionReceipt') {
115
+ if (operation.status === 'pending') return null
116
+ const transactionHash = await getSubmittedTransactionHash(
117
+ parameters.store,
118
+ operation,
119
+ )
120
+ if (!transactionHash) return null
121
+ const receipt = await next(
122
+ {
123
+ ...request,
124
+ params: [transactionHash],
125
+ },
126
+ requestOptions,
127
+ )
128
+ if (!receipt || typeof receipt !== 'object') return receipt
129
+ const success =
130
+ operation.status === 'submitting'
131
+ ? await completeSubmission(
132
+ parameters.store,
133
+ operation,
134
+ transactionHash,
135
+ )
136
+ : operation
137
+ return {
138
+ ...receipt,
139
+ multisig: MultisigOperation.toRpc(success),
140
+ }
141
+ }
142
+ if (operation.status === 'pending')
143
+ return await toTransaction(client, operation, parameters.store)
144
+ const transactionHash = await getSubmittedTransactionHash(
145
+ parameters.store,
146
+ operation,
147
+ )
148
+ if (!transactionHash)
149
+ return await toTransaction(client, operation, parameters.store)
150
+ const transaction = await next(
151
+ {
152
+ ...request,
153
+ params: [transactionHash],
154
+ },
155
+ requestOptions,
156
+ )
157
+ if (!transaction || typeof transaction !== 'object')
158
+ return await toTransaction(client, operation, parameters.store)
159
+ const success =
160
+ operation.status === 'submitting'
161
+ ? await completeSubmission(
162
+ parameters.store,
163
+ operation,
164
+ transactionHash,
165
+ )
166
+ : operation
167
+ return {
168
+ ...transaction,
169
+ multisig: MultisigOperation.toRpc(success),
170
+ }
171
+ }
172
+
173
+ if (
174
+ request.method !== 'eth_sendRawTransaction' &&
175
+ request.method !== 'eth_sendRawTransactionSync' &&
176
+ request.method !== 'multisig_approveRawTransaction' &&
177
+ request.method !== 'multisig_approveRawTransactionSync'
178
+ )
179
+ return await next(request, requestOptions)
180
+
181
+ const standard =
182
+ request.method === 'eth_sendRawTransaction' ||
183
+ request.method === 'eth_sendRawTransactionSync'
184
+ const serialized = request.params?.[0]
185
+ if (!isSerializedTempoTransaction(serialized)) {
186
+ if (standard) return await next(request, requestOptions)
187
+ throw new RpcResponse.InvalidParamsError({
188
+ message: 'Expected a serialized Tempo multisig transaction.',
189
+ })
190
+ }
191
+
192
+ return await submit({
193
+ client,
194
+ method: request.method,
195
+ next,
196
+ request,
197
+ requestOptions,
198
+ serialized,
199
+ store: parameters.store,
200
+ })
201
+ }
202
+ }
203
+
204
+ export declare namespace handleRequest {
205
+ /** RPC request handler. */
206
+ export type Handler = (
207
+ request: Request,
208
+ options?: RequestOptions | undefined,
209
+ ) => Promise<unknown>
210
+
211
+ /** RPC request passed to a handler. */
212
+ export type Request = {
213
+ /** RPC method name. */
214
+ method: string
215
+ /** RPC method parameters. */
216
+ params?: readonly unknown[] | undefined
217
+ }
218
+
219
+ /** Options for one handled request. */
220
+ export type RequestOptions = EIP1193RequestOptions & {
221
+ /** Chain selected by the caller or inferred from the multisig request. */
222
+ chainId?: number | undefined
223
+ }
224
+
225
+ /** Parameters for {@link handleRequest}. */
226
+ export type Parameters = {
227
+ /** Store shared by multisig coordinators. */
228
+ store: Store.Atomic
229
+ }
230
+
231
+ /** Error type for {@link handleRequest}. */
232
+ export type ErrorType =
233
+ | ConfigStore.InvalidStoreValueError
234
+ | OperationStore.InvalidStoreValueError
235
+ | OperationStore.StoreConflictError
236
+ | RpcResponse.InvalidParamsError
237
+ }
238
+
239
+ /** Collects approvals and submits a transaction after quorum. @internal */
240
+ // biome-ignore lint/correctness/noUnusedVariables: _
241
+ async function submit(options: submit.Options) {
242
+ const transaction = (() => {
243
+ try {
244
+ return deserialize(options.serialized)
245
+ } catch (error) {
246
+ if (
247
+ options.method !== 'eth_sendRawTransaction' &&
248
+ options.method !== 'eth_sendRawTransactionSync'
249
+ )
250
+ throw error
251
+ return undefined
252
+ }
253
+ })()
254
+ const signature = transaction?.signature
255
+ if (!transaction || signature?.type !== 'multisig') {
256
+ if (
257
+ options.method === 'eth_sendRawTransaction' ||
258
+ options.method === 'eth_sendRawTransactionSync'
259
+ )
260
+ return await options.next(options.request, options.requestOptions)
261
+ throw new RpcResponse.InvalidParamsError({
262
+ message: 'Expected a multisig transaction signature.',
263
+ })
264
+ }
265
+
266
+ const { signature: _, ...unsigned } = transaction
267
+ const envelope = TxEnvelopeTempo.from(unsigned as never)
268
+ const serializedUnsigned = serializeUnsigned(
269
+ envelope,
270
+ options.serialized.startsWith(TxEnvelopeTempo.feePayerMagic),
271
+ )
272
+ const blockNumber = await getBlockNumber(options.client, { cacheTime: 0 })
273
+ const config = MultisigConfig.from(signature.config)
274
+ const configCommitment = await validateConfig({
275
+ account: signature.account,
276
+ blockNumber,
277
+ client: options.client,
278
+ config,
279
+ })
280
+ const operationHash = MultisigOperation.getHash({
281
+ account: signature.account,
282
+ config,
283
+ transaction: serializedUnsigned,
284
+ type: 'transaction',
285
+ })
286
+ const incoming = signature.signatures.map((signature) =>
287
+ SignatureEnvelope.serialize(signature),
288
+ )
289
+ if (incoming.length === 0)
290
+ throw new RpcResponse.InvalidParamsError({
291
+ message: 'A multisig approval envelope must include a signature.',
292
+ })
293
+ const now = Date.now()
294
+ const expiredSubmissionIds = new Set<Hex.Hex>()
295
+ const operation = await OperationStore.update(
296
+ options.store,
297
+ operationHash,
298
+ async (existing) => {
299
+ if (existing && existing.type !== 'transaction')
300
+ throw new OperationStore.InvalidStoreValueError()
301
+ if (existing?.status === 'success') return existing
302
+ if (existing?.status === 'submitting' && existing.expiresAt! > now)
303
+ return existing
304
+ if (existing?.status === 'submitting')
305
+ expiredSubmissionIds.add(existing.submissionId!)
306
+ const existingApprovals = existing
307
+ ? await selectApprovals({
308
+ account: signature.account,
309
+ client: options.client,
310
+ config,
311
+ discardInvalidNested: true,
312
+ hash: operationHash,
313
+ approvals: existing.approvals,
314
+ blockNumber,
315
+ store: options.store,
316
+ })
317
+ : undefined
318
+ const approvals = await selectApprovals({
319
+ account: signature.account,
320
+ client: options.client,
321
+ config,
322
+ hash: operationHash,
323
+ approvals: [...(existingApprovals?.approvals ?? []), ...incoming],
324
+ blockNumber,
325
+ store: options.store,
326
+ })
327
+ return MultisigOperation.from({
328
+ account: signature.account,
329
+ approvals: approvals.approvals,
330
+ config,
331
+ createdAt: existing?.createdAt ?? now,
332
+ hash: operationHash,
333
+ signatureCount: approvals.signatureCount,
334
+ status: 'pending',
335
+ threshold: approvals.threshold,
336
+ transaction: mergeTransaction(
337
+ existing?.transaction,
338
+ serializedUnsigned,
339
+ ),
340
+ type: 'transaction',
341
+ updatedAt: now,
342
+ weight: approvals.weight,
343
+ })
344
+ },
345
+ )
346
+
347
+ for (const submissionId of expiredSubmissionIds) {
348
+ if (
349
+ operation.status === 'submitting' &&
350
+ operation.submissionId?.toLowerCase() === submissionId.toLowerCase()
351
+ )
352
+ continue
353
+ await removeSettledSubmission(options.store, operationHash, submissionId)
354
+ }
355
+
356
+ await ConfigStore.write(options.store, {
357
+ address: signature.account,
358
+ commitment: configCommitment,
359
+ config,
360
+ })
361
+ await cacheNextConfigs({
362
+ account: signature.account,
363
+ config,
364
+ store: options.store,
365
+ transaction,
366
+ })
367
+
368
+ if (operation.type !== 'transaction')
369
+ throw new OperationStore.InvalidStoreValueError()
370
+ if (operation.status === 'success')
371
+ return await submittedResult(options, operation)
372
+ if (operation.status === 'submitting' && operation.expiresAt! > Date.now())
373
+ return await submittingResult(options, operation)
374
+ if (operation.weight < operation.threshold)
375
+ return pendingResult(options.method, operation)
376
+
377
+ const submissionId = Hex.random(32)
378
+ const timeout = options.request.params?.[1]
379
+ const synchronous =
380
+ options.method === 'eth_sendRawTransactionSync' ||
381
+ options.method === 'multisig_approveRawTransactionSync'
382
+ const leaseTtl =
383
+ synchronous &&
384
+ typeof timeout === 'number' &&
385
+ Number.isSafeInteger(timeout) &&
386
+ timeout >= 0
387
+ ? timeout + submissionTtl
388
+ : submissionTtl
389
+ const claim = await OperationStore.update(
390
+ options.store,
391
+ operationHash,
392
+ (current) => {
393
+ if (!current || current.type !== 'transaction')
394
+ throw new OperationStore.InvalidStoreValueError()
395
+ if (current.status === 'success') return current
396
+ if (current.status === 'submitting' && current.expiresAt! > Date.now())
397
+ return current
398
+ if (current.weight < current.threshold) return current
399
+ const now = Date.now()
400
+ return MultisigOperation.from({
401
+ ...current,
402
+ expiresAt: Math.min(now + leaseTtl, Number.MAX_SAFE_INTEGER),
403
+ status: 'submitting',
404
+ submissionId,
405
+ updatedAt: now,
406
+ })
407
+ },
408
+ )
409
+ if (claim.type !== 'transaction')
410
+ throw new OperationStore.InvalidStoreValueError()
411
+ if (claim.status === 'success') return await submittedResult(options, claim)
412
+ if (claim.status === 'pending') return pendingResult(options.method, claim)
413
+ if (claim.submissionId !== submissionId)
414
+ return await submittingResult(options, claim)
415
+
416
+ const { final, transactionHash: initialTransactionHash } =
417
+ await (async () => {
418
+ try {
419
+ const blockNumber = await getBlockNumber(options.client, {
420
+ cacheTime: 0,
421
+ })
422
+ await validateConfig({
423
+ account: claim.account,
424
+ blockNumber,
425
+ client: options.client,
426
+ config: claim.config,
427
+ })
428
+ const finalApprovals = await selectApprovals({
429
+ account: claim.account,
430
+ blockNumber,
431
+ client: options.client,
432
+ config: claim.config,
433
+ hash: claim.hash,
434
+ approvals: claim.approvals,
435
+ store: options.store,
436
+ })
437
+ const final = MultisigOperation.serializeTransaction(claim, {
438
+ approvals: finalApprovals.selectedApprovals,
439
+ })
440
+ const transactionHash = TxEnvelopeTempo.hash(
441
+ TxEnvelopeTempo.deserialize(final) as TxEnvelopeTempo.Signed,
442
+ )
443
+ await OperationStore.writeSubmission(
444
+ options.store,
445
+ operationHash,
446
+ submissionId,
447
+ final,
448
+ )
449
+ return { final, transactionHash }
450
+ } catch (error) {
451
+ await releaseSubmission(options.store, operationHash, submissionId)
452
+ throw error
453
+ }
454
+ })()
455
+ let transactionHash = initialTransactionHash
456
+
457
+ let result: unknown
458
+ const stopLease = maintainSubmissionLease(
459
+ options.store,
460
+ operationHash,
461
+ submissionId,
462
+ leaseTtl,
463
+ )
464
+ let leaseStopped = false
465
+ const stopLeaseOnce = async () => {
466
+ if (leaseStopped) return
467
+ leaseStopped = true
468
+ await stopLease()
469
+ }
470
+ try {
471
+ try {
472
+ result = await options.next(
473
+ {
474
+ method:
475
+ options.method === 'eth_sendRawTransaction' ||
476
+ options.method === 'multisig_approveRawTransaction'
477
+ ? 'eth_sendRawTransaction'
478
+ : 'eth_sendRawTransactionSync',
479
+ params: [final, ...(options.request.params?.slice(1) ?? [])],
480
+ },
481
+ options.requestOptions,
482
+ )
483
+ const returnedHash = getTransactionHash(result)
484
+ if (returnedHash.toLowerCase() !== transactionHash.toLowerCase())
485
+ transactionHash = returnedHash
486
+ } catch (error) {
487
+ const submittedHash = await OperationStore.readSubmission(
488
+ options.store,
489
+ claim,
490
+ submissionId,
491
+ )
492
+ const transaction = await (async () => {
493
+ if (!submittedHash) return null
494
+ try {
495
+ return await options.next({
496
+ method: 'eth_getTransactionByHash',
497
+ params: [submittedHash],
498
+ })
499
+ } catch {
500
+ return null
501
+ }
502
+ })()
503
+ await stopLeaseOnce()
504
+ if (!submittedHash || !transaction) {
505
+ await releaseSubmission(options.store, operationHash, submissionId)
506
+ throw error
507
+ }
508
+ transactionHash = submittedHash
509
+ }
510
+ } finally {
511
+ await stopLeaseOnce()
512
+ }
513
+ const success = await OperationStore.update(
514
+ options.store,
515
+ operationHash,
516
+ (current) => {
517
+ if (!current || current.type !== 'transaction')
518
+ throw new OperationStore.InvalidStoreValueError()
519
+ if (current.status === 'success') {
520
+ if (
521
+ current.transactionHash?.toLowerCase() !==
522
+ transactionHash.toLowerCase()
523
+ )
524
+ throw new OperationStore.InvalidStoreValueError()
525
+ return current
526
+ }
527
+ if (
528
+ current.status !== 'submitting' ||
529
+ current.submissionId !== submissionId
530
+ )
531
+ throw new OperationStore.InvalidStoreValueError()
532
+ const { expiresAt: _, submissionId: __, ...operation } = current
533
+ return MultisigOperation.from({
534
+ ...operation,
535
+ status: 'success',
536
+ transactionHash,
537
+ updatedAt: Date.now(),
538
+ })
539
+ },
540
+ )
541
+ if (success.type !== 'transaction' || success.status !== 'success')
542
+ throw new OperationStore.InvalidStoreValueError()
543
+ await removeSettledSubmission(options.store, operationHash, submissionId)
544
+ if (
545
+ options.method === 'eth_sendRawTransactionSync' &&
546
+ result &&
547
+ typeof result === 'object'
548
+ )
549
+ return { ...result, multisig: MultisigOperation.toRpc(success) }
550
+ return await submittedResult(options, success)
551
+ }
552
+
553
+ declare namespace submit {
554
+ /** Options for {@link submit}. */
555
+ export type Options = {
556
+ /** Client used to validate configs. */
557
+ client: ReturnType<typeof createClient>
558
+ /** RPC submission method. */
559
+ method:
560
+ | 'eth_sendRawTransaction'
561
+ | 'eth_sendRawTransactionSync'
562
+ | 'multisig_approveRawTransaction'
563
+ | 'multisig_approveRawTransactionSync'
564
+ /** Downstream RPC request handler. */
565
+ next: handleRequest.Handler
566
+ /** Original RPC request. */
567
+ request: handleRequest.Request
568
+ /** Original request overrides. */
569
+ requestOptions?: handleRequest.RequestOptions | undefined
570
+ /** Serialized Tempo transaction. */
571
+ serialized: Hex.Hex
572
+ /** Shared multisig store. */
573
+ store: Store.Atomic
574
+ }
575
+ }
576
+
577
+ /** Collects approvals for a multisig key authorization. @internal */
578
+ // biome-ignore lint/correctness/noUnusedVariables: declaration merge
579
+ async function approveKeyAuthorization(
580
+ options: approveKeyAuthorization.Options,
581
+ ) {
582
+ const value = options.request.params?.[0]
583
+ if (!value || typeof value !== 'object')
584
+ throw new RpcResponse.InvalidParamsError({
585
+ message: 'Expected a multisig key authorization approval.',
586
+ })
587
+
588
+ const approval = await (async () => {
589
+ if ('keyAuthorization' in value) {
590
+ const authorization = (() => {
591
+ try {
592
+ return KeyAuthorization.fromRpc(
593
+ value.keyAuthorization as KeyAuthorization.Rpc,
594
+ )
595
+ } catch {
596
+ throw new RpcResponse.InvalidParamsError({
597
+ message: 'Invalid multisig key authorization.',
598
+ })
599
+ }
600
+ })()
601
+ const signature = authorization.signature
602
+ if (signature?.type !== 'multisig')
603
+ throw new RpcResponse.InvalidParamsError({
604
+ message: 'Expected a multisig key authorization signature.',
605
+ })
606
+ if (
607
+ !authorization.account ||
608
+ !isAddressEqual(authorization.account, signature.account)
609
+ )
610
+ throw new RpcResponse.InvalidParamsError({
611
+ message:
612
+ 'Multisig key authorization account does not match its signature.',
613
+ })
614
+ const { signature: _, ...unsigned } = authorization
615
+ const keyAuthorization = KeyAuthorization.serialize(
616
+ KeyAuthorization.from(unsigned),
617
+ )
618
+ const config = MultisigConfig.from(signature.config)
619
+ const hash = MultisigOperation.getHash({
620
+ account: signature.account,
621
+ config,
622
+ keyAuthorization,
623
+ type: 'keyAuthorization',
624
+ })
625
+ return {
626
+ account: signature.account,
627
+ approvals: signature.signatures.map((signature) =>
628
+ SignatureEnvelope.serialize(signature),
629
+ ),
630
+ config,
631
+ hash,
632
+ keyAuthorization,
633
+ }
634
+ }
635
+
636
+ if ('hash' in value && 'signature' in value) {
637
+ if (typeof value.hash !== 'string' || !Hash.validate(value.hash))
638
+ throw new RpcResponse.InvalidParamsError({
639
+ message: 'Expected a multisig operation hash.',
640
+ })
641
+ const operation = await OperationStore.read(options.store, value.hash)
642
+ if (!operation || operation.type !== 'keyAuthorization')
643
+ throw new RpcResponse.InvalidParamsError({
644
+ message: 'Multisig key authorization operation was not found.',
645
+ })
646
+ if (operation.status === 'success') return { operation }
647
+ const signature = (() => {
648
+ try {
649
+ return SignatureEnvelope.serialize(
650
+ SignatureEnvelope.from(value.signature as Hex.Hex),
651
+ )
652
+ } catch {
653
+ throw new RpcResponse.InvalidParamsError({
654
+ message: 'Invalid multisig owner signature.',
655
+ })
656
+ }
657
+ })()
658
+ return {
659
+ account: operation.account,
660
+ approvals: [signature],
661
+ config: operation.config,
662
+ hash: operation.hash,
663
+ keyAuthorization: operation.keyAuthorization,
664
+ }
665
+ }
666
+
667
+ throw new RpcResponse.InvalidParamsError({
668
+ message: 'Expected a multisig key authorization approval.',
669
+ })
670
+ })()
671
+ if ('operation' in approval)
672
+ return MultisigOperation.toRpc(approval.operation)
673
+ if (approval.approvals.length === 0)
674
+ throw new RpcResponse.InvalidParamsError({
675
+ message: 'A multisig approval envelope must include a signature.',
676
+ })
677
+
678
+ const blockNumber = await getBlockNumber(options.client, { cacheTime: 0 })
679
+ const commitment = await validateConfig({
680
+ account: approval.account,
681
+ blockNumber,
682
+ client: options.client,
683
+ config: approval.config,
684
+ })
685
+ const now = Date.now()
686
+ const operation = await OperationStore.update(
687
+ options.store,
688
+ approval.hash,
689
+ async (existing) => {
690
+ if (existing && existing.type !== 'keyAuthorization')
691
+ throw new OperationStore.InvalidStoreValueError()
692
+ if (existing?.status === 'success') return existing
693
+ if (
694
+ existing &&
695
+ (existing.account.toLowerCase() !== approval.account.toLowerCase() ||
696
+ MultisigConfig.getCommitment(existing.config).toLowerCase() !==
697
+ MultisigConfig.getCommitment(approval.config).toLowerCase() ||
698
+ existing.keyAuthorization.toLowerCase() !==
699
+ approval.keyAuthorization.toLowerCase())
700
+ )
701
+ throw new OperationStore.InvalidStoreValueError()
702
+ const existingApprovals = existing
703
+ ? await selectApprovals({
704
+ account: approval.account,
705
+ approvals: existing.approvals,
706
+ blockNumber,
707
+ client: options.client,
708
+ config: approval.config,
709
+ discardInvalidNested: true,
710
+ hash: approval.hash,
711
+ store: options.store,
712
+ })
713
+ : undefined
714
+ const approvals = await selectApprovals({
715
+ account: approval.account,
716
+ approvals: [
717
+ ...(existingApprovals?.approvals ?? []),
718
+ ...approval.approvals,
719
+ ],
720
+ blockNumber,
721
+ client: options.client,
722
+ config: approval.config,
723
+ hash: approval.hash,
724
+ store: options.store,
725
+ })
726
+ if (
727
+ existing &&
728
+ approvals.approvals.length === existing.approvals.length &&
729
+ approvals.approvals.every(
730
+ (approval, index) => approval === existing.approvals[index],
731
+ )
732
+ )
733
+ return existing
734
+ const pending = {
735
+ account: approval.account,
736
+ approvals: approvals.approvals,
737
+ config: approval.config,
738
+ createdAt: existing?.createdAt ?? now,
739
+ hash: approval.hash,
740
+ keyAuthorization: approval.keyAuthorization,
741
+ signatureCount: approvals.signatureCount,
742
+ threshold: approvals.threshold,
743
+ type: 'keyAuthorization',
744
+ updatedAt: now,
745
+ weight: approvals.weight,
746
+ } as const
747
+ if (approvals.weight < approvals.threshold)
748
+ return MultisigOperation.from({ ...pending, status: 'pending' })
749
+ return MultisigOperation.from({
750
+ ...pending,
751
+ keyAuthorization: MultisigOperation.serializeKeyAuthorization(
752
+ approval.keyAuthorization,
753
+ {
754
+ account: approval.account,
755
+ approvals: approvals.selectedApprovals,
756
+ config: approval.config,
757
+ },
758
+ ),
759
+ status: 'success',
760
+ })
761
+ },
762
+ )
763
+ await ConfigStore.write(options.store, {
764
+ address: approval.account,
765
+ commitment,
766
+ config: approval.config,
767
+ })
768
+ if (operation.type !== 'keyAuthorization')
769
+ throw new OperationStore.InvalidStoreValueError()
770
+ return MultisigOperation.toRpc(operation)
771
+ }
772
+
773
+ declare namespace approveKeyAuthorization {
774
+ /** Options for {@link approveKeyAuthorization}. */
775
+ export type Options = {
776
+ /** Client used to validate configs. */
777
+ client: ReturnType<typeof createClient>
778
+ /** Original RPC request. */
779
+ request: handleRequest.Request
780
+ /** Shared multisig store. */
781
+ store: Store.Atomic
782
+ }
783
+ }
784
+
785
+ /** Deserializes a Tempo transaction or throws an RPC parameter error. */
786
+ function deserialize(serialized: Hex.Hex) {
787
+ try {
788
+ return Transaction.deserialize(
789
+ serialized as Transaction.TransactionSerializedTempo,
790
+ )
791
+ } catch {
792
+ throw new RpcResponse.InvalidParamsError({
793
+ message: 'Invalid serialized Tempo transaction.',
794
+ })
795
+ }
796
+ }
797
+
798
+ /** Validates a config against an observed chain commitment. */
799
+ function assertConfig(options: {
800
+ account: `0x${string}`
801
+ commitment: Hex.Hex
802
+ config: MultisigConfig.Config
803
+ }) {
804
+ const { account, commitment, config } = options
805
+ if (
806
+ config.version === 0n &&
807
+ MultisigConfig.getAddress(config).toLowerCase() !== account.toLowerCase()
808
+ )
809
+ throw new RpcResponse.InvalidParamsError({
810
+ message: 'Initial multisig config does not match the multisig account.',
811
+ })
812
+ const expected = (() => {
813
+ if (config.version === 0n) return Hex.fromNumber(0, { size: 32 })
814
+ return MultisigConfig.getCommitment(config)
815
+ })()
816
+ if (commitment.toLowerCase() !== expected.toLowerCase())
817
+ throw new RpcResponse.InvalidParamsError({
818
+ message: `Multisig config does not match account ${account}.`,
819
+ })
820
+ }
821
+
822
+ /** Reads and validates a config commitment at one block. */
823
+ async function validateConfig(options: {
824
+ account: `0x${string}`
825
+ blockNumber: bigint
826
+ client: ReturnType<typeof createClient>
827
+ config: MultisigConfig.Config
828
+ }) {
829
+ const { account, blockNumber, client, config } = options
830
+ const commitment = await getConfigCommitment(client, {
831
+ account,
832
+ blockNumber,
833
+ })
834
+ assertConfig({ account, commitment, config })
835
+ return commitment
836
+ }
837
+
838
+ /** Caches candidate configs from valid native update calls. */
839
+ // biome-ignore lint/correctness/noUnusedVariables: declaration merge
840
+ async function cacheNextConfigs(options: cacheNextConfigs.Options) {
841
+ let currentConfig = options.config
842
+ for (const call of options.transaction.calls) {
843
+ if (
844
+ !call.to ||
845
+ !isAddressEqual(call.to, Addresses.nativeMultisig) ||
846
+ !call.data
847
+ )
848
+ continue
849
+ const decoded = (() => {
850
+ try {
851
+ return decodeFunctionData({ abi: Abis.nativeMultisig, data: call.data })
852
+ } catch {
853
+ return undefined
854
+ }
855
+ })()
856
+ if (decoded?.functionName !== 'updateConfig') continue
857
+ const [current, threshold, owners] = decoded.args
858
+ const suppliedConfig = (() => {
859
+ try {
860
+ return MultisigConfig.from(current)
861
+ } catch {
862
+ return undefined
863
+ }
864
+ })()
865
+ if (
866
+ !suppliedConfig ||
867
+ MultisigConfig.getCommitment(suppliedConfig).toLowerCase() !==
868
+ MultisigConfig.getCommitment(currentConfig).toLowerCase()
869
+ )
870
+ continue
871
+ const config = (() => {
872
+ try {
873
+ return MultisigConfig.from({
874
+ owners,
875
+ salt: suppliedConfig.salt,
876
+ threshold,
877
+ version: suppliedConfig.version + 1n,
878
+ })
879
+ } catch {
880
+ return undefined
881
+ }
882
+ })()
883
+ if (!config) continue
884
+ await ConfigStore.write(options.store, {
885
+ address: options.account,
886
+ commitment: MultisigConfig.getCommitment(config),
887
+ config,
888
+ })
889
+ currentConfig = config
890
+ }
891
+ }
892
+
893
+ declare namespace cacheNextConfigs {
894
+ /** Parameters for {@link cacheNextConfigs}. */
895
+ export type Options = {
896
+ /** Root multisig account. */
897
+ account: Address
898
+ /** Root config used to authorize the transaction. */
899
+ config: MultisigConfig.Config
900
+ /** Shared multisig store. */
901
+ store: Store.Store
902
+ /** Submitted transaction containing potential config updates. */
903
+ transaction: Transaction.TransactionSerializableTempo
904
+ }
905
+ }
906
+
907
+ /** Selects approvals after validating every nested config. */
908
+ // biome-ignore lint/correctness/noUnusedVariables: _
909
+ async function selectApprovals(options: selectApprovals.Options) {
910
+ const validation = new Map<string, Promise<Hex.Hex>>()
911
+ let validationCount = 0
912
+ const select = async (approvals: readonly SignatureEnvelope.Serialized[]) => {
913
+ const result = await MultisigOperation.selectApprovals({
914
+ account: options.account,
915
+ approvals,
916
+ config: options.config,
917
+ hash: options.hash,
918
+ })
919
+ const configs = new Map<
920
+ string,
921
+ { account: Address; config: MultisigConfig.Config }
922
+ >()
923
+ const visit = (signature: SignatureEnvelope.SignatureEnvelope) => {
924
+ if (signature.type !== 'multisig') return
925
+ const config = MultisigConfig.from(signature.config)
926
+ const key = signature.account.toLowerCase()
927
+ configs.set(key, { account: signature.account, config })
928
+ for (const approval of signature.signatures) visit(approval)
929
+ }
930
+ for (const approval of result.approvals)
931
+ visit(SignatureEnvelope.from(approval))
932
+
933
+ for (const { account, config } of configs.values()) {
934
+ const key = `${account.toLowerCase()}:${MultisigConfig.getCommitment(config)}`
935
+ const pending = (() => {
936
+ const existing = validation.get(key)
937
+ if (existing) return existing
938
+ validationCount++
939
+ if (validationCount > MultisigConfig.maxOwners)
940
+ throw new RpcResponse.InvalidParamsError({
941
+ message: 'Multisig approval validation exceeds the owner limit.',
942
+ })
943
+ const pending = validateConfig({
944
+ account,
945
+ blockNumber: options.blockNumber,
946
+ client: options.client,
947
+ config,
948
+ })
949
+ validation.set(key, pending)
950
+ return pending
951
+ })()
952
+ const commitment = await pending
953
+ await ConfigStore.write(options.store, {
954
+ address: account,
955
+ commitment,
956
+ config,
957
+ })
958
+ }
959
+ return result
960
+ }
961
+ try {
962
+ if (!options.discardInvalidNested) return await select(options.approvals)
963
+
964
+ const approvals: SignatureEnvelope.Serialized[] = []
965
+ for (const approval of options.approvals) {
966
+ if (SignatureEnvelope.from(approval).type !== 'multisig') {
967
+ approvals.push(approval)
968
+ continue
969
+ }
970
+ try {
971
+ const retained = (await select([approval])).approvals[0]
972
+ if (retained) approvals.push(retained)
973
+ } catch (error) {
974
+ if (
975
+ error instanceof MultisigOperation.InvalidApprovalError ||
976
+ error instanceof RpcResponse.InvalidParamsError
977
+ )
978
+ continue
979
+ throw error
980
+ }
981
+ }
982
+ return await select(approvals)
983
+ } catch (error) {
984
+ if (error instanceof MultisigOperation.InvalidApprovalError)
985
+ throw new RpcResponse.InvalidParamsError({ message: error.shortMessage })
986
+ throw error
987
+ }
988
+ }
989
+
990
+ declare namespace selectApprovals {
991
+ /** Options for {@link selectApprovals}. */
992
+ export type Options = MultisigOperation.selectApprovals.Options & {
993
+ /** Block used to validate every config. */
994
+ blockNumber: bigint
995
+ /** Client used to validate nested multisig configurations. */
996
+ client: ReturnType<typeof createClient>
997
+ /** Discards stored nested approvals invalidated by a child configuration change. */
998
+ discardInvalidNested?: boolean | undefined
999
+ /** Shared multisig store. */
1000
+ store: Store.Store
1001
+ }
1002
+ }
1003
+
1004
+ /** Returns an existing successful operation through the requested send method. */
1005
+ async function submittedResult(
1006
+ options: submit.Options,
1007
+ operation: MultisigOperation.TransactionOperation,
1008
+ ) {
1009
+ if (
1010
+ options.method === 'eth_sendRawTransaction' ||
1011
+ options.method === 'multisig_approveRawTransaction'
1012
+ )
1013
+ return operation.hash
1014
+ if (options.method === 'multisig_approveRawTransactionSync')
1015
+ return MultisigOperation.toRpc(operation)
1016
+ const timeout = options.request.params?.[1]
1017
+ const deadline =
1018
+ Date.now() +
1019
+ (typeof timeout === 'number' && timeout >= 0 ? timeout : submissionTtl)
1020
+ while (true) {
1021
+ const receipt = await options.next(
1022
+ {
1023
+ method: 'eth_getTransactionReceipt',
1024
+ params: [operation.transactionHash],
1025
+ },
1026
+ options.requestOptions,
1027
+ )
1028
+ if (receipt && typeof receipt === 'object')
1029
+ return { ...receipt, multisig: MultisigOperation.toRpc(operation) }
1030
+ if (Date.now() >= deadline)
1031
+ throw new Error('Timed out while waiting for the multisig transaction.')
1032
+ await new Promise((resolve) => setTimeout(resolve, pollingInterval))
1033
+ }
1034
+ }
1035
+
1036
+ /** Waits for the relay that owns a live submission lease. */
1037
+ async function submittingResult(
1038
+ options: submit.Options,
1039
+ operation: MultisigOperation.TransactionOperation,
1040
+ ): Promise<unknown> {
1041
+ if (
1042
+ options.method === 'eth_sendRawTransaction' ||
1043
+ options.method === 'multisig_approveRawTransaction'
1044
+ )
1045
+ return operation.hash
1046
+ const timeout = options.request.params?.[1]
1047
+ const deadline =
1048
+ Date.now() +
1049
+ (typeof timeout === 'number' && timeout >= 0 ? timeout : submissionTtl)
1050
+ while (true) {
1051
+ const current = await OperationStore.read(options.store, operation.hash)
1052
+ if (!current || current.type !== 'transaction')
1053
+ throw new OperationStore.InvalidStoreValueError()
1054
+ if (current.status === 'success')
1055
+ return await submittedResult(options, current)
1056
+ if (current.status === 'pending' || current.expiresAt! <= Date.now())
1057
+ return await submit(options)
1058
+ if (Date.now() >= deadline) return pendingResult(options.method, current)
1059
+ await new Promise((resolve) => setTimeout(resolve, pollingInterval))
1060
+ }
1061
+ }
1062
+
1063
+ /** Returns a pending result for an operation that has not reached quorum. */
1064
+ function pendingResult(
1065
+ method: submit.Options['method'],
1066
+ operation: MultisigOperation.TransactionOperation,
1067
+ ) {
1068
+ if (
1069
+ method === 'eth_sendRawTransaction' ||
1070
+ method === 'multisig_approveRawTransaction'
1071
+ )
1072
+ return operation.hash
1073
+ if (method === 'multisig_approveRawTransactionSync')
1074
+ return MultisigOperation.toRpc(operation)
1075
+ return {
1076
+ blockHash: null,
1077
+ blockNumber: null,
1078
+ contractAddress: null,
1079
+ cumulativeGasUsed: null,
1080
+ effectiveGasPrice: null,
1081
+ from: operation.account,
1082
+ gasUsed: null,
1083
+ logs: [],
1084
+ logsBloom: null,
1085
+ multisig: MultisigOperation.toRpc(operation),
1086
+ status: 'pending',
1087
+ to: null,
1088
+ transactionHash: operation.hash,
1089
+ transactionIndex: null,
1090
+ type: '0x76',
1091
+ } as const
1092
+ }
1093
+
1094
+ /** Marks a transaction as successful after a lookup proves that it was submitted. */
1095
+ async function completeSubmission(
1096
+ store: Store.Atomic,
1097
+ operation: MultisigOperation.TransactionOperation,
1098
+ transactionHash: Hex.Hex,
1099
+ ) {
1100
+ if (operation.status !== 'submitting' || !operation.submissionId)
1101
+ throw new OperationStore.InvalidStoreValueError()
1102
+ const success = await OperationStore.update(
1103
+ store,
1104
+ operation.hash,
1105
+ (current) => {
1106
+ if (!current || current.type !== 'transaction')
1107
+ throw new OperationStore.InvalidStoreValueError()
1108
+ if (current.status === 'success') {
1109
+ if (
1110
+ current.transactionHash?.toLowerCase() !==
1111
+ transactionHash.toLowerCase()
1112
+ )
1113
+ throw new OperationStore.InvalidStoreValueError()
1114
+ return current
1115
+ }
1116
+ if (
1117
+ current.status !== 'submitting' ||
1118
+ current.submissionId !== operation.submissionId
1119
+ )
1120
+ throw new OperationStore.InvalidStoreValueError()
1121
+ const {
1122
+ expiresAt: _,
1123
+ submissionId: __,
1124
+ transactionHash: ___,
1125
+ ...value
1126
+ } = current
1127
+ return MultisigOperation.from({
1128
+ ...value,
1129
+ status: 'success',
1130
+ transactionHash,
1131
+ updatedAt: Date.now(),
1132
+ })
1133
+ },
1134
+ )
1135
+ await removeSettledSubmission(store, operation.hash, operation.submissionId)
1136
+ return success as MultisigOperation.TransactionOperation
1137
+ }
1138
+
1139
+ /** Releases a failed submission lease without discarding collected approvals. */
1140
+ async function releaseSubmission(
1141
+ store: Store.Atomic,
1142
+ hash: Hex.Hex,
1143
+ submissionId: Hex.Hex,
1144
+ ) {
1145
+ await OperationStore.update(store, hash, (current) => {
1146
+ if (!current || current.type !== 'transaction')
1147
+ throw new OperationStore.InvalidStoreValueError()
1148
+ if (
1149
+ current.status !== 'submitting' ||
1150
+ current.submissionId !== submissionId
1151
+ )
1152
+ return current
1153
+ const { expiresAt: _, submissionId: __, ...operation } = current
1154
+ return MultisigOperation.from({
1155
+ ...operation,
1156
+ status: 'pending',
1157
+ updatedAt: Date.now(),
1158
+ })
1159
+ })
1160
+ await OperationStore.removeSubmission(store, hash, submissionId)
1161
+ }
1162
+
1163
+ /** Removes settled submission data without replacing a successful result with a cleanup error. */
1164
+ async function removeSettledSubmission(
1165
+ store: Store.Store,
1166
+ hash: Hex.Hex,
1167
+ submissionId: Hex.Hex,
1168
+ ) {
1169
+ try {
1170
+ await OperationStore.removeSubmission(store, hash, submissionId)
1171
+ } catch {}
1172
+ }
1173
+
1174
+ /** Preserves or upgrades a fee-payer envelope without removing an existing signature. */
1175
+ function mergeTransaction(existing: Hex.Hex | undefined, incoming: Hex.Hex) {
1176
+ if (!existing) return incoming
1177
+ const existingTransaction = TxEnvelopeTempo.deserialize(existing as never)
1178
+ const incomingTransaction = TxEnvelopeTempo.deserialize(incoming as never)
1179
+ if (!('feePayerSignature' in incomingTransaction)) return existing
1180
+ if (incomingTransaction.feePayerSignature !== null) return incoming
1181
+ if (!('feePayerSignature' in existingTransaction)) return incoming
1182
+ return existing
1183
+ }
1184
+
1185
+ /** Returns a synthetic transaction while the downstream transaction is unavailable. */
1186
+ async function toTransaction(
1187
+ client: ReturnType<typeof createClient>,
1188
+ operation: MultisigOperation.TransactionOperation,
1189
+ store: Store.Store,
1190
+ ) {
1191
+ const approvals = await (async () => {
1192
+ if (operation.status !== 'pending')
1193
+ return await MultisigOperation.selectApprovals({
1194
+ account: operation.account,
1195
+ approvals: operation.approvals,
1196
+ config: operation.config,
1197
+ hash: operation.hash,
1198
+ })
1199
+ const blockNumber = await getBlockNumber(client, { cacheTime: 0 })
1200
+ await validateConfig({
1201
+ account: operation.account,
1202
+ blockNumber,
1203
+ client,
1204
+ config: operation.config,
1205
+ })
1206
+ return await selectApprovals({
1207
+ account: operation.account,
1208
+ approvals: operation.approvals,
1209
+ blockNumber,
1210
+ client,
1211
+ config: operation.config,
1212
+ discardInvalidNested: true,
1213
+ hash: operation.hash,
1214
+ store,
1215
+ })
1216
+ })()
1217
+ const current =
1218
+ operation.status === 'pending'
1219
+ ? MultisigOperation.from({
1220
+ ...operation,
1221
+ approvals: approvals.approvals,
1222
+ signatureCount: approvals.signatureCount,
1223
+ threshold: approvals.threshold,
1224
+ weight: approvals.weight,
1225
+ })
1226
+ : operation
1227
+ const serialized = MultisigOperation.serializeTransaction(
1228
+ approvals.approvals.length > 0 ? current : operation,
1229
+ {
1230
+ // Keep one stored approval in the synthetic envelope when every nested
1231
+ // approval became stale. The next submission replaces it before broadcast.
1232
+ approvals:
1233
+ approvals.selectedApprovals.length > 0
1234
+ ? approvals.selectedApprovals
1235
+ : operation.approvals.slice(0, 1),
1236
+ },
1237
+ )
1238
+ const transaction = deserialize(serialized)
1239
+ return {
1240
+ ...ox_Transaction.toRpc(
1241
+ {
1242
+ ...transaction,
1243
+ blockHash: null,
1244
+ blockNumber: null,
1245
+ from: operation.account,
1246
+ hash: operation.hash,
1247
+ transactionIndex: null,
1248
+ } as never,
1249
+ { pending: true },
1250
+ ),
1251
+ multisig: MultisigOperation.toRpc(current),
1252
+ }
1253
+ }
1254
+
1255
+ /** Returns the persisted transaction hash for a submitting or successful operation. */
1256
+ async function getSubmittedTransactionHash(
1257
+ store: Store.Store,
1258
+ operation: MultisigOperation.TransactionOperation,
1259
+ ) {
1260
+ if (operation.status === 'success') return operation.transactionHash!
1261
+ if (operation.status !== 'submitting' || !operation.submissionId)
1262
+ throw new OperationStore.InvalidStoreValueError()
1263
+ return await OperationStore.readSubmission(
1264
+ store,
1265
+ operation,
1266
+ operation.submissionId,
1267
+ )
1268
+ }
1269
+
1270
+ /** Renews a submission lease until the returned cleanup function runs. */
1271
+ function maintainSubmissionLease(
1272
+ store: Store.Atomic,
1273
+ hash: Hex.Hex,
1274
+ submissionId: Hex.Hex,
1275
+ leaseTtl: number,
1276
+ ) {
1277
+ let active = true
1278
+ let error: unknown
1279
+ let renewal = Promise.resolve()
1280
+ const timer = setInterval(() => {
1281
+ renewal = renewal.then(async () => {
1282
+ if (!active || error) return
1283
+ try {
1284
+ const operation = await OperationStore.update(
1285
+ store,
1286
+ hash,
1287
+ (current) => {
1288
+ if (!current || current.type !== 'transaction')
1289
+ throw new OperationStore.InvalidStoreValueError()
1290
+ if (
1291
+ current.status !== 'submitting' ||
1292
+ current.submissionId !== submissionId
1293
+ )
1294
+ return current
1295
+ return MultisigOperation.from({
1296
+ ...current,
1297
+ expiresAt: Math.min(
1298
+ Date.now() + leaseTtl,
1299
+ Number.MAX_SAFE_INTEGER,
1300
+ ),
1301
+ updatedAt: Date.now(),
1302
+ })
1303
+ },
1304
+ )
1305
+ if (
1306
+ operation.type !== 'transaction' ||
1307
+ operation.status !== 'submitting' ||
1308
+ operation.submissionId !== submissionId
1309
+ )
1310
+ throw new OperationStore.InvalidStoreValueError()
1311
+ } catch (cause) {
1312
+ error = cause
1313
+ }
1314
+ })
1315
+ }, submissionTtl / 2)
1316
+
1317
+ return async () => {
1318
+ active = false
1319
+ clearInterval(timer)
1320
+ await renewal
1321
+ if (error) throw error
1322
+ }
1323
+ }
1324
+
1325
+ /** Canonically serializes an unsigned Tempo transaction. */
1326
+ function serializeUnsigned(
1327
+ envelope: Omit<TxEnvelopeTempo.TxEnvelopeTempo, 'signature'>,
1328
+ feePayer: boolean,
1329
+ ) {
1330
+ return TxEnvelopeTempo.serialize(
1331
+ envelope,
1332
+ feePayer
1333
+ ? envelope.from
1334
+ ? { format: 'feePayer', sender: envelope.from }
1335
+ : { format: 'feePayer' }
1336
+ : {},
1337
+ )
1338
+ }
1339
+
1340
+ /** Extracts a transaction hash from an RPC submission result. */
1341
+ function getTransactionHash(result: unknown): Hex.Hex {
1342
+ if (typeof result === 'string' && Hash.validate(result)) return result
1343
+ if (
1344
+ result &&
1345
+ typeof result === 'object' &&
1346
+ 'transactionHash' in result &&
1347
+ typeof result.transactionHash === 'string' &&
1348
+ Hash.validate(result.transactionHash)
1349
+ )
1350
+ return result.transactionHash
1351
+ throw new Error('Expected transaction hash in multisig broadcast result.')
1352
+ }
1353
+
1354
+ /** Resolves and validates the chain used by a handled request. */
1355
+ async function resolveRequestOptions(options: {
1356
+ request: handleRequest.Request
1357
+ requestOptions?: handleRequest.RequestOptions | undefined
1358
+ store: Store.Store
1359
+ }): Promise<handleRequest.RequestOptions | undefined> {
1360
+ const { request, requestOptions, store } = options
1361
+ const chainId_explicit = requestOptions?.chainId
1362
+ if (
1363
+ chainId_explicit !== undefined &&
1364
+ (!Number.isSafeInteger(chainId_explicit) || chainId_explicit <= 0)
1365
+ )
1366
+ throw new RpcResponse.InvalidParamsError({
1367
+ message: 'Expected a valid chain ID.',
1368
+ })
1369
+ const chainId_request = await resolveRequestChainId(request, store)
1370
+ if (
1371
+ chainId_explicit !== undefined &&
1372
+ chainId_request !== undefined &&
1373
+ chainId_explicit !== chainId_request
1374
+ )
1375
+ throw new RpcResponse.InvalidParamsError({
1376
+ message: 'Conflicting chain ids.',
1377
+ })
1378
+ const chainId = chainId_explicit ?? chainId_request
1379
+ if (chainId === undefined) return requestOptions
1380
+ return { ...requestOptions, chainId }
1381
+ }
1382
+
1383
+ /** Resolves a chain from request fields, envelopes, or stored operations. */
1384
+ async function resolveRequestChainId(
1385
+ request: handleRequest.Request,
1386
+ store: Store.Store,
1387
+ ) {
1388
+ const value = request.params?.[0]
1389
+ const chainId_body =
1390
+ value && typeof value === 'object' && 'chainId' in value
1391
+ ? parseChainId(value.chainId)
1392
+ : undefined
1393
+ const chainId_multisig = await (async () => {
1394
+ if (
1395
+ request.method === 'eth_sendRawTransaction' ||
1396
+ request.method === 'eth_sendRawTransactionSync' ||
1397
+ request.method === 'multisig_approveRawTransaction' ||
1398
+ request.method === 'multisig_approveRawTransactionSync'
1399
+ ) {
1400
+ if (!isSerializedTempoTransaction(value)) return undefined
1401
+ try {
1402
+ return parseChainId(Transaction.deserialize(value).chainId)
1403
+ } catch {
1404
+ return undefined
1405
+ }
1406
+ }
1407
+
1408
+ if (
1409
+ request.method === 'multisig_approveKeyAuthorization' &&
1410
+ value &&
1411
+ typeof value === 'object' &&
1412
+ 'keyAuthorization' in value
1413
+ ) {
1414
+ try {
1415
+ return parseChainId(
1416
+ KeyAuthorization.fromRpc(
1417
+ value.keyAuthorization as KeyAuthorization.Rpc,
1418
+ ).chainId,
1419
+ )
1420
+ } catch {
1421
+ return undefined
1422
+ }
1423
+ }
1424
+
1425
+ const hash = (() => {
1426
+ if (
1427
+ request.method === 'eth_getTransactionByHash' ||
1428
+ request.method === 'eth_getTransactionReceipt'
1429
+ )
1430
+ return value
1431
+ if (
1432
+ request.method === 'multisig_approveKeyAuthorization' &&
1433
+ value &&
1434
+ typeof value === 'object' &&
1435
+ 'hash' in value
1436
+ )
1437
+ return value.hash
1438
+ return undefined
1439
+ })()
1440
+ if (typeof hash !== 'string' || !Hash.validate(hash)) return undefined
1441
+ const operation = await OperationStore.read(store, hash)
1442
+ if (!operation) return undefined
1443
+ if (operation.type === 'transaction')
1444
+ return parseChainId(
1445
+ Transaction.deserialize(
1446
+ operation.transaction as Transaction.TransactionSerializedTempo,
1447
+ ).chainId,
1448
+ )
1449
+ return parseChainId(
1450
+ KeyAuthorization.deserialize(operation.keyAuthorization).chainId,
1451
+ )
1452
+ })()
1453
+
1454
+ if (
1455
+ chainId_body !== undefined &&
1456
+ chainId_multisig !== undefined &&
1457
+ chainId_body !== chainId_multisig
1458
+ )
1459
+ throw new RpcResponse.InvalidParamsError({
1460
+ message: 'Conflicting chain ids.',
1461
+ })
1462
+ return chainId_body ?? chainId_multisig
1463
+ }
1464
+
1465
+ /** Parses a supported chain ID representation. */
1466
+ function parseChainId(value: unknown) {
1467
+ const chainId = (() => {
1468
+ if (typeof value === 'number') return value
1469
+ if (typeof value === 'bigint') return Number(value)
1470
+ if (typeof value === 'string' && Hex.validate(value)) {
1471
+ try {
1472
+ return Hex.toNumber(value)
1473
+ } catch {
1474
+ return undefined
1475
+ }
1476
+ }
1477
+ return undefined
1478
+ })()
1479
+ if (chainId === undefined || !Number.isSafeInteger(chainId) || chainId <= 0)
1480
+ return undefined
1481
+ return chainId
1482
+ }
1483
+
1484
+ /** Checks whether a value is a serialized Tempo transaction. */
1485
+ function isSerializedTempoTransaction(value: unknown): value is Hex.Hex {
1486
+ return (
1487
+ typeof value === 'string' &&
1488
+ (value.startsWith('0x76') || value.startsWith('0x78'))
1489
+ )
1490
+ }