Relay.handleRequest
Overview
Relay.handleRequest composes plugins around a downstream
handler for the Tempo (Execution) RPC. Requests enter plugins in array order,
and responses return in reverse. Plugins can transform, answer, forward, or
reject requests.
The handler accepts an RPC method and parameters and returns the method's result. It is not an HTTP listener or a JSON-RPC envelope serializer: the application owns HTTP handling, request IDs, batching, authentication, and error serialization.
Recipes
Forward Requests to the Execution RPC
Omit plugins to forward requests directly. An empty plugin list has the same effect.
import { http } from 'viem'
import { Relay } from 'viem/tempo'
const rpc = http('https://rpc.tempo.xyz')({})
const handle = Relay.handleRequest(rpc.request)
const chainId = await handle({ method: 'eth_chainId' })
'0x1079'Handle a Method Locally
A plugin can answer a relay-specific method without sending it to the execution RPC. Other methods continue to the next handler.
import { http } from 'viem'
import { Relay } from 'viem/tempo'
const rpc = http('https://rpc.tempo.xyz')({})
const handle = Relay.handleRequest(rpc.request, {
plugins: [
{
async handleRequest(context, next) {
if (context.request.method === 'relay_status') return { ready: true }
await next()
},
},
],
})
const status = await handle({ method: 'relay_status' })
{ ready: true }Select a Chain per Request
Pass chainId when a route selects the chain. Middleware reads or updates
context.options; next() forwards the current request and options, preserving
chain selection, retries, and cancellation.
import { http } from 'viem'
import { Relay } from 'viem/tempo'
const mainnet = http('https://rpc.tempo.xyz')({})
const testnet = http('https://rpc.moderato.tempo.xyz')({})
const handle = Relay.handleRequest((request, options) => {
if (options?.chainId === 4217) return mainnet.request(request, options)
if (options?.chainId === 42431) return testnet.request(request, options)
throw new Error('Unsupported chain')
})
const chainId = await handle({ method: 'eth_chainId' }, { chainId: 4217 })
'0x1079'Relay.handleRequest
Creates an RPC request handler by composing relay plugins around a downstream handler.
Usage
import { http } from 'viem'
import { Relay } from 'viem/tempo'
const rpc = http('https://rpc.tempo.xyz')({})
const handle = Relay.handleRequest(rpc.request, {
plugins: [],
})Parameters
next
- Type:
Relay.handleRequest.Handler
The downstream handler. It receives a request and optional per-request options and returns a promise for the RPC result.
const handle = Relay.handleRequest(
rpc.request,
)options.plugins
- Type:
readonly Relay.Plugin[] - Default:
[]
Plugin handlers run in array order. Code after await next() runs in reverse
order. Post-fill hooks run after all middleware returns. Without plugins, the
downstream handler is returned directly.
const handle = Relay.handleRequest(rpc.request, {
plugins: [
{
async handleRequest(context, next) {
if (context.request.method === 'relay_status') return { ready: true }
await next()
},
},
],
})options.resolveTokens
- Type:
(chainId: number) => readonly Address[] | Promise<readonly Address[]> - Default: Bundled Tempo token addresses for the selected chain.
Supplies fee-token candidates shared by fee selection and sponsorship. Results are memoized within each request and chain. Implement cross-request caching inside the resolver when fetching a remote token list.
const handle = Relay.handleRequest(rpc.request, {
resolveTokens: () => [Addresses.pathUsd],
plugins: [Relay.feeToken()],
})Return Value
Relay.handleRequest.Handler
The composed handler accepts a method and optional readonly params. Its
per-request options extend EIP1193RequestOptions with an optional numeric
chainId, preserving retries, deduplication, and cancellation settings. It returns
a promise for the RPC result.
Ordinary RPC errors propagate through middleware. Transaction processing normalizes RPC errors and can return execution-error capabilities when requested.
Relay.Plugin
An object containing request middleware and optional post-fill hooks.
Usage
import { Relay } from 'viem/tempo'
const status: Relay.Plugin = {
async handleRequest(context, next) {
if (context.request.method === 'relay_status') return { ready: true }
await next()
// Read or replace context.result here.
},
}Request Context
context.request and context.options are forwarded by next(), which returns
Promise<void>. The downstream result is available as context.result. Return a
result directly to answer locally, or assign context.result after next().
Call next() at most once per middleware invocation.
context.client and context.getClient(chainId) issue requests through the
remaining middleware. context.resolveTokens(chainId) shares the configured
resolver across plugins, memoized per request and chain. context.getStore(store)
shares in-flight cache work within the request.
Post-Fill Hooks
afterFill(filled, context) runs after middleware selects the final transaction.
All hooks run concurrently against a deeply frozen snapshot. Each returns
{ capabilities }; duplicate capability keys returned by hooks are rejected.
Hooks may replace upstream capability fields, but cannot patch transaction fields.
signTransaction(filled, context) runs concurrently with afterFill and returns
an RPC fee-payer signature (r, s, and yParity) or undefined. Only one signing
hook may be configured. The relay removes the placeholder sender signature when
applying the fee-payer signature.
Hooks run once for the final successful fill, not for intermediate fills or error capabilities. Middleware sees the downstream result before these hooks run; the public relay handler returns the completed response.