Handler.relay
Creates a server handler that proxies certain RPC requests (like eth_fillTransaction) with wallet-aware enrichment — fee token resolution, simulation-based balance diffs, conditional sponsorship, and automatic AMM resolution for insufficient balances.
Usage
import { } from 'accounts/server'
const = .()Then plug handler into your server framework of choice:
createServer(.) // Node.js
Bun.serve() // Bun
Deno.serve() // Deno
app.all('*', => .(.request)) // Elysia
app.use(.) // Express
app.use( => .(.req.raw)) // Hono
export const = . // Next.js
export const = . // Next.jsFeatures
Sponsor transactions with a fee payer account and optional validation
Auto-swap via the Stablecoin DEX when a user has insufficient balance
Automatically resolve the user's optimal fee token
Simulate transactions and return per-account token balance diffs
Derive fee estimates as raw and human-readable values
Exact deficit amount and token metadata when funds are insufficient
Sponsorship
Configure a feePayer to sponsor transactions. The relay signs feePayerSignature on the filled transaction and returns sponsor details in the response metadata. Use a validate callback for conditional sponsorship — rejected transactions are re-filled for self-payment.
const = .({
: {
: ('0x...'),
: 'My App',
: 'https://myapp.com',
// Optional — validate sponsorship approval.
: () => . !== ,
},
})const = await .({
: 'eth_fillTransaction',
: [{
: ,
: [{
: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
// transfer 100 USDC.e
: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
}],
}],
})
..
// true
..
// {
// address: '0x1234567890abcdef1234567890abcdef12345678',
// name: 'My App',
// url: 'https://myapp.com',
// }Auto Swap
When a user has insufficient balance of a required token, the relay automatically injects swap calls (approve + buy) via the Stablecoin DEX. Swap details are reported in capabilities.autoSwap, and swap-related balance diffs are excluded from capabilities.balanceDiffs. Enabled by features: 'all', configurable via autoSwap.
const = await .({
: 'eth_fillTransaction',
: [{
: ,
: [{
: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
// transfer 100 USDC.e
: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
}],
}],
})
..
// {
// maxIn: {
// decimals: 6,
// formatted: '105.000000',
// name: 'AlphaUSD',
// symbol: 'AlphaUSD',
// token: '0x20c0000000000000000000000000000000000001',
// value: '0x6422c40',
// },
// minOut: {
// decimals: 6,
// formatted: '100.000000',
// name: 'USDC.e',
// symbol: 'USDC.e',
// token: '0x20c000000000000000000000b9537d11c60e8b50',
// value: '0x5f5e100',
// },
// slippage: 0.05,
// }import { Handler } from 'accounts/server'
const handler = Handler.relay({
autoSwap: { slippage: 0.05 }, // 5% (default)
})Best Fee Tokens
Picks the user's optimal feeToken automatically:
- On-chain preference via
fee.getUserToken(if set and has balance) - Highest-balance token from the
resolveTokenslist - Validator fallback
const = await .({
: 'eth_fillTransaction',
: [{
: ,
: [{
: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
// transfer 100 USDC.e
: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
}],
}],
})
..
// {
// amount: '0x6b86',
// decimals: 6,
// formatted: '0.027526',
// symbol: 'USDC.e',
// }Balance Diffs
Simulates the transaction and returns per-account token balance diffs in capabilities.balanceDiffs.
const = await .({
: 'eth_fillTransaction',
: [{
: ,
: [{
: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
// transfer 100 USDC.e
: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
}],
}],
})
..
// {
// '0x1234567890abcdef1234567890abcdef12345678': [{
// address: '0x20c000000000000000000000b9537d11c60e8b50',
// decimals: 6,
// direction: 'outgoing',
// formatted: '100.000000',
// name: 'USDC.e',
// recipients: ['0xcafebabecafebabecafebabecafebabecafebabe'],
// symbol: 'USDC.e',
// value: '0x5f5e100',
// }],
// }// Balance diffs are included by default — no extra config needed.
const = .()Fee Derivation
Derives fee estimates from the filled transaction and returns them as both raw and human-readable values, so the UI can display costs without additional computation.
const = await .({
: 'eth_fillTransaction',
: [{
: ,
: [{
: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
// transfer 100 USDC.e
: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
}],
}],
})
..
// {
// amount: '0x6b86',
// decimals: 6,
// formatted: '0.027526',
// symbol: 'pathUSD',
// }// Formatted fees are included by default — no extra config needed.
const = .()Require Funds
For insufficient balance errors, the relay also returns a capabilities.requireFunds object with the exact deficit amount and token metadata — enabling UIs to prompt the user to fund their account.
const = await .({
: 'eth_fillTransaction',
: [{
: ,
: [{
: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
// transfer 100 USDC.e (but account has 40)
: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
}],
}],
})
..
// {
// amount: '0x3938700',
// decimals: 6,
// formatted: '60.000000',
// token: '0x20c000000000000000000000b9537d11c60e8b50',
// symbol: 'USDC.e',
// }
..
// {
// '0x1234567890abcdef1234567890abcdef12345678': [{
// address: '0x20c000000000000000000000b9537d11c60e8b50',
// decimals: 6,
// direction: 'outgoing',
// formatted: '100.000000',
// name: 'USDC.e',
// recipients: ['0xcafebabecafebabecafebabecafebabecafebabe'],
// symbol: 'USDC.e',
// value: '0x5f5e100',
// }],
// }Enabling Features
By default, only a minimum set of features are enabled, given on what options you pass to Handler.relay() (e.g. feePayer, autoSwap, etc).
Set features: 'all' to enable all features by default such as: fee token resolution, auto-swap, and simulation (balance diffs + fee breakdown). This will come at the cost of slightly increased network latency.
import { } from 'viem/accounts'
import { } from 'accounts/server'
const = .({
: 'all',
})Parameters
autoSwap
- Type:
false | { slippage?: number }
AMM swap options for automatic insufficient balance resolution. When a user doesn't hold enough of a token, the relay auto-swaps from their fee token via the Stablecoin DEX. Set to false to disable even when features: 'all' is set.
import { } from 'accounts/server'
const = .({
: { : 0.02 }, // 2% slippage
})autoSwap.slippage
- Type:
number - Default:
0.05(5%)
Slippage tolerance for AMM swaps. The relay sets maxAmountIn = deficit + deficit * slippage.
chains
- Type:
readonly [Chain, ...Chain[]] - Default:
[tempo, tempoModerato]
Supported chains. The handler resolves the client based on the chainId in the incoming transaction.
import { } from 'viem/chains'
import { } from 'accounts/server'
const = .({
: [],
})features
- Type:
'all' - Optional
Controls which relay features are enabled. By default, only fee payer sponsorship is active.
'all': enables all features (fee token resolution, auto-swap, balance diffs, fee breakdown, etc).undefined(default): enables only features that are configured via options (e.g.feePayer,autoSwap, etc).
import { } from 'accounts/server'
const = .({
: 'all',
})feePayer
- Type:
object - Optional
Fee payer configuration. When provided, the relay will sign feePayerSignature on the filled transaction.
import { } from 'viem/accounts'
import { } from 'accounts/server'
const = .({
: {
: ('0x...'),
: 'My App',
: 'https://myapp.com',
},
})feePayer.account
- Type:
LocalAccount - Required
The account to use as the fee payer.
feePayer.name
- Type:
string - Optional
Sponsor display name returned in the response metadata.
feePayer.url
- Type:
string - Optional
Sponsor URL returned in the response metadata.
feePayer.validate
- Type:
(request: TransactionRequest) => boolean | Promise<boolean> - Optional
Validates whether to sponsor a transaction. When omitted, all transactions are sponsored. Return false to reject sponsorship — the relay will re-fill without feePayer so gas/nonce are correct for self-payment.
import { } from 'viem/accounts'
import { } from 'accounts/server'
const = '0x...'
const = .({
: {
: ('0x...'),
: () => . !== ,
},
})onRequest
- Type:
(request: RpcRequest) => Promise<void> - Optional
Callback called before processing each request. Useful for logging, rate limiting, or custom validation.
import { } from 'accounts/server'
const = .({
: async () => {
.('Processing request:', .)
},
})path
- Type:
string - Default:
'/'
Path where the handler listens for requests.
import { } from 'accounts/server'
const = .({
: '/relay',
})resolveTokens
- Type:
(chainId: number) => readonly Token[] | Promise<readonly Token[]> - Default: Fetches
https://tokenlist.tempo.xyz/list/:chainId
Resolves the list of tokens to check balances for during fee token resolution. The relay checks balanceOf for each token and picks the one with the highest balance.
import { } from 'accounts/server'
const = .({
: () => [
{
: '0x20c0000000000000000000000000000000000000',
: 6,
: 'pathUSD',
: 'pathUSD',
},
{
: '0x20c000000000000000000000b9537d11c60e8b50',
: 6,
: 'USDC.e',
: 'USDC.e',
},
],
})transports
- Type:
Record<number, Transport> - Default:
http()for each chain
Transports keyed by chain ID.
import { } from 'viem'
import { } from 'viem/chains'
import { } from 'accounts/server'
const = .({
: {
[.]: ('https://rpc.tempo.xyz'),
},
})The relay enriches the standard eth_fillTransaction response with a capabilities object:
type Response = {
/** Wallet-specific capabilities computed during fill. */
capabilities: {
/** Per-account balance diffs from simulation (swap-related diffs excluded). */
balanceDiffs?: {
[account: Address]: BalanceDiff[]
}
/** Fee estimate for the transaction. */
fee: {
/** Raw fee amount in token units (hex-encoded). */
amount: Hex
/** Token decimals (e.g. 6). */
decimals: number
/** Human-readable fee (e.g. "0.028022"). */
formatted: string
/** Token symbol (e.g. "AlphaUSD"). */
symbol: string
} | null
/** AMM swap injected to cover an insufficient balance. */
autoSwap?: {
/** Max input amount with slippage. */
maxIn: SwapAmount
/** Deficit amount that triggered the swap. */
minOut: SwapAmount
/** Slippage tolerance (e.g. 0.05 = 5%). */
slippage: number
}
/** Structured error details when the fill fails (e.g. InsufficientBalance). */
error?: {
/** ABI item that caused the error. */
abiItem: AbiItem
/** Data that caused the error. */
data: Hex
/** Revert error name (e.g. "InsufficientBalance"). */
errorName: string
/** Human-readable error message. */
message: string
}
/** Funding requirement when InsufficientBalance is encountered. */
requireFunds?: {
/** Deficit amount in token units (hex-encoded). */
amount: Hex
/** Token decimals (e.g. 6). */
decimals: number
/** Human-readable deficit (e.g. "100.000000"). */
formatted: string
/** Token address. */
token: Address
/** Token symbol (e.g. "USDC.e"). */
symbol: string
}
/** Sponsor details (when sponsored). */
sponsor?: { address: Address; name: string; url: string }
/** Whether the transaction is sponsored by a fee payer. */
sponsored: boolean
}
/** Fully filled transaction. */
tx: {
// ...filled tx fields
/** Resolved fee token used for this transaction. */
feeToken: Address
}
}
type BalanceDiff = {
/** Token address. */
address: Address
/** Token decimals (e.g. 6). */
decimals: number
/** Direction relative to the user. */
direction: 'incoming' | 'outgoing'
/** Human-readable formatted amount (e.g. "100.00"). */
formatted: string
/** Token name (e.g. "USDC.e"). */
name: string
/** Addresses receiving this asset. */
recipients: readonly Address[]
/** Token symbol (e.g. "USDC.e"). */
symbol: string
/** Token amount (hex-encoded). */
value: Hex
}
type SwapAmount = {
/** Token decimals. */
decimals: number
/** Human-readable formatted amount. */
formatted: string
/** Token name (e.g. "AlphaUSD"). */
name: string
/** Token symbol (e.g. "AlphaUSD"). */
symbol: string
/** Token address. */
token: Address
/** Amount (hex-encoded). */
value: Hex
}Was this helpful?