UNPKG

viem

Version:

TypeScript Interface for Ethereum

193 lines • 9.57 kB
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 { 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, } from '../clients/transports/createTransport.js'; import * as Transaction from './Transaction.js'; /** * 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, relayTransport, parameters) { 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); if (method === 'eth_sendRawTransactionSync' || method === 'eth_sendRawTransaction') { const serialized = params[0]; 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); // 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); } } } return (await transport_default.request({ method, params }, options)); }, type: withRelay.type, }); }; } /** @deprecated Use `withRelay` instead. */ export function withFeePayer(defaultTransport, relayTransport, parameters) { 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?.[0]; if (request && typeof request === 'object' && 'feePayer' in request && request.feePayer === true) return transport_relay.request({ method, params }, options); } if (method === 'eth_sendRawTransactionSync' || method === 'eth_sendRawTransaction') { const serialized = params[0]; 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); // 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); } } } return (await transport_default.request({ method, params }, options)); }, type: withFeePayer.type, }); }; } /** * 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, options) { const { account } = options; const sendCallsMagic = Hash.keccak256(Hex.fromString('TEMPO_5792')); return (options) => { const t = transport(options); const chain = options.chain; return { ...t, async request(args) { 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); }, }; }; } //# sourceMappingURL=Transport.js.map