accounts 0.6.7 → 0.7.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 (50) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/core/ExecutionError.d.ts +25 -0
  3. package/dist/core/ExecutionError.d.ts.map +1 -0
  4. package/dist/core/ExecutionError.js +170 -0
  5. package/dist/core/ExecutionError.js.map +1 -0
  6. package/dist/core/Schema.d.ts +33 -7
  7. package/dist/core/Schema.d.ts.map +1 -1
  8. package/dist/core/zod/rpc.d.ts +14 -1
  9. package/dist/core/zod/rpc.d.ts.map +1 -1
  10. package/dist/core/zod/rpc.js +14 -1
  11. package/dist/core/zod/rpc.js.map +1 -1
  12. package/dist/server/CliAuth.d.ts +110 -43
  13. package/dist/server/CliAuth.d.ts.map +1 -1
  14. package/dist/server/CliAuth.js +243 -155
  15. package/dist/server/CliAuth.js.map +1 -1
  16. package/dist/server/Handler.d.ts +0 -1
  17. package/dist/server/Handler.d.ts.map +1 -1
  18. package/dist/server/Handler.js +0 -1
  19. package/dist/server/Handler.js.map +1 -1
  20. package/dist/server/internal/handlers/relay.d.ts +29 -12
  21. package/dist/server/internal/handlers/relay.d.ts.map +1 -1
  22. package/dist/server/internal/handlers/relay.js +180 -125
  23. package/dist/server/internal/handlers/relay.js.map +1 -1
  24. package/dist/server/internal/handlers/sponsorship.d.ts +77 -0
  25. package/dist/server/internal/handlers/sponsorship.d.ts.map +1 -0
  26. package/dist/server/internal/handlers/sponsorship.js +96 -0
  27. package/dist/server/internal/handlers/sponsorship.js.map +1 -0
  28. package/dist/server/internal/handlers/utils.d.ts +3 -1
  29. package/dist/server/internal/handlers/utils.d.ts.map +1 -1
  30. package/dist/server/internal/handlers/utils.js +15 -12
  31. package/dist/server/internal/handlers/utils.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/core/ExecutionError.test.ts +205 -0
  34. package/src/core/ExecutionError.ts +189 -0
  35. package/src/core/Provider.test.ts +4 -2
  36. package/src/core/zod/rpc.ts +18 -1
  37. package/src/server/CliAuth.test-d.ts +6 -0
  38. package/src/server/CliAuth.test.ts +83 -0
  39. package/src/server/CliAuth.ts +331 -208
  40. package/src/server/Handler.ts +0 -1
  41. package/src/server/internal/handlers/relay.test.ts +318 -108
  42. package/src/server/internal/handlers/relay.ts +243 -138
  43. package/src/server/internal/handlers/sponsorship.ts +172 -0
  44. package/src/server/internal/handlers/utils.ts +15 -10
  45. package/dist/server/internal/handlers/feePayer.d.ts +0 -73
  46. package/dist/server/internal/handlers/feePayer.d.ts.map +0 -1
  47. package/dist/server/internal/handlers/feePayer.js +0 -184
  48. package/dist/server/internal/handlers/feePayer.js.map +0 -1
  49. package/src/server/internal/handlers/feePayer.test.ts +0 -336
  50. package/src/server/internal/handlers/feePayer.ts +0 -271
@@ -1,336 +0,0 @@
1
- import type { RpcRequest } from 'ox'
2
- import { SignatureEnvelope, Transaction as core_Transaction, TxEnvelopeTempo } from 'ox/tempo'
3
- import { parseUnits } from 'viem'
4
- import { sendTransaction, sendTransactionSync, waitForTransactionReceipt } from 'viem/actions'
5
- import { Actions, Transaction, withFeePayer } from 'viem/tempo'
6
- import { afterAll, afterEach, beforeAll, describe, expect, test } from 'vp/test'
7
-
8
- import { accounts, addresses, chain, getClient, http } from '../../../../test/config.js'
9
- import { createServer, type Server } from '../../../../test/utils.js'
10
- import { feePayer } from './feePayer.js'
11
-
12
- const userAccount = accounts[9]!
13
- const feePayerAccount = accounts[0]!
14
-
15
- let server: Server
16
- let fp: ReturnType<typeof getClient>
17
- let requests: RpcRequest.RpcRequest[] = []
18
-
19
- beforeAll(async () => {
20
- server = await createServer(
21
- feePayer({
22
- account: feePayerAccount,
23
- chains: [chain],
24
- transports: { [chain.id]: http() },
25
- onRequest: async (request) => {
26
- requests.push(request)
27
- },
28
- }).listener,
29
- )
30
- fp = getClient({ transport: http(server.url) })
31
- })
32
-
33
- afterAll(() => {
34
- server.close()
35
- process.on('SIGINT', () => {
36
- server.close()
37
- process.exit(0)
38
- })
39
- process.on('SIGTERM', () => {
40
- server.close()
41
- process.exit(0)
42
- })
43
- })
44
-
45
- afterEach(() => {
46
- requests = []
47
- })
48
-
49
- /** Signs a sponsor-bound Tempo transaction, preserving the feePayerSignature. */
50
- async function signSponsoredTx(account: (typeof accounts)[number], transaction: object) {
51
- const serialized = (await Transaction.serialize(transaction as never)) as `0x76${string}`
52
- const envelope = TxEnvelopeTempo.deserialize(serialized)
53
- const signature = await account.sign({
54
- hash: TxEnvelopeTempo.getSignPayload(envelope),
55
- })
56
- return TxEnvelopeTempo.serialize(envelope, {
57
- signature: SignatureEnvelope.from(signature),
58
- })
59
- }
60
-
61
- describe('POST /', () => {
62
- test('default: eth_fillTransaction returns a sponsor-bound transaction the sender can broadcast', async () => {
63
- const response = (await fp.request({
64
- method: 'eth_fillTransaction',
65
- params: [
66
- {
67
- chainId: chain.id,
68
- feePayer: true,
69
- from: userAccount.address,
70
- to: '0x0000000000000000000000000000000000000000',
71
- },
72
- ],
73
- })) as {
74
- sponsor: { address: string }
75
- tx: Record<string, unknown>
76
- }
77
- const prepared = core_Transaction.fromRpc(response.tx as never) as {
78
- feePayerSignature?: unknown
79
- }
80
- const signed = await signSponsoredTx(userAccount, prepared)
81
- const receipt = (await getClient().request({
82
- method: 'eth_sendRawTransactionSync',
83
- params: [signed],
84
- })) as { feePayer?: string | undefined }
85
-
86
- expect(response.sponsor.address).toBe(feePayerAccount.address)
87
- expect(prepared?.feePayerSignature).toBeDefined()
88
- expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
89
- expect(requests.map(({ method }) => method)).toMatchInlineSnapshot(`
90
- [
91
- "eth_fillTransaction",
92
- ]
93
- `)
94
- })
95
-
96
- test('behavior: mutating a sponsor-bound transaction invalidates the fee payer binding', async () => {
97
- const response = (await fp.request({
98
- method: 'eth_fillTransaction',
99
- params: [
100
- {
101
- chainId: chain.id,
102
- feePayer: true,
103
- from: userAccount.address,
104
- to: '0x0000000000000000000000000000000000000000',
105
- },
106
- ],
107
- })) as { tx: Record<string, unknown> }
108
- const prepared = core_Transaction.fromRpc(response.tx as never) as {
109
- gas?: bigint | undefined
110
- feePayerSignature?: unknown
111
- }
112
- const signed = await signSponsoredTx(userAccount, {
113
- ...prepared,
114
- gas: (prepared?.gas ?? 0n) + 1n,
115
- })
116
-
117
- await expect(
118
- getClient().request({
119
- method: 'eth_sendRawTransactionSync',
120
- params: [signed],
121
- }),
122
- ).rejects.toThrowError()
123
- })
124
-
125
- test('behavior: eth_signRawTransaction', async () => {
126
- const client = getClient({
127
- account: userAccount,
128
- transport: withFeePayer(http(), http(server.url)),
129
- })
130
-
131
- const receipt = await sendTransactionSync(client, {
132
- feePayer: true,
133
- to: '0x0000000000000000000000000000000000000000',
134
- })
135
-
136
- expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
137
-
138
- expect(requests.map(({ method }) => method)).toMatchInlineSnapshot(`
139
- [
140
- "eth_signRawTransaction",
141
- ]
142
- `)
143
- })
144
-
145
- test('behavior: eth_sendRawTransaction', async () => {
146
- const client = getClient({
147
- account: userAccount,
148
- transport: withFeePayer(http(), http(server.url), {
149
- policy: 'sign-and-broadcast',
150
- }),
151
- })
152
-
153
- const hash = await sendTransaction(client, {
154
- feePayer: true,
155
- to: '0x0000000000000000000000000000000000000001',
156
- })
157
- const receipt = await waitForTransactionReceipt(getClient(), { hash })
158
-
159
- expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
160
-
161
- expect(requests.map(({ method }) => method)).toMatchInlineSnapshot(`
162
- [
163
- "eth_sendRawTransaction",
164
- ]
165
- `)
166
- })
167
-
168
- test('behavior: eth_sendRawTransactionSync', async () => {
169
- const client = getClient({
170
- account: userAccount,
171
- transport: withFeePayer(http(), http(server.url), {
172
- policy: 'sign-and-broadcast',
173
- }),
174
- })
175
-
176
- const receipt = await sendTransactionSync(client, {
177
- feePayer: true,
178
- to: '0x0000000000000000000000000000000000000002',
179
- })
180
-
181
- expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
182
-
183
- expect(requests.map(({ method }) => method)).toMatchInlineSnapshot(`
184
- [
185
- "eth_sendRawTransactionSync",
186
- ]
187
- `)
188
- })
189
-
190
- test('behavior: unsupported method', async () => {
191
- await expect(fp.request({ method: 'eth_chainId' })).rejects.toThrowError()
192
- })
193
-
194
- test('behavior: internal error', async () => {
195
- await expect(
196
- fp.request({
197
- method: 'eth_signRawTransaction' as never,
198
- params: ['0xinvalid'],
199
- }),
200
- ).rejects.toThrowError()
201
- })
202
- })
203
-
204
- describe('behavior: conditional sponsoring', () => {
205
- const rejectedAccount = accounts[3]!
206
- let conditionalServer: Server
207
- let conditionalFp: ReturnType<typeof getClient>
208
-
209
- beforeAll(async () => {
210
- // Fund accounts with alphaUsd for transfers + fee payment.
211
- const rpc = getClient()
212
- await Actions.token.mintSync(rpc, {
213
- account: accounts[0]!,
214
- token: addresses.alphaUsd,
215
- amount: parseUnits('100', 6),
216
- to: userAccount.address,
217
- })
218
- await Actions.fee.setUserToken(rpc, { account: userAccount, token: addresses.alphaUsd })
219
- await Actions.token.mintSync(rpc, {
220
- account: accounts[0]!,
221
- token: addresses.alphaUsd,
222
- amount: parseUnits('100', 6),
223
- to: rejectedAccount.address,
224
- })
225
- await Actions.fee.setUserToken(rpc, { account: rejectedAccount, token: addresses.alphaUsd })
226
-
227
- conditionalServer = await createServer(
228
- feePayer({
229
- account: feePayerAccount,
230
- chains: [chain],
231
- transports: { [chain.id]: http() },
232
- validate: (request) =>
233
- request.from?.toLowerCase() !== rejectedAccount.address.toLowerCase(),
234
- }).listener,
235
- )
236
- conditionalFp = getClient({ transport: http(conditionalServer.url) })
237
- })
238
-
239
- afterAll(() => {
240
- conditionalServer.close()
241
- })
242
-
243
- test('behavior: approved tx is sponsored and can be broadcast', async () => {
244
- const { data, to } = Actions.token.transfer.call({
245
- token: addresses.alphaUsd,
246
- to: rejectedAccount.address,
247
- amount: 1n,
248
- })
249
- const response = (await conditionalFp.request({
250
- method: 'eth_fillTransaction',
251
- params: [
252
- {
253
- chainId: chain.id,
254
- feePayer: true,
255
- from: userAccount.address,
256
- to,
257
- data,
258
- },
259
- ],
260
- })) as { sponsor?: { address: string }; tx: Record<string, unknown> }
261
-
262
- const prepared = core_Transaction.fromRpc(response.tx as never) as {
263
- feePayerSignature?: unknown
264
- }
265
- expect(prepared.feePayerSignature).toBeDefined()
266
- expect(response.sponsor?.address).toBe(feePayerAccount.address)
267
-
268
- const signed = await signSponsoredTx(userAccount, prepared)
269
- const receipt = (await getClient().request({
270
- method: 'eth_sendRawTransactionSync',
271
- params: [signed],
272
- })) as { feePayer?: string | undefined }
273
-
274
- expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
275
- })
276
-
277
- test('behavior: rejected tx is not sponsored and can be self-paid', async () => {
278
- const { data, to } = Actions.token.transfer.call({
279
- token: addresses.alphaUsd,
280
- to: userAccount.address,
281
- amount: 1n,
282
- })
283
- const response = (await conditionalFp.request({
284
- method: 'eth_fillTransaction',
285
- params: [
286
- {
287
- chainId: chain.id,
288
- feePayer: true,
289
- from: rejectedAccount.address,
290
- to,
291
- data,
292
- },
293
- ],
294
- })) as { sponsor?: { address: string }; tx: Record<string, unknown> }
295
-
296
- const prepared = core_Transaction.fromRpc(response.tx as never) as {
297
- feePayerSignature?: unknown
298
- }
299
- expect(prepared.feePayerSignature).toBeUndefined()
300
- expect(response.sponsor).toBeUndefined()
301
-
302
- const signed = await signSponsoredTx(rejectedAccount, prepared)
303
- const receipt = (await getClient().request({
304
- method: 'eth_sendRawTransactionSync',
305
- params: [signed],
306
- })) as { feePayer?: string | undefined }
307
-
308
- // Sender pays their own fee — no external fee payer.
309
- expect(receipt.feePayer).not.toBe(feePayerAccount.address.toLowerCase())
310
- })
311
-
312
- test('behavior: rejected raw transaction returns error', async () => {
313
- // First fill without the conditional server to get a signed tx.
314
- const response = (await fp.request({
315
- method: 'eth_fillTransaction',
316
- params: [
317
- {
318
- chainId: chain.id,
319
- feePayer: true,
320
- from: rejectedAccount.address,
321
- to: '0x0000000000000000000000000000000000000000',
322
- },
323
- ],
324
- })) as { tx: Record<string, unknown> }
325
- const prepared = core_Transaction.fromRpc(response.tx as never)
326
- const signed = await signSponsoredTx(rejectedAccount, prepared)
327
-
328
- // Submit the signed tx to the conditional server — should reject.
329
- await expect(
330
- conditionalFp.request({
331
- method: 'eth_signRawTransaction' as never,
332
- params: [signed],
333
- }),
334
- ).rejects.toThrowError()
335
- })
336
- })
@@ -1,271 +0,0 @@
1
- import { RpcRequest, RpcResponse, Signature } from 'ox'
2
- import { Transaction as core_Transaction, TxEnvelopeTempo } from 'ox/tempo'
3
- import type { Address, Chain, Client, Transport } from 'viem'
4
- import { createClient, http } from 'viem'
5
- import type { LocalAccount } from 'viem/accounts'
6
- import { signTransaction } from 'viem/actions'
7
- import { tempo, tempoModerato } from 'viem/chains'
8
- import { Transaction } from 'viem/tempo'
9
-
10
- import { type Handler, from } from '../../Handler.js'
11
- import * as Utils from './utils.js'
12
-
13
- /**
14
- * Instantiates a fee payer service request handler that can be used to
15
- * sponsor the fee for user transactions.
16
- *
17
- * @example
18
- * ```ts
19
- * import { privateKeyToAccount } from 'viem/accounts'
20
- * import { Handler } from 'accounts/server'
21
- *
22
- * const handler = Handler.feePayer({
23
- * account: privateKeyToAccount('0x...'),
24
- * })
25
- *
26
- * // Plug handler into your server framework of choice:
27
- * createServer(handler.listener) // Node.js
28
- * Bun.serve(handler) // Bun
29
- * Deno.serve(handler) // Deno
30
- * app.use(handler.listener) // Express
31
- * app.all('*', c => handler.fetch(c.req.raw)) // Hono
32
- * export const GET = handler.fetch // Next.js
33
- * export const POST = handler.fetch // Next.js
34
- * ```
35
- *
36
- * @param options - Options.
37
- * @returns Request handler.
38
- */
39
- export function feePayer(options: feePayer.Options): Handler {
40
- const {
41
- account,
42
- chains = [tempo, tempoModerato],
43
- name,
44
- onRequest,
45
- path = '/',
46
- validate,
47
- transports = {},
48
- url,
49
- ...rest
50
- } = options
51
-
52
- const clients = new Map<number, Client>()
53
- for (const chain of chains) {
54
- const transport = transports[chain.id] ?? http()
55
- clients.set(chain.id, createClient({ chain, transport }))
56
- }
57
-
58
- function getClient(chainId?: number): Client {
59
- if (chainId) {
60
- const client = clients.get(chainId)
61
- if (!client) throw new Error(`Chain ${chainId} not configured`)
62
- return client
63
- }
64
- return clients.get(chains[0]!.id)!
65
- }
66
-
67
- const sponsor = {
68
- address: account.address,
69
- ...(name ? { name } : {}),
70
- ...(url ? { url } : {}),
71
- }
72
-
73
- const router = from(rest)
74
-
75
- router.post(path, async (c) => {
76
- const request = RpcRequest.from((await c.req.raw.json()) as any)
77
-
78
- try {
79
- await onRequest?.(request)
80
-
81
- const method = request.method as string
82
- if (
83
- method !== 'eth_fillTransaction' &&
84
- method !== 'eth_signRawTransaction' &&
85
- method !== 'eth_sendRawTransaction' &&
86
- method !== 'eth_sendRawTransactionSync'
87
- )
88
- return Response.json(
89
- RpcResponse.from(
90
- {
91
- error: new RpcResponse.MethodNotSupportedError({
92
- message: `Method not supported: ${request.method}`,
93
- }),
94
- },
95
- { request },
96
- ),
97
- )
98
-
99
- if (method === 'eth_fillTransaction') {
100
- const [parameters] = Utils.parseParams.parse(request.params) as [Record<string, unknown>]
101
- const chainId = Utils.resolveChainId(parameters.chainId)
102
- const client = getClient(chainId)
103
- const normalized = Utils.normalizeFillTransactionRequest(parameters)
104
- const sender =
105
- typeof parameters.from === 'string' ? (parameters.from as Address) : undefined
106
-
107
- let transaction = await (async () => {
108
- if (isPreparedFeePayerTransaction(parameters))
109
- return Utils.normalizeTempoTransaction(parameters)
110
-
111
- const fillRequest = Utils.formatFillTransactionRequest(client, {
112
- ...normalized,
113
- ...(typeof chainId !== 'undefined' ? { chainId } : {}),
114
- feePayer: true,
115
- })
116
- const result = (await client.request({
117
- method: 'eth_fillTransaction' as never,
118
- params: [fillRequest],
119
- })) as { tx?: Record<string, unknown> | undefined }
120
- return Utils.normalizeTempoTransaction(result.tx)
121
- })()
122
-
123
- if (
124
- validate &&
125
- // @ts-expect-error - TODO: Convert to `TransactionRequest` properly.
126
- !(await validate({
127
- ...transaction,
128
- from: sender,
129
- } as Transaction.TransactionRequest))
130
- ) {
131
- // Re-fill without feePayer so gas/nonce are correct for self-payment.
132
- const fillRequest = Utils.formatFillTransactionRequest(client, {
133
- ...normalized,
134
- ...(typeof chainId !== 'undefined' ? { chainId } : {}),
135
- })
136
- const result = (await client.request({
137
- method: 'eth_fillTransaction' as never,
138
- params: [fillRequest],
139
- })) as { tx?: Record<string, unknown> | undefined }
140
- transaction = Utils.normalizeTempoTransaction(result.tx)
141
-
142
- return Utils.rpcResult(request, {
143
- tx: core_Transaction.toRpc(transaction as core_Transaction.Transaction),
144
- })
145
- }
146
-
147
- const signed = await sign({ account, transaction, sender })
148
-
149
- return Utils.rpcResult(request, {
150
- sponsor,
151
- tx: core_Transaction.toRpc(signed as core_Transaction.Transaction),
152
- })
153
- }
154
-
155
- const serialized = request.params?.[0] as `0x76${string}` | undefined
156
-
157
- if (!serialized?.startsWith('0x76') && !serialized?.startsWith('0x78'))
158
- throw new RpcResponse.InvalidParamsError({
159
- message: 'Only Tempo (0x76/0x78) transactions are supported.',
160
- })
161
-
162
- const transaction = Transaction.deserialize(serialized)
163
-
164
- if (!transaction.signature || !transaction.from)
165
- throw new RpcResponse.InvalidParamsError({
166
- message: 'Transaction must be signed by the sender before fee payer signing.',
167
- })
168
-
169
- if (validate && !(await validate(transaction as Transaction.TransactionRequest)))
170
- throw new RpcResponse.InvalidParamsError({
171
- message: 'Sponsorship rejected.',
172
- })
173
-
174
- const client = getClient(transaction.chainId)
175
- const serializedTransaction = toSerializedTransaction(
176
- await signTransaction(client, {
177
- ...transaction,
178
- account,
179
- feePayer: account,
180
- } as never),
181
- )
182
-
183
- if (method === 'eth_signRawTransaction')
184
- return Response.json(RpcResponse.from({ result: serializedTransaction }, { request }))
185
-
186
- const result = await client.request({
187
- method: method as never,
188
- params: [serializedTransaction],
189
- })
190
-
191
- return Response.json(RpcResponse.from({ result }, { request }))
192
- } catch (error) {
193
- return Utils.rpcError(request, error)
194
- }
195
- })
196
-
197
- return router
198
- }
199
-
200
- export declare namespace feePayer {
201
- type Options = from.Options & {
202
- /** Account to use as the fee payer. */
203
- account: LocalAccount
204
- /**
205
- * Supported chains. The handler resolves the client based on the
206
- * `chainId` in the incoming transaction.
207
- * @default [tempo, tempoModerato]
208
- */
209
- chains?: readonly [Chain, ...Chain[]] | undefined
210
- /** Function to call before handling the request. */
211
- onRequest?: (request: RpcRequest.RpcRequest) => Promise<void>
212
- /** Path to use for the handler. */
213
- path?: string | undefined
214
- /**
215
- * Validates whether to sponsor the transaction. When omitted, all
216
- * transactions are sponsored. Return `false` to reject sponsorship.
217
- */
218
- validate?: ((request: Transaction.TransactionRequest) => boolean | Promise<boolean>) | undefined
219
- /** Sponsor display name returned from `eth_fillTransaction`. */
220
- name?: string | undefined
221
- /** Transports keyed by chain ID. Defaults to `http()` for each chain. */
222
- transports?: Record<number, Transport> | undefined
223
- /** Sponsor URL returned from `eth_fillTransaction`. */
224
- url?: string | undefined
225
- }
226
- }
227
-
228
- /** Signs a filled transaction as the fee payer. */
229
- export async function sign(options: {
230
- account: LocalAccount
231
- transaction: Record<string, unknown>
232
- sender?: `0x${string}` | undefined
233
- }) {
234
- const { account, transaction, sender } = options
235
- const from = (transaction.from as `0x${string}` | undefined) ?? sender
236
- const { signature: _, ...withoutSenderSig } = transaction
237
- const prepared = { ...withoutSenderSig, from }
238
-
239
- if (!prepared.from)
240
- throw new RpcResponse.InvalidParamsError({
241
- message: 'Transaction sender must be provided before fee payer signing.',
242
- })
243
- if (!account.sign) throw new Error('Fee payer account cannot sign transactions.')
244
-
245
- const feePayerSignature = Signature.from(
246
- await account.sign({
247
- hash: TxEnvelopeTempo.getFeePayerSignPayload(TxEnvelopeTempo.from(prepared as never), {
248
- sender: prepared.from,
249
- }),
250
- }),
251
- )
252
-
253
- return { ...prepared, feePayerSignature }
254
- }
255
-
256
- function isPreparedFeePayerTransaction(value: Record<string, unknown>) {
257
- return (
258
- typeof value.from === 'string' &&
259
- typeof Utils.resolveChainId(value.chainId) === 'number' &&
260
- typeof value.gas !== 'undefined' &&
261
- typeof value.nonce !== 'undefined' &&
262
- (typeof value.maxFeePerGas !== 'undefined' || typeof value.gasPrice !== 'undefined')
263
- )
264
- }
265
-
266
- function toSerializedTransaction(value: unknown) {
267
- if (typeof value === 'string') return value
268
- if (value && typeof value === 'object' && 'raw' in value && typeof value.raw === 'string')
269
- return value.raw
270
- throw new Error('Expected a serialized transaction result.')
271
- }