viem 2.56.9 → 2.57.0

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 (260) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/_cjs/actions/public/waitForTransactionReceipt.js +12 -1
  3. package/_cjs/actions/public/waitForTransactionReceipt.js.map +1 -1
  4. package/_cjs/chains/definitions/blockdag.js +23 -0
  5. package/_cjs/chains/definitions/blockdag.js.map +1 -0
  6. package/_cjs/chains/definitions/candyChain.js +29 -0
  7. package/_cjs/chains/definitions/candyChain.js.map +1 -0
  8. package/_cjs/chains/definitions/creditCoin3Devnet.js +6 -0
  9. package/_cjs/chains/definitions/creditCoin3Devnet.js.map +1 -1
  10. package/_cjs/chains/definitions/creditCoin3Mainnet.js +6 -0
  11. package/_cjs/chains/definitions/creditCoin3Mainnet.js.map +1 -1
  12. package/_cjs/chains/definitions/creditCoin3Testnet.js +6 -0
  13. package/_cjs/chains/definitions/creditCoin3Testnet.js.map +1 -1
  14. package/_cjs/chains/definitions/kortana.js +23 -0
  15. package/_cjs/chains/definitions/kortana.js.map +1 -0
  16. package/_cjs/chains/index.js +20 -14
  17. package/_cjs/chains/index.js.map +1 -1
  18. package/_cjs/clients/createClientResolver.js +45 -0
  19. package/_cjs/clients/createClientResolver.js.map +1 -0
  20. package/_cjs/errors/version.js +1 -1
  21. package/_cjs/index.js +13 -9
  22. package/_cjs/index.js.map +1 -1
  23. package/_cjs/op-stack/actions/getGame.js +1 -1
  24. package/_cjs/op-stack/actions/getGame.js.map +1 -1
  25. package/_cjs/op-stack/actions/getGames.js +1 -1
  26. package/_cjs/op-stack/actions/getGames.js.map +1 -1
  27. package/_cjs/op-stack/actions/getTimeToNextGame.js +1 -1
  28. package/_cjs/op-stack/actions/getTimeToNextGame.js.map +1 -1
  29. package/_cjs/op-stack/actions/getTimeToNextL2Output.js +1 -1
  30. package/_cjs/op-stack/actions/getTimeToNextL2Output.js.map +1 -1
  31. package/_cjs/tempo/Client.js +2 -10
  32. package/_cjs/tempo/Client.js.map +1 -1
  33. package/_cjs/tempo/ExecutionError.js +290 -0
  34. package/_cjs/tempo/ExecutionError.js.map +1 -0
  35. package/_cjs/tempo/Relay.js +88 -0
  36. package/_cjs/tempo/Relay.js.map +1 -0
  37. package/_cjs/tempo/Transaction.js.map +1 -1
  38. package/_cjs/tempo/Transport.js +21 -12
  39. package/_cjs/tempo/Transport.js.map +1 -1
  40. package/_cjs/tempo/chainConfig.js +3 -0
  41. package/_cjs/tempo/chainConfig.js.map +1 -1
  42. package/_cjs/tempo/index.js +3 -3
  43. package/_cjs/tempo/index.js.map +1 -1
  44. package/_cjs/tempo/internal/relay/cache.js +74 -0
  45. package/_cjs/tempo/internal/relay/cache.js.map +1 -0
  46. package/_cjs/tempo/internal/relay/deadline.js +35 -0
  47. package/_cjs/tempo/internal/relay/deadline.js.map +1 -0
  48. package/_cjs/tempo/internal/relay/error.js +102 -0
  49. package/_cjs/tempo/internal/relay/error.js.map +1 -0
  50. package/_cjs/tempo/internal/relay/feePayer.js +426 -0
  51. package/_cjs/tempo/internal/relay/feePayer.js.map +1 -0
  52. package/_cjs/tempo/internal/relay/feeToken.js +143 -0
  53. package/_cjs/tempo/internal/relay/feeToken.js.map +1 -0
  54. package/_cjs/tempo/{Multisig.js → internal/relay/multisig.js} +132 -122
  55. package/_cjs/tempo/internal/relay/multisig.js.map +1 -0
  56. package/_cjs/tempo/internal/relay/plugin.js +13 -0
  57. package/_cjs/tempo/internal/relay/plugin.js.map +1 -0
  58. package/_cjs/tempo/internal/relay/preflight.js +58 -0
  59. package/_cjs/tempo/internal/relay/preflight.js.map +1 -0
  60. package/_cjs/tempo/internal/relay/request.js +319 -0
  61. package/_cjs/tempo/internal/relay/request.js.map +1 -0
  62. package/_cjs/tempo/internal/relay/simulate.js +267 -0
  63. package/_cjs/tempo/internal/relay/simulate.js.map +1 -0
  64. package/_cjs/tempo/internal/relay/utils.js +174 -0
  65. package/_cjs/tempo/internal/relay/utils.js.map +1 -0
  66. package/_cjs/tempo/internal/relay/virtualAddress.js +78 -0
  67. package/_cjs/tempo/internal/relay/virtualAddress.js.map +1 -0
  68. package/_cjs/tempo/internal/relay.js +139 -0
  69. package/_cjs/tempo/internal/relay.js.map +1 -0
  70. package/_cjs/tempo/multisig/Config.js +0 -2
  71. package/_cjs/tempo/multisig/Config.js.map +1 -1
  72. package/_cjs/tempo/multisig/Operation.js +1 -2
  73. package/_cjs/tempo/multisig/Operation.js.map +1 -1
  74. package/_cjs/utils/siwe/parseSiweMessage.js +3 -3
  75. package/_cjs/utils/siwe/parseSiweMessage.js.map +1 -1
  76. package/_esm/actions/public/waitForTransactionReceipt.js +15 -1
  77. package/_esm/actions/public/waitForTransactionReceipt.js.map +1 -1
  78. package/_esm/chains/definitions/blockdag.js +20 -0
  79. package/_esm/chains/definitions/blockdag.js.map +1 -0
  80. package/_esm/chains/definitions/candyChain.js +26 -0
  81. package/_esm/chains/definitions/candyChain.js.map +1 -0
  82. package/_esm/chains/definitions/creditCoin3Devnet.js +6 -0
  83. package/_esm/chains/definitions/creditCoin3Devnet.js.map +1 -1
  84. package/_esm/chains/definitions/creditCoin3Mainnet.js +6 -0
  85. package/_esm/chains/definitions/creditCoin3Mainnet.js.map +1 -1
  86. package/_esm/chains/definitions/creditCoin3Testnet.js +6 -0
  87. package/_esm/chains/definitions/creditCoin3Testnet.js.map +1 -1
  88. package/_esm/chains/definitions/kortana.js +20 -0
  89. package/_esm/chains/definitions/kortana.js.map +1 -0
  90. package/_esm/chains/index.js +3 -0
  91. package/_esm/chains/index.js.map +1 -1
  92. package/_esm/clients/createClientResolver.js +64 -0
  93. package/_esm/clients/createClientResolver.js.map +1 -0
  94. package/_esm/errors/version.js +1 -1
  95. package/_esm/index.js +2 -0
  96. package/_esm/index.js.map +1 -1
  97. package/_esm/op-stack/actions/getGame.js +1 -1
  98. package/_esm/op-stack/actions/getGame.js.map +1 -1
  99. package/_esm/op-stack/actions/getGames.js +1 -1
  100. package/_esm/op-stack/actions/getGames.js.map +1 -1
  101. package/_esm/op-stack/actions/getTimeToNextGame.js +2 -2
  102. package/_esm/op-stack/actions/getTimeToNextGame.js.map +1 -1
  103. package/_esm/op-stack/actions/getTimeToNextL2Output.js +2 -2
  104. package/_esm/op-stack/actions/getTimeToNextL2Output.js.map +1 -1
  105. package/_esm/tempo/Client.js +8 -14
  106. package/_esm/tempo/Client.js.map +1 -1
  107. package/_esm/tempo/ExecutionError.js +306 -0
  108. package/_esm/tempo/ExecutionError.js.map +1 -0
  109. package/_esm/tempo/Relay.js +170 -0
  110. package/_esm/tempo/Relay.js.map +1 -0
  111. package/_esm/tempo/Transaction.js.map +1 -1
  112. package/_esm/tempo/Transport.js +21 -46
  113. package/_esm/tempo/Transport.js.map +1 -1
  114. package/_esm/tempo/chainConfig.js +4 -0
  115. package/_esm/tempo/chainConfig.js.map +1 -1
  116. package/_esm/tempo/index.js +8 -3
  117. package/_esm/tempo/index.js.map +1 -1
  118. package/_esm/tempo/internal/relay/cache.js +72 -0
  119. package/_esm/tempo/internal/relay/cache.js.map +1 -0
  120. package/_esm/tempo/internal/relay/deadline.js +34 -0
  121. package/_esm/tempo/internal/relay/deadline.js.map +1 -0
  122. package/_esm/tempo/internal/relay/error.js +101 -0
  123. package/_esm/tempo/internal/relay/error.js.map +1 -0
  124. package/_esm/tempo/internal/relay/feePayer.js +442 -0
  125. package/_esm/tempo/internal/relay/feePayer.js.map +1 -0
  126. package/_esm/tempo/internal/relay/feeToken.js +143 -0
  127. package/_esm/tempo/internal/relay/feeToken.js.map +1 -0
  128. package/_esm/tempo/{Multisig.js → internal/relay/multisig.js} +133 -129
  129. package/_esm/tempo/internal/relay/multisig.js.map +1 -0
  130. package/_esm/tempo/internal/relay/plugin.js +10 -0
  131. package/_esm/tempo/internal/relay/plugin.js.map +1 -0
  132. package/_esm/tempo/internal/relay/preflight.js +57 -0
  133. package/_esm/tempo/internal/relay/preflight.js.map +1 -0
  134. package/_esm/tempo/internal/relay/request.js +321 -0
  135. package/_esm/tempo/internal/relay/request.js.map +1 -0
  136. package/_esm/tempo/internal/relay/simulate.js +277 -0
  137. package/_esm/tempo/internal/relay/simulate.js.map +1 -0
  138. package/_esm/tempo/internal/relay/utils.js +174 -0
  139. package/_esm/tempo/internal/relay/utils.js.map +1 -0
  140. package/_esm/tempo/internal/relay/virtualAddress.js +73 -0
  141. package/_esm/tempo/internal/relay/virtualAddress.js.map +1 -0
  142. package/_esm/tempo/internal/relay.js +138 -0
  143. package/_esm/tempo/internal/relay.js.map +1 -0
  144. package/_esm/tempo/multisig/Config.js +2 -1
  145. package/_esm/tempo/multisig/Config.js.map +1 -1
  146. package/_esm/tempo/multisig/Operation.js +1 -1
  147. package/_esm/tempo/multisig/Operation.js.map +1 -1
  148. package/_esm/utils/siwe/parseSiweMessage.js +3 -3
  149. package/_esm/utils/siwe/parseSiweMessage.js.map +1 -1
  150. package/_types/actions/public/waitForTransactionReceipt.d.ts.map +1 -1
  151. package/_types/chains/definitions/blockdag.d.ts +51 -0
  152. package/_types/chains/definitions/blockdag.d.ts.map +1 -0
  153. package/_types/chains/definitions/candyChain.d.ts +48 -0
  154. package/_types/chains/definitions/candyChain.d.ts.map +1 -0
  155. package/_types/chains/definitions/creditCoin3Devnet.d.ts +6 -9
  156. package/_types/chains/definitions/creditCoin3Devnet.d.ts.map +1 -1
  157. package/_types/chains/definitions/creditCoin3Mainnet.d.ts +6 -9
  158. package/_types/chains/definitions/creditCoin3Mainnet.d.ts.map +1 -1
  159. package/_types/chains/definitions/creditCoin3Testnet.d.ts +6 -9
  160. package/_types/chains/definitions/creditCoin3Testnet.d.ts.map +1 -1
  161. package/_types/chains/definitions/kortana.d.ts +51 -0
  162. package/_types/chains/definitions/kortana.d.ts.map +1 -0
  163. package/_types/chains/index.d.ts +3 -0
  164. package/_types/chains/index.d.ts.map +1 -1
  165. package/_types/clients/createClientResolver.d.ts +76 -0
  166. package/_types/clients/createClientResolver.d.ts.map +1 -0
  167. package/_types/errors/version.d.ts +1 -1
  168. package/_types/index.d.ts +2 -0
  169. package/_types/index.d.ts.map +1 -1
  170. package/_types/op-stack/actions/getGame.d.ts.map +1 -1
  171. package/_types/tempo/Capabilities.d.ts +7 -28
  172. package/_types/tempo/Capabilities.d.ts.map +1 -1
  173. package/_types/tempo/Client.d.ts +6 -15
  174. package/_types/tempo/Client.d.ts.map +1 -1
  175. package/_types/tempo/ExecutionError.d.ts +79 -0
  176. package/_types/tempo/ExecutionError.d.ts.map +1 -0
  177. package/_types/tempo/Relay.d.ts +302 -0
  178. package/_types/tempo/Relay.d.ts.map +1 -0
  179. package/_types/tempo/Transaction.d.ts +2 -1
  180. package/_types/tempo/Transaction.d.ts.map +1 -1
  181. package/_types/tempo/Transport.d.ts +15 -29
  182. package/_types/tempo/Transport.d.ts.map +1 -1
  183. package/_types/tempo/chainConfig.d.ts.map +1 -1
  184. package/_types/tempo/index.d.ts +8 -3
  185. package/_types/tempo/index.d.ts.map +1 -1
  186. package/_types/tempo/internal/relay/cache.d.ts +9 -0
  187. package/_types/tempo/internal/relay/cache.d.ts.map +1 -0
  188. package/_types/tempo/internal/relay/deadline.d.ts +4 -0
  189. package/_types/tempo/internal/relay/deadline.d.ts.map +1 -0
  190. package/_types/tempo/internal/relay/error.d.ts +47 -0
  191. package/_types/tempo/internal/relay/error.d.ts.map +1 -0
  192. package/_types/tempo/internal/relay/feePayer.d.ts +70 -0
  193. package/_types/tempo/internal/relay/feePayer.d.ts.map +1 -0
  194. package/_types/tempo/internal/relay/feeToken.d.ts +38 -0
  195. package/_types/tempo/internal/relay/feeToken.d.ts.map +1 -0
  196. package/_types/tempo/internal/relay/multisig.d.ts +4 -0
  197. package/_types/tempo/internal/relay/multisig.d.ts.map +1 -0
  198. package/_types/tempo/internal/relay/plugin.d.ts +5 -0
  199. package/_types/tempo/internal/relay/plugin.d.ts.map +1 -0
  200. package/_types/tempo/internal/relay/preflight.d.ts +17 -0
  201. package/_types/tempo/internal/relay/preflight.d.ts.map +1 -0
  202. package/_types/tempo/internal/relay/request.d.ts +16 -0
  203. package/_types/tempo/internal/relay/request.d.ts.map +1 -0
  204. package/_types/tempo/internal/relay/simulate.d.ts +59 -0
  205. package/_types/tempo/internal/relay/simulate.d.ts.map +1 -0
  206. package/_types/tempo/internal/relay/utils.d.ts +294 -0
  207. package/_types/tempo/internal/relay/utils.d.ts.map +1 -0
  208. package/_types/tempo/internal/relay/virtualAddress.d.ts +9 -0
  209. package/_types/tempo/internal/relay/virtualAddress.d.ts.map +1 -0
  210. package/_types/tempo/internal/relay.d.ts +4 -0
  211. package/_types/tempo/internal/relay.d.ts.map +1 -0
  212. package/_types/tempo/multisig/Config.d.ts +0 -13
  213. package/_types/tempo/multisig/Config.d.ts.map +1 -1
  214. package/_types/tempo/multisig/Operation.d.ts +0 -5
  215. package/_types/tempo/multisig/Operation.d.ts.map +1 -1
  216. package/_types/utils/siwe/parseSiweMessage.d.ts.map +1 -1
  217. package/actions/public/waitForTransactionReceipt.ts +16 -2
  218. package/chains/definitions/blockdag.ts +20 -0
  219. package/chains/definitions/candyChain.ts +26 -0
  220. package/chains/definitions/creditCoin3Devnet.ts +6 -0
  221. package/chains/definitions/creditCoin3Mainnet.ts +6 -0
  222. package/chains/definitions/creditCoin3Testnet.ts +6 -0
  223. package/chains/definitions/kortana.ts +20 -0
  224. package/chains/index.ts +3 -0
  225. package/clients/createClientResolver.ts +191 -0
  226. package/errors/version.ts +1 -1
  227. package/index.ts +6 -0
  228. package/op-stack/actions/getGame.ts +3 -1
  229. package/op-stack/actions/getGames.ts +1 -1
  230. package/op-stack/actions/getTimeToNextGame.ts +2 -2
  231. package/op-stack/actions/getTimeToNextL2Output.ts +2 -2
  232. package/package.json +1 -1
  233. package/tempo/Capabilities.ts +7 -29
  234. package/tempo/Client.ts +8 -38
  235. package/tempo/ExecutionError.ts +418 -0
  236. package/tempo/Relay.ts +428 -0
  237. package/tempo/Transaction.ts +2 -1
  238. package/tempo/Transport.ts +69 -60
  239. package/tempo/chainConfig.ts +4 -0
  240. package/tempo/index.ts +7 -3
  241. package/tempo/internal/relay/cache.ts +82 -0
  242. package/tempo/internal/relay/deadline.ts +45 -0
  243. package/tempo/internal/relay/error.ts +128 -0
  244. package/tempo/internal/relay/feePayer.ts +692 -0
  245. package/tempo/internal/relay/feeToken.ts +190 -0
  246. package/tempo/{Multisig.ts → internal/relay/multisig.ts} +162 -193
  247. package/tempo/internal/relay/plugin.ts +13 -0
  248. package/tempo/internal/relay/preflight.ts +81 -0
  249. package/tempo/internal/relay/request.ts +391 -0
  250. package/tempo/internal/relay/simulate.ts +367 -0
  251. package/tempo/internal/relay/utils.ts +202 -0
  252. package/tempo/internal/relay/virtualAddress.ts +95 -0
  253. package/tempo/internal/relay.ts +158 -0
  254. package/tempo/multisig/Config.ts +3 -2
  255. package/tempo/multisig/Operation.ts +1 -1
  256. package/utils/siwe/parseSiweMessage.ts +21 -13
  257. package/_cjs/tempo/Multisig.js.map +0 -1
  258. package/_esm/tempo/Multisig.js.map +0 -1
  259. package/_types/tempo/Multisig.d.ts +0 -37
  260. package/_types/tempo/Multisig.d.ts.map +0 -1
@@ -0,0 +1,692 @@
1
+ import type { Address } from 'abitype'
2
+ import { Hash, type Hex, RpcResponse, Signature } from 'ox'
3
+ import { type Transaction as core_Transaction, TxEnvelopeTempo } from 'ox/tempo'
4
+ import type { LocalAccount } from '../../../accounts/types.js'
5
+ import { type Client, createClient } from '../../../clients/createClient.js'
6
+ import { http } from '../../../clients/transports/http.js'
7
+ import type * as Relay from '../../Relay.js'
8
+ import * as Transaction from '../../Transaction.js'
9
+ import * as Request from './request.js'
10
+ import * as Utils from './utils.js'
11
+
12
+ export function create(options: Relay.feePayer.Options): Relay.Plugin {
13
+ // Bind signing permission to the middleware invocation and its validated payload.
14
+ const authorized = new WeakMap<Relay.Plugin.Context, Hex.Hex>()
15
+ const allowedFeePayers = new Set(
16
+ options.allowedFeePayers?.map((url) =>
17
+ ExternalFeePayerUrl.normalize(url, {
18
+ allowUnsafe: options.internal_allowUnsafeUrls,
19
+ }),
20
+ ),
21
+ )
22
+ return {
23
+ async handleRequest(context, next) {
24
+ const { request } = context
25
+ if (
26
+ ![
27
+ 'eth_fillTransaction',
28
+ 'eth_signRawTransaction',
29
+ 'eth_sendRawTransaction',
30
+ 'eth_sendRawTransactionSync',
31
+ ].includes(request.method)
32
+ )
33
+ return next()
34
+ const { client, getClient, options: requestOptions } = context
35
+ const chainId = requestOptions.chainId
36
+
37
+ const getTokens = (id: number) => context.resolveTokens(id)
38
+
39
+ const record = (details: SponsorshipDetails | undefined) => {
40
+ if (details && requestOptions.response)
41
+ requestOptions.response.sponsorship_details = details
42
+ }
43
+
44
+ if (request.method !== 'eth_fillTransaction') {
45
+ if (!options.account) {
46
+ if (request.method === 'eth_signRawTransaction')
47
+ throw new RpcResponse.MethodNotFoundError({
48
+ message:
49
+ 'eth_signRawTransaction requires a fee payer to be configured on the relay. Add `Relay.feePayer({ account })` to enable transaction sponsorship.',
50
+ })
51
+
52
+ return next()
53
+ }
54
+
55
+ const serialized = request.params?.[0]
56
+ if (
57
+ typeof serialized !== 'string' ||
58
+ !requestsRawSponsorship(serialized as Hex.Hex)
59
+ )
60
+ return next()
61
+
62
+ const result = await handleRawTransaction({
63
+ ...options,
64
+ account: options.account,
65
+ getClient,
66
+ getFeeToken: async (id) => (await getTokens(id))[0],
67
+ method: request.method as
68
+ | 'eth_signRawTransaction'
69
+ | 'eth_sendRawTransaction'
70
+ | 'eth_sendRawTransactionSync',
71
+ request,
72
+ })
73
+
74
+ record(result.sponsorshipDetails)
75
+ return result.result
76
+ }
77
+
78
+ const parameters = request.params![0] as Record<string, unknown>
79
+ const { feePayer: _, ...normalized } =
80
+ Utils.normalizeFillTransactionRequest(parameters)
81
+
82
+ const external =
83
+ typeof parameters.feePayer === 'string'
84
+ ? ExternalFeePayerUrl.normalize(parameters.feePayer, {
85
+ allowUnsafe: options.internal_allowUnsafeUrls ?? false,
86
+ })
87
+ : undefined
88
+ if (external && !allowedFeePayers.has(external))
89
+ throw new RpcResponse.InvalidParamsError({
90
+ message: 'External fee payer URL is not allowed.',
91
+ })
92
+
93
+ const wantsSponsorship =
94
+ (!!options.account || !!external) && parameters.feePayer !== false
95
+ if (options.account && !external) delete normalized.feePayerSignature
96
+ const base = { ...normalized, chainId }
97
+ if (!wantsSponsorship) return Request.fill(client, base)
98
+
99
+ const token =
100
+ options.feeToken ??
101
+ (parameters.feeToken as Address | undefined) ??
102
+ (!options.validate || external
103
+ ? (await getTokens(chainId!))[0]
104
+ : undefined)
105
+ const transaction = {
106
+ ...base,
107
+ feePayer: true,
108
+ ...(token ? { feeToken: token } : {}),
109
+ }
110
+
111
+ const prepared = isPreparedTransaction(transaction)
112
+ const fillClient = external
113
+ ? createClient({
114
+ chain: client.chain,
115
+ transport: http(external, {
116
+ fetchOptions: { redirect: 'error' },
117
+ retryCount: 0,
118
+ }),
119
+ })
120
+ : client
121
+ const result: Request.Result =
122
+ prepared && !external
123
+ ? { tx: transaction }
124
+ : await Request.fill(
125
+ fillClient,
126
+ transaction,
127
+ external ? { ...requestOptions, retryCount: 0 } : requestOptions,
128
+ )
129
+ const filled = Utils.normalizeTempoTransaction(result.tx)
130
+
131
+ // Reserve intrinsic gas for larger signatures before validating and signing the candidate.
132
+ if (
133
+ (!prepared || parameters.feePayer !== true) &&
134
+ filled.gas &&
135
+ !filled.feePayerSignature
136
+ )
137
+ filled.gas += 20_000n
138
+ if (token && (!external || filled.feeToken == null))
139
+ Object.assign(filled, { feeToken: token })
140
+
141
+ const sponsored =
142
+ external ||
143
+ !options.validate ||
144
+ (await shouldSponsor({
145
+ sender: parameters.from as Address | undefined,
146
+ transaction: filled,
147
+ validate: options.validate,
148
+ }))
149
+ if (!sponsored) return Request.fill(client, { ...base, feePayer: false })
150
+
151
+ const defer =
152
+ typeof normalized.multisigSimulation === 'object' &&
153
+ normalized.multisigSimulation !== null
154
+
155
+ const sponsor = sponsored
156
+ ? external
157
+ ? (result.capabilities?.sponsor ?? result.sponsor)
158
+ : options.account
159
+ ? getSponsor({ ...options, account: options.account })
160
+ : undefined
161
+ : undefined
162
+
163
+ if (options.account && !external && !defer) {
164
+ delete filled.signature
165
+ if (!filled.from) filled.from = parameters.from as Address
166
+ authorized.set(
167
+ context,
168
+ TxEnvelopeTempo.getFeePayerSignPayload(
169
+ TxEnvelopeTempo.from(filled as never),
170
+ { sender: filled.from },
171
+ ),
172
+ )
173
+ }
174
+ return {
175
+ ...result,
176
+ ...(sponsor ? { sponsor } : {}),
177
+ tx: Utils.formatTempoTransaction(
178
+ filled as core_Transaction.Transaction,
179
+ ),
180
+ capabilities: {
181
+ ...result.capabilities,
182
+ sponsored: external
183
+ ? (result.capabilities?.sponsored ?? !!sponsor)
184
+ : !!sponsor,
185
+ ...(sponsor ? { sponsor } : {}),
186
+ },
187
+ }
188
+ },
189
+ async signTransaction(result, context) {
190
+ const approved = authorized.get(context)
191
+ authorized.delete(context)
192
+ const parameters = context.request.params![0] as Record<string, unknown>
193
+ if (
194
+ !approved ||
195
+ !options.account ||
196
+ typeof parameters.feePayer === 'string' ||
197
+ parameters.feePayer === false ||
198
+ !result.capabilities?.sponsored ||
199
+ (typeof parameters.multisigSimulation === 'object' &&
200
+ parameters.multisigSimulation !== null)
201
+ )
202
+ return undefined
203
+ const transaction = Utils.normalizeTempoTransaction(result.tx)
204
+ const payload = TxEnvelopeTempo.getFeePayerSignPayload(
205
+ TxEnvelopeTempo.from(transaction as never),
206
+ { sender: (transaction.from ?? parameters.from) as Address },
207
+ )
208
+ if (payload !== approved)
209
+ throw new RpcResponse.InvalidParamsError({
210
+ message: 'Sponsored transaction changed after validation.',
211
+ })
212
+ const signed = await sign({
213
+ account: options.account,
214
+ onSponsored: options.onSponsored,
215
+ sender: parameters.from as Address | undefined,
216
+ transaction,
217
+ signal: context.options.signal,
218
+ })
219
+ if (signed.sponsorshipDetails && context.options.response)
220
+ context.options.response.sponsorship_details = signed.sponsorshipDetails
221
+ const tx = Utils.formatTempoTransaction(
222
+ signed.transaction as core_Transaction.Transaction,
223
+ )
224
+ return tx.feePayerSignature as Relay.Plugin.Signature
225
+ },
226
+ }
227
+ }
228
+
229
+ /** Checks a prepared transaction with its chain ID. Rejected fills fall back to sender payment; rejected raw submissions return a refusal. */
230
+ export type Validate = (
231
+ request: Transaction.TransactionRequest & {
232
+ chainId?: number | Hex.Hex | undefined
233
+ },
234
+ ) => Validation | Promise<Validation>
235
+
236
+ /** A sponsorship verdict: `true` sponsors; `false` or a named reason refuses. */
237
+ export type Validation =
238
+ | boolean
239
+ | 'billing_past_due'
240
+ | 'billing_required'
241
+ | 'fee_token_unsupported'
242
+ | 'spend_limit_exceeded'
243
+ | 'tx_fee_limit_exceeded'
244
+
245
+ /** Details recorded for a sponsored transaction. */
246
+ export type SponsorshipDetails = {
247
+ /** Whether Tempo subsidizes the sponsorship instead of charging the API-key organization. */
248
+ subsidized: boolean
249
+ }
250
+
251
+ /** Refusal messages keyed by named validation reason. */
252
+ const refusalMessages = {
253
+ billing_past_due: 'Billing past due.',
254
+ billing_required: 'Billing required.',
255
+ fee_token_unsupported: 'Fee token unsupported.',
256
+ spend_limit_exceeded: 'Spend limit exceeded.',
257
+ tx_fee_limit_exceeded: 'Transaction fee limit exceeded.',
258
+ } as const satisfies Record<Exclude<Validation, boolean>, string>
259
+
260
+ /** Returns sponsor metadata for `eth_fillTransaction` responses. */
261
+ // biome-ignore lint/correctness/noUnusedVariables: declaration merge
262
+ function getSponsor(options: getSponsor.Options): getSponsor.ReturnType {
263
+ const { account, name, url } = options
264
+ return {
265
+ address: account.address,
266
+ ...(name ? { name } : {}),
267
+ ...(url ? { url } : {}),
268
+ }
269
+ }
270
+
271
+ declare namespace getSponsor {
272
+ type Options = {
273
+ /** Account used for sponsorship. */
274
+ account: LocalAccount
275
+ /** Optional display name. */
276
+ name?: string | undefined
277
+ /** Optional display URL. */
278
+ url?: string | undefined
279
+ }
280
+
281
+ type ReturnType = {
282
+ /** Sponsor address. */
283
+ address: Address
284
+ /** Sponsor display name. */
285
+ name?: string | undefined
286
+ /** Sponsor display URL. */
287
+ url?: string | undefined
288
+ }
289
+ }
290
+
291
+ /** Returns whether the fee payer approves a filled transaction. */
292
+ // biome-ignore lint/correctness/noUnusedVariables: declaration merge
293
+ async function shouldSponsor(options: shouldSponsor.Options) {
294
+ const { sender, transaction, validate } = options
295
+ if (!validate) return true
296
+ // Named refusal reasons collapse to false: fills fall back to an
297
+ // unsponsored fill instead of surfacing the reason.
298
+ const verdict = await validate({
299
+ ...transaction,
300
+ from: sender,
301
+ } as Transaction.TransactionRequest)
302
+ return verdict === true
303
+ }
304
+
305
+ declare namespace shouldSponsor {
306
+ type Options = {
307
+ /** Sender address from the original request. */
308
+ sender?: Address | undefined
309
+ /** Filled transaction to validate. */
310
+ transaction: Record<string, unknown>
311
+ /** Optional sponsorship approval callback. */
312
+ validate?: Validate | undefined
313
+ }
314
+ }
315
+
316
+ /** Returns whether a raw Tempo transaction is explicitly requesting sponsorship. */
317
+ function requestsRawSponsorship(serialized: `0x${string}`) {
318
+ if (!Utils.isSerializedTempoTransaction(serialized)) return false
319
+ const transaction = Transaction.deserialize(serialized)
320
+ return (
321
+ 'feePayerSignature' in transaction && transaction.feePayerSignature === null
322
+ )
323
+ }
324
+
325
+ /** Returns `true` when a fill request already has the fields needed for sponsorship signing. */
326
+ function isPreparedTransaction(value: Record<string, unknown>) {
327
+ return (
328
+ typeof value.from === 'string' &&
329
+ typeof Utils.resolveChainId(value.chainId) === 'number' &&
330
+ typeof value.gas !== 'undefined' &&
331
+ typeof value.nonce !== 'undefined' &&
332
+ (typeof value.maxFeePerGas !== 'undefined' ||
333
+ typeof value.gasPrice !== 'undefined')
334
+ )
335
+ }
336
+
337
+ /** Signs a filled transaction as the fee payer. */
338
+ export async function sign(options: sign.Options) {
339
+ const { account, transaction, sender } = options
340
+ const from = (transaction.from as Address | undefined) ?? sender
341
+ const { signature: _, ...withoutSenderSig } = transaction
342
+ const prepared = { ...withoutSenderSig, from }
343
+
344
+ if (!prepared.from)
345
+ throw new RpcResponse.InvalidParamsError({
346
+ message: 'Transaction sender must be provided before fee payer signing.',
347
+ })
348
+ if (!account.sign)
349
+ throw new Error('Fee payer account cannot sign transactions.')
350
+
351
+ const chainId = Utils.resolveChainId(transaction.chainId)
352
+ if (chainId === undefined)
353
+ throw new RpcResponse.InvalidParamsError({
354
+ message: 'Transaction chainId must be provided before fee payer signing.',
355
+ })
356
+
357
+ const envelope = TxEnvelopeTempo.from(prepared as never)
358
+ const signPayload = TxEnvelopeTempo.getFeePayerSignPayload(envelope, {
359
+ sender: prepared.from,
360
+ })
361
+ options.signal?.throwIfAborted()
362
+ const feePayerSignature = Signature.from(
363
+ await account.sign({ hash: signPayload }),
364
+ )
365
+ options.signal?.throwIfAborted()
366
+
367
+ // Record the commitment before releasing the signature, since the signed fill can be broadcast through another endpoint.
368
+ const feeToken = transaction.feeToken as Address | null | undefined
369
+ const sponsorshipDetails = await options.onSponsored?.({
370
+ chainId,
371
+ ...(feeToken ? { feeToken } : {}),
372
+ method: 'eth_fillTransaction',
373
+ sender: prepared.from,
374
+ signPayload,
375
+ transaction: TxEnvelopeTempo.serialize(envelope, { feePayerSignature }),
376
+ })
377
+
378
+ return {
379
+ ...(sponsorshipDetails ? { sponsorshipDetails } : {}),
380
+ transaction: { ...prepared, feePayerSignature },
381
+ }
382
+ }
383
+
384
+ export declare namespace sign {
385
+ type Options = {
386
+ /** Abort signal for the enclosing fill. */
387
+ signal?: AbortSignal | undefined
388
+ /** Account used as the fee payer. */
389
+ account: LocalAccount
390
+ /** Called once the fee payer has signed the fill. Awaited: a throw aborts the fill. */
391
+ onSponsored?:
392
+ | ((
393
+ event: SponsoredEvent,
394
+ ) => SponsorshipDetails | Promise<SponsorshipDetails | void> | void)
395
+ | undefined
396
+ /** Filled transaction to sign. */
397
+ transaction: Record<string, unknown>
398
+ /** Sender address from the original request. */
399
+ sender?: Address | undefined
400
+ }
401
+ }
402
+
403
+ /** Handles `eth_signRawTransaction` and broadcast methods for sponsored Tempo transactions. */
404
+ // biome-ignore lint/correctness/noUnusedVariables: declaration merge
405
+ async function handleRawTransaction(options: handleRawTransaction.Options) {
406
+ const {
407
+ account,
408
+ feeToken: sponsorFeeToken,
409
+ getClient,
410
+ method,
411
+ request,
412
+ validate,
413
+ } = options
414
+ const serialized = request.params?.[0] as `0x76${string}` | undefined
415
+
416
+ if (!Utils.isSerializedTempoTransaction(serialized))
417
+ throw new RpcResponse.InvalidParamsError({
418
+ message: 'Only Tempo (0x76/0x78) transactions are supported.',
419
+ })
420
+
421
+ const transaction = Transaction.deserialize(serialized)
422
+ // Prefer sender recovered from raw envelope; multisig finalize path supplies fallback sender.
423
+ const sender = transaction.from ?? options.sender
424
+
425
+ // Sponsorship only applies after sender has signed original transaction.
426
+ if (!transaction.signature || !sender)
427
+ throw new RpcResponse.InvalidParamsError({
428
+ message:
429
+ 'Transaction must be signed by the sender before fee payer signing.',
430
+ })
431
+ if (!account.sign)
432
+ throw new Error('Fee payer account cannot sign transactions.')
433
+
434
+ const client = getClient(transaction.chainId)
435
+ const feeToken_chain = (
436
+ client.chain as { feeToken?: Address | undefined } | undefined
437
+ )?.feeToken
438
+ const feeToken =
439
+ (transaction.feeToken as Address | null | undefined) ??
440
+ sponsorFeeToken ??
441
+ (await options.getFeeToken?.(transaction.chainId)) ??
442
+ feeToken_chain
443
+ const transaction_sponsored = feeToken
444
+ ? { ...transaction, feeToken }
445
+ : transaction
446
+
447
+ if (validate) {
448
+ const verdict = await validate(
449
+ transaction_sponsored as Transaction.TransactionRequest,
450
+ )
451
+ if (verdict !== true)
452
+ throw new RpcResponse.InvalidParamsError(
453
+ verdict === false
454
+ ? { message: 'Sponsorship rejected.' }
455
+ : { data: { code: verdict }, message: refusalMessages[verdict] },
456
+ )
457
+ }
458
+
459
+ const envelope = TxEnvelopeTempo.from(transaction_sponsored as never)
460
+ const signPayload = TxEnvelopeTempo.getFeePayerSignPayload(envelope, {
461
+ sender,
462
+ })
463
+ const feePayerSignature = Signature.from(
464
+ await account.sign({ hash: signPayload }),
465
+ )
466
+ const serializedTransaction = TxEnvelopeTempo.serialize(envelope, {
467
+ feePayerSignature,
468
+ signature: transaction.signature,
469
+ })
470
+
471
+ // Awaited before any broadcast so a recording failure refuses sponsorship;
472
+ // no sponsored transaction ever reaches the chain unrecorded.
473
+ const sponsorshipDetails = await options.onSponsored?.({
474
+ chainId: transaction.chainId,
475
+ ...(feeToken ? { feeToken } : {}),
476
+ method,
477
+ sender,
478
+ signPayload,
479
+ transaction: serializedTransaction,
480
+ transactionHash: Hash.keccak256(serializedTransaction),
481
+ })
482
+
483
+ // Raw-sign requests stop after fee-payer signature is added; send methods broadcast it.
484
+ const result =
485
+ method === 'eth_signRawTransaction'
486
+ ? serializedTransaction
487
+ : await client.request({
488
+ method: method as never,
489
+ params: [
490
+ serializedTransaction,
491
+ ...(request.params?.slice(1) ?? []),
492
+ ] as never,
493
+ })
494
+ return { result, ...(sponsorshipDetails ? { sponsorshipDetails } : {}) }
495
+ }
496
+
497
+ declare namespace handleRawTransaction {
498
+ type Options = {
499
+ /** Account used as the fee payer. */
500
+ account: LocalAccount
501
+ /** Optional token the fee payer prefers for sponsored raw transactions. */
502
+ feeToken?: Address | undefined
503
+ /** Optional fee-token resolver used when the raw envelope omits `feeToken`. */
504
+ getFeeToken?:
505
+ | ((chainId: number) => Promise<Address | undefined>)
506
+ | undefined
507
+ /** Client resolver keyed by transaction `chainId`. */
508
+ getClient: (chainId?: number) => Client
509
+ /** Raw transaction method to handle. */
510
+ method:
511
+ | 'eth_signRawTransaction'
512
+ | 'eth_sendRawTransaction'
513
+ | 'eth_sendRawTransactionSync'
514
+ /** Called once the fee payer has signed, before any broadcast. Awaited: a throw aborts the request. */
515
+ onSponsored?:
516
+ | ((
517
+ event: SponsoredEvent,
518
+ ) => SponsorshipDetails | Promise<SponsorshipDetails | void> | void)
519
+ | undefined
520
+ /** Incoming JSON-RPC request. */
521
+ request: { params?: readonly unknown[] | undefined }
522
+ /** Sender address to use if it cannot be recovered from the raw envelope. */
523
+ sender?: Address | undefined
524
+ /** Optional sponsorship approval callback. */
525
+ validate?: Validate | undefined
526
+ }
527
+ }
528
+
529
+ /** Facts of one sponsorship commitment, emitted at fee-payer signing time. */
530
+ export type SponsoredEvent = {
531
+ /** Chain the sponsored transaction targets. */
532
+ chainId: number
533
+ /** Fee token the sponsorship resolved, when known. */
534
+ feeToken?: Address | undefined
535
+ /** Method that triggered sponsorship: a fill (intent) or a raw submission. */
536
+ method:
537
+ | 'eth_fillTransaction'
538
+ | 'eth_signRawTransaction'
539
+ | 'eth_sendRawTransaction'
540
+ | 'eth_sendRawTransactionSync'
541
+ /** Transaction sender. */
542
+ sender: Address
543
+ /** Fee-payer sign payload: a stable identity for the sponsored envelope. */
544
+ signPayload: Hex.Hex
545
+ /** Serialized sponsored transaction (sender-unsigned for fill intents). */
546
+ transaction: Hex.Hex
547
+ /** Transaction hash (keccak256 of the signed envelope); absent for fill intents, whose senders have not signed yet. */
548
+ transactionHash?: Hex.Hex | undefined
549
+ }
550
+
551
+ export namespace ExternalFeePayerUrl {
552
+ type Options = {
553
+ /** Whether to allow non-HTTPS or local/private external fee payer URLs. */
554
+ allowUnsafe?: boolean | undefined
555
+ }
556
+
557
+ /** Normalizes external fee-payer relay URLs before forwarding fill payloads. */
558
+ export function normalize(value: string, options: Options = {}): string {
559
+ let url: URL
560
+ try {
561
+ url = new URL(value)
562
+ } catch {
563
+ throw new RpcResponse.InvalidParamsError({
564
+ message: 'Invalid fee payer URL.',
565
+ })
566
+ }
567
+
568
+ const allowUnsafe = options.allowUnsafe === true
569
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
570
+ throw new RpcResponse.InvalidParamsError({
571
+ message: 'Invalid fee payer URL protocol.',
572
+ })
573
+ if (!allowUnsafe && url.protocol !== 'https:')
574
+ throw new RpcResponse.InvalidParamsError({
575
+ message: 'Invalid fee payer URL protocol.',
576
+ })
577
+ if (!allowUnsafe && isUnsafeHostname(url.hostname))
578
+ throw new RpcResponse.InvalidParamsError({
579
+ message: 'Invalid fee payer URL host.',
580
+ })
581
+
582
+ url.username = ''
583
+ url.password = ''
584
+ url.hash = ''
585
+ return url.href
586
+ }
587
+
588
+ function isUnsafeHostname(hostname: string): boolean {
589
+ const host = normalizeHostname(hostname)
590
+ if (host === 'localhost' || host.endsWith('.localhost')) return true
591
+ const ipv4 = parseIpv4(host)
592
+ if (ipv4) return isUnsafeIpv4(ipv4)
593
+ if (!host.includes(':')) return false
594
+ const ipv6 = parseIpv6(host)
595
+ if (!ipv6) return true
596
+ return isUnsafeIpv6(ipv6)
597
+ }
598
+
599
+ function normalizeHostname(hostname: string): string {
600
+ return hostname
601
+ .toLowerCase()
602
+ .replace(/^\[|\]$/g, '')
603
+ .split('%')[0]!
604
+ }
605
+
606
+ function parseIpv4(
607
+ value: string,
608
+ ): [number, number, number, number] | undefined {
609
+ const parts = value.split('.')
610
+ if (parts.length !== 4 || parts.some((part) => !/^\d+$/.test(part)))
611
+ return undefined
612
+ const numbers = parts.map((part) => Number(part))
613
+ if (
614
+ numbers.some((part) => !Number.isInteger(part) || part < 0 || part > 255)
615
+ )
616
+ return undefined
617
+ return numbers as [number, number, number, number]
618
+ }
619
+
620
+ function isUnsafeIpv4(parts: [number, number, number, number]): boolean {
621
+ const [a, b] = parts
622
+ if (a === 0 || a === 10 || a === 127) return true
623
+ if (a === 100 && b >= 64 && b <= 127) return true
624
+ if (a === 169 && b === 254) return true
625
+ if (a === 172 && b >= 16 && b <= 31) return true
626
+ if (a === 192 && b === 168) return true
627
+ return a >= 224
628
+ }
629
+
630
+ function parseIpv6(
631
+ value: string,
632
+ ):
633
+ | [number, number, number, number, number, number, number, number]
634
+ | undefined {
635
+ const halves = value.split('::')
636
+ if (halves.length > 2) return undefined
637
+
638
+ const left = halves[0] ? halves[0].split(':') : []
639
+ const right = halves[1] ? halves[1].split(':') : []
640
+ const missing = halves.length === 2 ? 8 - left.length - right.length : 0
641
+ if (missing < 0) return undefined
642
+
643
+ const parts = [...left, ...Array(missing).fill('0'), ...right]
644
+ if (parts.length !== 8) return undefined
645
+
646
+ const numbers = parts.map((part) =>
647
+ /^[\da-f]{1,4}$/i.test(part) ? Number.parseInt(part, 16) : NaN,
648
+ )
649
+ if (
650
+ numbers.some(
651
+ (part) => !Number.isInteger(part) || part < 0 || part > 0xffff,
652
+ )
653
+ )
654
+ return undefined
655
+ return numbers as [
656
+ number,
657
+ number,
658
+ number,
659
+ number,
660
+ number,
661
+ number,
662
+ number,
663
+ number,
664
+ ]
665
+ }
666
+
667
+ function isUnsafeIpv6(
668
+ parts: [number, number, number, number, number, number, number, number],
669
+ ) {
670
+ const [a] = parts
671
+ if (parts.every((part) => part === 0)) return true
672
+ if (parts.slice(0, 7).every((part) => part === 0) && parts[7] === 1)
673
+ return true
674
+ if ((a & 0xfe00) === 0xfc00) return true
675
+ if ((a & 0xffc0) === 0xfe80) return true
676
+ if ((a & 0xffc0) === 0xfec0) return true
677
+ if ((a & 0xff00) === 0xff00) return true
678
+
679
+ const ipv4 = ipv4FromIpv6(parts)
680
+ return ipv4 ? isUnsafeIpv4(ipv4) : false
681
+ }
682
+
683
+ function ipv4FromIpv6(
684
+ parts: [number, number, number, number, number, number, number, number],
685
+ ): [number, number, number, number] | undefined {
686
+ const mapped =
687
+ parts.slice(0, 5).every((part) => part === 0) && parts[5] === 0xffff
688
+ const compatible = parts.slice(0, 6).every((part) => part === 0)
689
+ if (!mapped && !compatible) return undefined
690
+ return [parts[6]! >> 8, parts[6]! & 0xff, parts[7]! >> 8, parts[7]! & 0xff]
691
+ }
692
+ }