forked from wevm/viem
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTransport.ts
More file actions
413 lines (363 loc) · 13 KB
/
Copy pathTransport.ts
File metadata and controls
413 lines (363 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
import * as Address from 'ox/Address'
import * as Hash from 'ox/Hash'
import * as Hex from 'ox/Hex'
import * as Provider from 'ox/Provider'
import * as RpcRequest from 'ox/RpcRequest'
import type { LocalAccount } from '../accounts/types.js'
import { getTransactionReceipt } from '../actions/public/getTransactionReceipt.js'
import { sendTransaction } from '../actions/wallet/sendTransaction.js'
import { sendTransactionSync } from '../actions/wallet/sendTransactionSync.js'
import { createClient } from '../clients/createClient.js'
import {
createTransport,
type Transport,
} from '../clients/transports/createTransport.js'
import {
type HttpTransport,
type HttpTransportConfig,
http as http_,
} from '../clients/transports/http.js'
import type { Chain } from '../types/chain.js'
import type { ChainConfig } from './chainConfig.js'
import type { Storage } from './Storage.js'
import * as Storage_ from './Storage.js'
import * as Transaction from './Transaction.js'
export type HttpConfig = Omit<
HttpTransportConfig,
'batch' | 'raw' | 'rpcSchema'
> & {
/** Storage for reading Zone authorization tokens. Defaults to sessionStorage (web) or memory (server). */
storage?: Storage | undefined
}
/**
* Creates an HTTP transport with support for Zone authentication tokens.
*
* Reads the authorization token from Storage and injects the
* `X-Authorization-Token` header on every request.
*
* @example
* ```ts
* import { createPublicClient } from 'viem'
* import { http, Zone } from 'viem/tempo'
*
* const client = createPublicClient({
* chain: Zone.a,
* transport: http(),
* })
* ```
*/
export function http(
url?: string | undefined,
config: HttpConfig = {},
): HttpTransport {
const { storage: storage_, onFetchRequest, ...rest } = config
const storage = storage_ ?? Storage_.defaultStorage()
return (config) =>
http_(url, {
...rest,
async onFetchRequest(request, init) {
const next = (await onFetchRequest?.(request, init)) ?? init
const headers = new Headers(next.headers)
const chainId = config.chain?.id
if (chainId) {
const token = (await storage.getItem(`auth:token:${chainId}`)) ?? null
if (token) headers.set('X-Authorization-Token', token)
}
return { ...next, headers }
},
})(config)
}
type RelayProxyParameters = {
/** Policy for how the relay should handle sponsored transactions. Defaults to `'sign-only'`. */
policy?: 'sign-only' | 'sign-and-broadcast' | undefined
}
export type FeePayer = Transport<typeof withFeePayer.type>
export type RemoteFeePayer = Transport<typeof withRemoteFeePayer.type>
export type Relay = Transport<typeof withRelay.type>
/**
* Creates a remote fee-payer transport that routes sponsored
* `eth_fillTransaction` requests to a fee-payer service.
*
* All other requests, including transaction broadcast, use the default
* transport. The fee-payer service must return a sponsored transaction from
* `eth_fillTransaction` without requiring a later signing or broadcast request.
*
* @param defaultTransport - The default transport to use.
* @param remoteFeePayerTransport - The remote fee-payer transport to use for sponsored fills.
* @returns A remote fee-payer transport.
*/
export function withRemoteFeePayer(
defaultTransport: Transport,
remoteFeePayerTransport: Transport,
): withRemoteFeePayer.ReturnValue {
return (config) => {
const transport_default = defaultTransport(config)
const transport_remoteFeePayer = remoteFeePayerTransport(config)
return createTransport({
key: withRemoteFeePayer.type,
name: 'Remote Fee Payer Proxy',
async request({ method, params }, options) {
if (method === 'eth_fillTransaction') {
const request = (params as readonly unknown[] | undefined)?.[0]
if (
request &&
typeof request === 'object' &&
'feePayer' in request &&
request.feePayer === true
)
return transport_remoteFeePayer.request(
{ method, params },
options,
) as never
}
return (await transport_default.request(
{ method, params },
options,
)) as never
},
type: withRemoteFeePayer.type,
})
}
}
export declare namespace withRemoteFeePayer {
export const type = 'remoteFeePayer'
export type ReturnValue = RemoteFeePayer
}
/**
* Creates a relay transport that routes requests between
* the default transport or the relay transport.
*
* All `eth_fillTransaction` requests are sent to the relay with the request's
* `feePayer` value preserved so the relay can decide whether to sponsor the transaction.
*
* The policy parameter controls how the relay handles sponsored transactions:
* - `'sign-only'`: Relay co-signs the transaction and returns it to the client transport, which then broadcasts it via the default transport
* - `'sign-and-broadcast'`: Relay co-signs and broadcasts the transaction directly
*
* @param defaultTransport - The default transport to use.
* @param relayTransport - The relay transport to use.
* @param parameters - Configuration parameters.
* @returns A relay transport.
*/
export function withRelay(
defaultTransport: Transport,
relayTransport: Transport,
parameters?: withRelay.Parameters,
): withRelay.ReturnValue {
const { policy = 'sign-only' } = parameters ?? {}
return (config) => {
const transport_default = defaultTransport(config)
const transport_relay = relayTransport(config)
return createTransport({
key: withRelay.type,
name: 'Relay Proxy',
async request({ method, params }, options) {
if (method === 'eth_fillTransaction')
return transport_relay.request({ method, params }, options) as never
if (
method === 'eth_sendRawTransactionSync' ||
method === 'eth_sendRawTransaction'
) {
const serialized = (params as any)[0] as `0x76${string}`
const transaction = Transaction.deserialize(serialized)
// Serialized Tempo envelopes encode `feePayer: true` as a missing fee payer
// signature until the relay co-signs the transaction.
if (transaction.feePayerSignature === null) {
// For 'sign-and-broadcast', relay signs and broadcasts
if (policy === 'sign-and-broadcast')
return transport_relay.request(
{ method, params },
options,
) as never
// For 'sign-only', request signature from relay using eth_signRawTransaction
{
// Request signature from relay using eth_signRawTransaction
const signedTransaction = await transport_relay.request(
{
method: 'eth_signRawTransaction',
params: [serialized],
},
options,
)
// Broadcast the signed transaction via the default transport
return transport_default.request(
{ method, params: [signedTransaction] },
options,
) as never
}
}
}
return (await transport_default.request(
{ method, params },
options,
)) as never
},
type: withRelay.type,
})
}
}
export declare namespace withRelay {
export const type = 'relay'
export type Parameters = RelayProxyParameters
export type ReturnValue = Relay
}
/** @deprecated Use `withRelay` or `withRemoteFeePayer` instead. */
export function withFeePayer(
defaultTransport: Transport,
relayTransport: Transport,
parameters?: withFeePayer.Parameters,
): withFeePayer.ReturnValue {
const { policy = 'sign-only' } = parameters ?? {}
return (config) => {
const transport_default = defaultTransport(config)
const transport_relay = relayTransport(config)
return createTransport({
key: withFeePayer.type,
name: 'Relay Proxy',
async request({ method, params }, options) {
if (method === 'eth_fillTransaction') {
const request = (params as readonly unknown[] | undefined)?.[0]
if (
request &&
typeof request === 'object' &&
'feePayer' in request &&
request.feePayer === true
)
return transport_relay.request({ method, params }, options) as never
}
if (
method === 'eth_sendRawTransactionSync' ||
method === 'eth_sendRawTransaction'
) {
const serialized = (params as any)[0] as `0x76${string}`
const transaction = Transaction.deserialize(serialized)
// Serialized Tempo envelopes encode `feePayer: true` as a missing fee payer
// signature until the relay co-signs the transaction.
if (transaction.feePayerSignature === null) {
// For 'sign-and-broadcast', relay signs and broadcasts
if (policy === 'sign-and-broadcast')
return transport_relay.request(
{ method, params },
options,
) as never
// For 'sign-only', request signature from relay using eth_signRawTransaction
{
// Request signature from relay using eth_signRawTransaction
const signedTransaction = await transport_relay.request(
{
method: 'eth_signRawTransaction',
params: [serialized],
},
options,
)
// Broadcast the signed transaction via the default transport
return transport_default.request(
{ method, params: [signedTransaction] },
options,
) as never
}
}
}
return (await transport_default.request(
{ method, params },
options,
)) as never
},
type: withFeePayer.type,
})
}
}
export declare namespace withFeePayer {
export const type = 'feePayer'
export type Parameters = {
/** Policy for how the fee payer should handle transactions. Defaults to `'sign-only'`. */
policy?: 'sign-only' | 'sign-and-broadcast' | undefined
}
export type ReturnValue = FeePayer
}
/**
* Creates a transport that instruments a compatibility layer for
* `wallet_` RPC actions (`sendCalls`, `getCallsStatus`, etc).
*
* @param transport - Transport to wrap.
* @returns Transport.
*/
export function walletNamespaceCompat(
transport: Transport,
options: walletNamespaceCompat.Parameters,
): Transport {
const { account } = options
const sendCallsMagic = Hash.keccak256(Hex.fromString('TEMPO_5792'))
return (options) => {
const t = transport(options)
const chain = options.chain as Chain & ChainConfig
return {
...t,
async request(args: never) {
const request = RpcRequest.from(args)
const client = createClient({
chain,
transport,
})
if (request.method === 'wallet_sendCalls') {
const params = request.params[0] ?? {}
const { capabilities, chainId, from } = params
const { sync, ...properties } = capabilities ?? {}
if (!chainId) throw new Provider.UnsupportedChainIdError()
if (Number(chainId) !== client.chain.id)
throw new Provider.UnsupportedChainIdError()
if (from && !Address.isEqual(from, account.address))
throw new Provider.DisconnectedError()
const calls = (params.calls ?? []).map((call) => ({
to: call.to,
value: call.value ? BigInt(call.value) : undefined,
data: call.data,
}))
const hash = await (async () => {
if (!sync)
return sendTransaction(client, {
account,
...(properties ? properties : {}),
calls,
})
const { transactionHash } = await sendTransactionSync(client, {
account,
...(properties ? properties : {}),
calls,
})
return transactionHash
})()
const id = Hex.concat(hash, Hex.padLeft(chainId, 32), sendCallsMagic)
return {
capabilities: { sync },
id,
}
}
if (request.method === 'wallet_getCallsStatus') {
const [id] = request.params ?? []
if (!id) throw new Error('`id` not found')
if (!id.endsWith(sendCallsMagic.slice(2)))
throw new Error('`id` not supported')
Hex.assert(id)
const hash = Hex.slice(id, 0, 32)
const chainId = Hex.slice(id, 32, 64)
const receipt = await getTransactionReceipt(client, { hash })
return {
atomic: true,
chainId: Number(chainId),
id,
receipts: [receipt],
status: receipt.status === 'success' ? 200 : 500,
version: '2.0.0',
}
}
return t.request(args)
},
} as never
}
}
export declare namespace walletNamespaceCompat {
export type Parameters = {
account: LocalAccount
}
}