<!--
Sitemap:
- [Tempo Accounts SDK - Getting Started](/docs/): Set up the Tempo Accounts SDK to create, manage, and interact with accounts on Tempo.
- [Deploying to Production](/docs/production): Things to consider before deploying your application with the Tempo Accounts SDK to production.
- [FAQ](/docs/faq): Frequently asked questions about the Tempo Accounts SDK.
- [Connect Accounts](/docs/guides/connect-accounts): Connect a Tempo account in your application.
- [Authentication](/docs/guides/authentication): Authenticate connected accounts against your own server with a signed SIWE challenge.
- [Identity](/docs/guides/identity): Request and verify identity claims about a connected account, starting with a verified email.
- [Transfers](/docs/guides/transfers): Send stablecoin transfers from a connected Tempo account, initiated either by the user or by your server.
- [Spend Permissions](/docs/guides/spend-permissions): Authorize spend limits, call scopes, and expiries so repeat transfers can be signed without a confirmation prompt.
- [React Native](/docs/guides/react-native): Set up Tempo Accounts in a React Native app.
- [Subscriptions](/docs/guides/subscriptions): Charge recurring payments from a connected Tempo account.
- [Fee Sponsorship](/docs/guides/fee-sponsorship): Sponsor transaction fees from a server-controlled policy.
- [Deposits](/docs/guides/deposits): Open the Tempo deposit flow from a connected account and let the user choose deposit details.
- [Swaps](/docs/guides/swaps): Open the Tempo swap flow from a connected account with optional pre-filled intent fields.
- [Theming](/docs/guides/theming): Match embedded account surfaces to your product.
- [CLI](/docs/guides/cli): Authorize and use Tempo accounts from command-line tools.
- [Adapters](/docs/adapters/): Choose the signing adapter for your Tempo Accounts SDK integration.
- [Tempo Wallet Adapter](/docs/adapters/tempo-wallet): Use Tempo Wallet as the hosted universal wallet adapter.
- [WebAuthn Adapter](/docs/adapters/webauthn): Use domain-bound passkeys as the account signing adapter.
- [Turnkey Adapter](/docs/adapters/turnkey): Use Turnkey-managed wallet accounts as the account signing adapter.
- [Privy Adapter](/docs/adapters/privy): Use Privy embedded wallets as the account signing adapter.
- [Private Key Adapter](/docs/adapters/private-key): Sign in-process with a `secp256k1` private key.
- [Custom Adapter](/docs/adapters/custom): Author your own adapter with the `Adapter.define` API.
- [Adapters](/docs/api/adapters): Pluggable adapters for the Tempo Accounts SDK Provider.
- [dialog](/docs/api/dialog): Adapter for the Tempo Wallet dialog, an embedded iframe or popup for account management.
- [local](/docs/api/local): Key-agnostic adapter for defining arbitrary account types and signing mechanisms.
- [mobileWebAuth](/docs/api/mobileWebAuth): Mobile web auth adapter for browser-session wallet requests.
- [postMessage](/docs/api/postMessage): Connect to wallet from anywhere on the web
- [privy](/docs/api/privy): React adapter backed by Privy sessions and embedded Ethereum wallets.
- [secp256k1](/docs/api/secp256k1): Adapter that signs in-process with a `secp256k1` private key.
- [turnkey](/docs/api/turnkey): Adapter backed by Turnkey client sessions.
- [webAuthn](/docs/api/webAuthn): Adapter for passkey-based accounts using WebAuthn registration and authentication.
- [Dialog](/docs/api/dialogs): Dialog modes for embedding the Tempo Wallet.
- [Dialog.iframe](/docs/api/dialog.iframe): Embed the Tempo Wallet auth UI in an iframe dialog element.
- [Dialog.popup](/docs/api/dialog.popup): Open the Tempo Wallet auth UI in a popup window.
- [Expiry](/docs/api/expiry): Utility functions for computing access key expiry timestamps.
- [Provider](/docs/api/provider): Create an EIP-1193 provider for managing accounts on Tempo.
- [Rpc](/docs/api/rpc): Per-method Zod schemas and shared building blocks for the Accounts JSON-RPC surface.
- [Schema](/docs/api/schema): Zod-based JSON-RPC schema definitions for the Accounts provider.
- [TrustedHosts](/docs/api/trustedHosts): Trusted host mappings and matching helpers for dialog adapters.
- [WebAuthnCeremony](/docs/api/webauthnceremony): Pluggable strategy for WebAuthn registration and authentication ceremonies.
- [WebAuthnCeremony.from](/docs/api/webauthnceremony.from): Create a WebAuthnCeremony from a custom implementation.
- [WebAuthnCeremony.server](/docs/api/webauthnceremony.server): Server-backed WebAuthn ceremony that delegates to a remote handler.
- [CLI](/docs/cli/provider): Create a Provider for CLI environments.
- [eth_accounts](/docs/rpc/eth_accounts): Get the addresses of the currently connected accounts.
- [eth_chainId](/docs/rpc/eth_chainId): Get the chain ID of the currently active chain.
- [eth_fillTransaction](/docs/rpc/eth_fillTransaction): Fills missing transaction fields and returns wallet-aware metadata.
- [eth_requestAccounts](/docs/rpc/eth_requestAccounts): Request access to user accounts, prompting the user to connect if needed.
- [eth_sendTransaction](/docs/rpc/eth_sendTransaction): Send a transaction from the connected account.
- [eth_sendTransactionSync](/docs/rpc/eth_sendTransactionSync): Send a transaction and wait for the receipt.
- [eth_signTransaction](/docs/rpc/eth_signTransaction): Sign a transaction without broadcasting it.
- [eth_signTypedData_v4](/docs/rpc/eth_signTypedData_v4): Sign EIP-712 typed structured data with the connected account.
- [personal_sign](/docs/rpc/personal_sign): Sign a message with the connected account.
- [wallet_authorizeAccessKey](/docs/rpc/wallet_authorizeAccessKey): Authorize an access key for delegated transaction signing.
- [wallet_connect](/docs/rpc/wallet_connect): Connect account(s) with optional capabilities like access key authorization.
- [wallet_deposit](/docs/rpc/wallet_deposit): Open the wallet deposit flow with optional pre-filled fields.
- [wallet_depositZone](/docs/rpc/wallet_depositZone): Open the wallet zone-deposit flow with optional pre-filled fields.
- [wallet_disconnect](/docs/rpc/wallet_disconnect): Disconnect the connected account(s).
- [wallet_getBalances](/docs/rpc/wallet_getBalances): Get token balances for an account.
- [wallet_getCallsStatus](/docs/rpc/wallet_getCallsStatus): Get the status of a batch of calls sent via wallet_sendCalls.
- [wallet_getCapabilities](/docs/rpc/wallet_getCapabilities): Get account capabilities for specified chains.
- [wallet_revokeAccessKey](/docs/rpc/wallet_revokeAccessKey): Revoke a previously authorized access key.
- [wallet_send](/docs/rpc/wallet_send): Open the wallet send-token flow with optional pre-filled fields.
- [wallet_sendCalls](/docs/rpc/wallet_sendCalls): Send a batch of calls from the connected account.
- [wallet_swap](/docs/rpc/wallet_swap): Open the wallet swap flow with optional pre-filled swap intent fields.
- [wallet_switchEthereumChain](/docs/rpc/wallet_switchEthereumChain): Switch the provider's active chain.
- [wallet_withdrawZone](/docs/rpc/wallet_withdrawZone): Open the wallet zone-withdraw flow with optional pre-filled fields.
- [Remote](/docs/api/remote): Bridge that runs inside the wallet's iframe/popup and serves RPC requests from the host SDK.
- [Remote.create](/docs/api/remote.create): Create a remote context bound to a Messenger and Provider.
- [Remote.useEnsureVisibility](/docs/api/remote.useEnsureVisibility): React hook that monitors iframe visibility and falls back to a popup when occluded.
- [Remote.useState](/docs/api/remote.useState): React hook to subscribe to a remote context's state store.
- [Remote.useTheme](/docs/api/remote.useTheme): React hook that applies theme overrides from URL search params and live messenger updates.
- [Remote.validateSearch](/docs/api/remote.validateSearch): Validate an RPC request payload from URL search params.
- [Tempo Accounts Server Handlers](/docs/server/): Configure server-side Tempo Accounts SDK handlers for relaying wallet RPC requests, composing backends, and managing WebAuthn ceremonies.
- [Handler.auth](/docs/server/handler.auth): Server handler that issues SIWE-based authentication challenges and sessions.
- [Handler.codeAuth](/docs/server/handler.codeAuth): Server handler for the device-code (PKCE) access-key bootstrap flow.
- [Handler.compose](/docs/server/handler.compose): Compose multiple server handlers into a single handler.
- [Handler.exchange](/docs/server/handler.exchange): Server handler that returns Stablecoin DEX quotes and ready-to-submit calls.
- [Handler.relay](/docs/server/handler.relay): Server handler that proxies certain RPC requests with wallet-aware enrichment.
- [Handler.webAuthn](/docs/server/handler.webAuthn): Server-side WebAuthn ceremony handler for registration and authentication.
- [hc](/docs/server/hc): Typed RPC client for handlers built with the Tempo Accounts SDK.
- [Identity.verify](/docs/server/identity.verify): Verify a wallet-issued identity token (verified email) against an issuer's JWKS.
- [Kv](/docs/server/kv): Key-value store adapters for server-side persistence.
- [Kv.cloudflare](/docs/server/kv.cloudflare): Kv adapter backed by a Cloudflare Workers KV namespace.
- [Kv.durableObject](/docs/server/kv.durableObject): Kv adapter backed by a Cloudflare Durable Object with atomic take and create.
- [Kv.from](/docs/server/kv.from): Wrap a custom Kv-shaped object so the SDK accepts it as a Kv.
- [Kv.memory](/docs/server/kv.memory): In-memory Kv adapter for tests and single-process deployments.
- [Keystore](/docs/api/keystore): Pluggable backends for key material.
- [Keystore.p256](/docs/api/keystore.p256): Pure-JS P-256 keystore.
- [Keystore.secp256k1](/docs/api/keystore.secp256k1): Pure-JS secp256k1 keystore.
- [Keystore.webCryptoP256](/docs/api/keystore.webCryptoP256): WebCrypto P-256 keystore.
- [Storage](/docs/api/storage): Pluggable storage adapters for persisting provider state.
- [Storage.combine](/docs/api/storage.combine): Combine multiple Storage adapters into one.
- [Storage.cookie](/docs/api/storage.cookie): Cookie-backed Storage adapter.
- [Storage.from](/docs/api/storage.from): Create a Storage adapter from a custom implementation.
- [Storage.idb](/docs/api/storage.idb): IndexedDB-backed Storage adapter.
- [Storage.localStorage](/docs/api/storage.localStorage): localStorage-backed Storage adapter.
- [Storage.memory](/docs/api/storage.memory): In-memory Storage adapter.
- [asyncStorage](/docs/api/storage.asyncStorage): React Native Storage adapter backed by AsyncStorage.
- [secureMmkv](/docs/api/storage.secureMmkv): Encrypted React Native Storage adapter backed by MMKV.
- [Tempo Accounts SDK](/index): The fastest way to add stablecoins to your application.
- [Secp256k1 Adapter](/docs/adapters/secp256k1): Sign in-process with a `secp256k1` private key.
- [Bring Your Auth](/docs/enterprise/bring-your-auth/): Connect enterprise auth and signing systems to Tempo accounts.
- [Hosted Universal Wallets](/docs/enterprise/hosted-universal-wallets): Stub for hosting a universal wallet on your own domain.
- [Handler.feePayer (Deprecated)](/docs/server/handler.feePayer): Deprecated — use Handler.relay with feePayer option instead.
- [tempoWallet](/docs/wagmi/tempoWallet): Wagmi connector for the Tempo Wallet dialog.
- [webAuthn](/docs/wagmi/webAuthn): Wagmi connector for passkey-based WebAuthn accounts.
- [AWS KMS](/docs/enterprise/bring-your-auth/aws-kms): Stub for integrating AWS KMS-backed signing with the Tempo Accounts SDK.
- [Custom Auth](/docs/enterprise/bring-your-auth/custom): Stub for first-party enterprise auth and signing integrations.
- [Privy](/docs/enterprise/bring-your-auth/privy): Enterprise notes for integrating Privy-backed auth with the Tempo Accounts SDK.
- [Turnkey](/docs/enterprise/bring-your-auth/turnkey): Stub for integrating Turnkey-backed signing with the Tempo Accounts SDK.
-->

# `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

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.relay()
```

Then plug `handler` into your server framework of choice:

```ts twoslash
// @noErrors
import { Handler } from 'accounts/server'
const handler = Handler.relay()
// ---cut---
createServer(handler.listener)              // Node.js
Bun.serve({ fetch: handler.fetch })         // Bun
Deno.serve({ fetch: handler.fetch })        // Deno
app.all('*', c => handler.fetch(c.request)) // Elysia
app.use(handler.listener)                   // Express
app.use(c => handler.fetch(c.req.raw))      // Hono
export const GET = handler.fetch            // Next.js
export const POST = handler.fetch           // Next.js
```

## Features

<Cards>
  <Card description="Sponsor transactions with a fee payer account and optional validation" icon="lucide:hand-coins" title="Sponsorship" to="#sponsorship" />

  <Card description="Auto-swap via the Stablecoin DEX when a user has insufficient balance" icon="lucide:arrow-left-right" title="Auto Swap" to="#auto-swap" />

  <Card description="Automatically resolve the user's optimal fee token" icon="lucide:coins" title="Best Fee Tokens" to="#best-fee-tokens" />

  <Card description="Simulate transactions and return per-account token balance diffs" icon="lucide:arrow-down-up" title="Balance Diffs" to="#balance-diffs" />

  <Card description="Derive fee estimates as raw and human-readable values" icon="lucide:receipt" title="Fee Derivation" to="#fee-derivation" />

  <Card description="Exact deficit amount and token metadata when funds are insufficient" icon="lucide:wallet" title="Require Funds" to="#require-funds" />
</Cards>

### Sponsorship

Configure a [`feePayer`](#feepayer) to sponsor transactions. The relay signs `feePayerSignature` on the filled transaction and returns sponsor details in the response metadata. Use a [`validate`](#feepayervalidate) callback for conditional sponsorship — rejected transactions are re-filled for self-payment.

:::code-group

```ts twoslash [server.ts]
import { privateKeyToAccount } from 'viem/accounts'
import { Handler } from 'accounts/server'

const blockedAddress = '0x...'
// ---cut---
const handler = Handler.relay({
  feePayer: {
    account: privateKeyToAccount('0x...'),
    name: 'My App',
    url: 'https://myapp.com',
    // Optional — validate sponsorship approval.
    validate: (request) => request.from !== blockedAddress,
  },
})
```

```ts twoslash [client.ts]
// @noErrors
import { Provider } from 'accounts'
const provider = Provider.create()
const [account] = await provider.request({ method: 'eth_accounts' })
// ---cut---
const result = await provider.request({
  method: 'eth_fillTransaction',
  params: [{
    from: account,
    calls: [{
      to: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
      // transfer 100 USDC.e
      data: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
    }],
  }],
})

result.capabilities.sponsored
// true

result.capabilities.sponsor
// {
//   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](https://docs.tempo.xyz/guide/stablecoin-dex). Swap details are reported in `capabilities.autoSwap` (including the prepended `calls`), and swap-related balance diffs are excluded from `capabilities.balanceDiffs`. Auto-swap is enabled when either [`autoSwap`](#autoswap) is set to a truthy value OR [`features: 'all'`](#features) is set.

:::code-group

```ts twoslash [client.ts]
// @noErrors
import { Provider } from 'accounts'
const provider = Provider.create()
const [account] = await provider.request({ method: 'eth_accounts' })
// ---cut---
const result = await provider.request({
  method: 'eth_fillTransaction',
  params: [{
    from: account,
    calls: [{
      to: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
      // transfer 100 USDC.e
      data: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
    }],
  }],
})

result.capabilities.autoSwap
// {
//   calls: [
//     // Stablecoin DEX `approve(...)` call.
//     { to: '0x20c0...', data: '0x095ea7b3...' },
//     // Stablecoin DEX `buy(...)` call.
//     { to: '0x20c0...', data: '0x...' },
//   ],
//   maxIn: {
//     decimals: 6,
//     formatted: '105.000000',
//     name: 'pathUSD',
//     symbol: 'pathUSD',
//     token: '0x20c0000000000000000000000000000000000001',
//     value: '0x6422c40',
//   },
//   minOut: {
//     decimals: 6,
//     formatted: '100.000000',
//     name: 'USDC.e',
//     symbol: 'USDC.e',
//     token: '0x20c000000000000000000000b9537d11c60e8b50',
//     value: '0x5f5e100',
//   },
//   slippage: 0.05,
// }
```

```ts [server.ts]
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. Internally, `resolveFeeToken` walks the candidates in this order and returns the first match:

1. **Explicit `feeToken`** — if the request (or sponsor's `feePayer.feeToken`) supplies a `feeToken`, it is used as-is and the rest of the resolution is skipped.
2. **On-chain preference (with balance)** — `fee.getUserToken(account)` (cached via [`kv`](#kv) for ~60s under `fee.userToken:{chainId}:{account}`). If the user has set a preference, `resolveFeeToken` uses it when the user holds a balance — first by checking the [`resolveTokens`](#resolvetokens) list, then falling back to a direct on-chain `balanceOf` for the preferred token if it isn't in the list.
3. **Highest-balance token** — across the [`resolveTokens`](#resolvetokens) list (extended with any TIP20 token addressed by the transaction's calls so a user transferring `USDC.e` can pay gas in `USDC.e` even when the configured list defaults to `pathUSD`).
4. **Undefined** — when no candidate has a positive balance, `resolveFeeToken` returns `undefined` and the relay falls back to the chain's first configured fee token (`getTokens(chainId)[0]`).

```ts twoslash
// @noErrors
import { Provider } from 'accounts'
const provider = Provider.create()
const [account] = await provider.request({ method: 'eth_accounts' })
// ---cut---
const result = await provider.request({
  method: 'eth_fillTransaction',
  params: [{
    from: account,
    calls: [{
      to: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
      // transfer 100 USDC.e
      data: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
    }],
  }],
})

result.capabilities.fee
// {
//   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`.

:::code-group

```ts twoslash [client.ts]
// @noErrors
import { Provider } from 'accounts'
const provider = Provider.create()
const [account] = await provider.request({ method: 'eth_accounts' })
// ---cut---
const result = await provider.request({
  method: 'eth_fillTransaction',
  params: [{
    from: account,
    calls: [{
      to: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
      // transfer 100 USDC.e
      data: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
    }],
  }],
})

result.capabilities.balanceDiffs
// {
//   '0x1234567890abcdef1234567890abcdef12345678': [{
//     address: '0x20c000000000000000000000b9537d11c60e8b50',
//     decimals: 6,
//     direction: 'outgoing',
//     formatted: '100.000000',
//     name: 'USDC.e',
//     recipients: ['0xcafebabecafebabecafebabecafebabecafebabe'],
//     symbol: 'USDC.e',
//     value: '0x5f5e100',
//   }],
// }
```

```ts twoslash [server.ts]
import { Handler } from 'accounts/server'
// ---cut---
// Balance diffs are included by default — no extra config needed.
const handler = Handler.relay()
```

:::

### 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.

:::code-group

```ts twoslash [client.ts]
// @noErrors
import { Provider } from 'accounts'
const provider = Provider.create()
const [account] = await provider.request({ method: 'eth_accounts' })
// ---cut---
const result = await provider.request({
  method: 'eth_fillTransaction',
  params: [{
    from: account,
    calls: [{
      to: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
      // transfer 100 USDC.e
      data: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
    }],
  }],
})

result.capabilities.fee
// {
//   amount: '0x6b86',
//   decimals: 6,
//   formatted: '0.027526',
//   symbol: 'USDC.e',
// }
```

```ts twoslash [server.ts]
import { Handler } from 'accounts/server'
// ---cut---
// Formatted fees are included by default — no extra config needed.
const handler = Handler.relay()
```

:::

### 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.

```ts twoslash
// @noErrors
import { Provider } from 'accounts'
const provider = Provider.create()
const [account] = await provider.request({ method: 'eth_accounts' })
// ---cut---
const result = await provider.request({
  method: 'eth_fillTransaction',
  params: [{
    from: account,
    calls: [{
      to: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e
      // transfer 100 USDC.e (but account has 40)
      data: '0xa9059cbb000000000000000000000000cafebabecafebabecafebabecafebabecafebabe0000000000000000000000000000000000000000000000000000000005f5e100',
    }],
  }],
})

result.capabilities.requireFunds
// {
//   amount: '0x3938700',
//   decimals: 6,
//   formatted: '60.000000',
//   token: '0x20c000000000000000000000b9537d11c60e8b50',
//   symbol: 'USDC.e',
// }

result.capabilities.balanceDiffs
// {
//   '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(){:js}` (e.g. `feePayer`, `autoSwap`, etc).

Set [`features: 'all'`](#features) 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.

```ts twoslash
// @noErrors
import { privateKeyToAccount } from 'viem/accounts'
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  features: 'all', // [!code focus]
})
```

## 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](https://docs.tempo.xyz/guide/stablecoin-dex). Set to `false` to disable even when `features: 'all'` is set.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  autoSwap: { slippage: 0.02 }, // 2% slippage // [!code focus]
})
```

#### autoSwap.slippage

* **Type:** `number`
* **Default:** `0.05` (5%)

Slippage tolerance for AMM swaps. The relay sets `maxAmountIn = deficit + deficit * slippage`.

### cacheTtl

* **Type:** `number`
* **Default:** `600` (10 minutes)

TTL in seconds for cached [`resolveTokens`](#resolvetokens) results. Cached entries are stored in [`kv`](#kv).

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  cacheTtl: 60, // [!code focus]
})
```

### chains

* **Type:** `readonly [Chain, ...Chain[]]`
* **Default:** `[tempo, tempoModerato, tempoDevnet]`

Supported chains. The handler resolves the client based on the `chainId` in the incoming transaction.

```ts twoslash
import { tempo } from 'viem/tempo/chains'
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  chains: [tempo], // [!code focus]
})
```

### 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).

```ts twoslash
// @noErrors
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  features: 'all', // [!code focus]
})
```

### feePayer

* **Type:** `object`
* **Optional**

Fee payer configuration. When provided, the relay will sign `feePayerSignature` on the filled transaction.

```ts twoslash
import { privateKeyToAccount } from 'viem/accounts'
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  feePayer: { // [!code focus]
    account: privateKeyToAccount('0x...'), // [!code focus]
    name: 'My App', // [!code focus]
    url: 'https://myapp.com', // [!code focus]
  }, // [!code focus]
})
```

#### feePayer.account

* **Type:** `LocalAccount`
* **Required**

The account to use as the fee payer.

#### feePayer.feeToken

* **Type:** `Address`
* **Optional**

Sponsor's preferred fee-token override. When sponsoring, this overrides whatever `feeToken` the consumer asked for — the sponsor is paying, so the sponsor picks. Falls back to the request's `feeToken` when omitted.

#### 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: Transaction.TransactionRequest) => boolean | Promise<boolean>`
* **Optional**

Validates whether to sponsor a transaction. The `request` parameter is a [`Transaction.TransactionRequest`](https://wevm.dev/viem/tempo/Transaction.TransactionRequest) from `viem/tempo` — the typed Tempo envelope built for sponsorship signing. 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.

```ts twoslash
import { privateKeyToAccount } from 'viem/accounts'
import { Handler } from 'accounts/server'

const blocked = '0x...'

const handler = Handler.relay({
  feePayer: {
    account: privateKeyToAccount('0x...'),
    validate: (request) => request.from !== blocked, // [!code focus]
  },
})
```

### kv

* **Type:** [`Kv`](/docs/server/kv)
* **Default:** `Kv.memory()`

Key-value store used to cache `resolveTokens` results across requests, and to cache the `fee.getUserToken` lookup used by [Best Fee Tokens](#best-fee-tokens). Shared with `Handler.exchange` — providing `Kv.cloudflare(env.KV)` (or any other adapter) on both handlers reuses a single cache.

```ts twoslash
// @noErrors
import { Handler, Kv } from 'accounts/server'

const handler = Handler.relay({
  kv: Kv.cloudflare(env.KV), // [!code focus]
})
```

### onRequest

* **Type:** `(request: RpcRequest) => Promise<void>`
* **Optional**

Callback called before processing each request. Useful for logging, rate limiting, or custom validation.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  onRequest: async (request) => { // [!code focus]
    console.log('Processing request:', request.method) // [!code focus]
  }, // [!code focus]
})
```

### path

* **Type:** `string`
* **Default:** `'/'`

Path where the handler listens for requests.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  path: '/relay', // [!code focus]
})
```

When used with [`Provider.relay`](/docs/api/provider#relay), expose both the
base path and chain-qualified child paths from your HTTP router. The provider
posts JSON-RPC to `/relay/:chainId`; direct `POST /relay` calls are only valid
when your router sends that exact path to the relay handler.

### 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.

```ts twoslash
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  resolveTokens: (chainId) => [ // [!code focus]
    { // [!code focus]
      address: '0x20c0000000000000000000000000000000000000', // [!code focus]
      decimals: 6, // [!code focus]
      name: 'pathUSD', // [!code focus]
      symbol: 'pathUSD', // [!code focus]
    }, // [!code focus]
    { // [!code focus]
      address: '0x20c000000000000000000000b9537d11c60e8b50', // [!code focus]
      decimals: 6, // [!code focus]
      name: 'USDC.e', // [!code focus]
      symbol: 'USDC.e', // [!code focus]
    }, // [!code focus]
  ], // [!code focus]
})
```

### transports

* **Type:** `Record<number, Transport>`
* **Default:** `http()` for each chain

Transports keyed by chain ID.

```ts twoslash
import { http } from 'viem'
import { tempo } from 'viem/tempo/chains'
import { Handler } from 'accounts/server'

const handler = Handler.relay({
  transports: { // [!code focus]
    [tempo.id]: http('https://rpc.tempo.xyz'), // [!code focus]
  }, // [!code focus]
})
```

### cors

* **Type:** `boolean | Cors`
* **Default:** `true`

Inherited from [`from.Options`](/docs/server/handler.compose#optionscors). CORS configuration applied to every response.

### headers

* **Type:** `Headers | Record<string, string>`
* **Optional**

Inherited from [`from.Options`](/docs/server/handler.compose#optionsheaders). Additional headers merged onto every response.

## Request

The relay accepts standard JSON-RPC `eth_fillTransaction` requests. In addition to the chain's parameter shape, the relay reads the following wallet-level fields from the first parameter object:

* `from` (`Address`) — used for fee-token resolution and sponsor validation.
* `feeToken` (`Address`) — explicit fee token request. Wins over `resolveTokens`-based resolution. The sponsor's [`feePayer.feeToken`](#feepayerfeetoken) overrides this when sponsoring.
* `feePayer` (`string | false`):
  * **`string`** — an external fee-payer URL. The relay forwards the fill through that URL (so a third-party sponsor service can sign), and re-emits its `capabilities.sponsor` and (if present) `capabilities.autoSwap` on the response. The handler's own [`feePayer`](#feepayer) is *not* required — providing an external URL alone is enough to enable sponsorship for the request.
  * **`false`** — opt out of sponsorship for this request even if the relay has [`feePayer`](#feepayer) configured.
  * **omitted** — sponsorship is applied when [`feePayer`](#feepayer) is configured on the handler.
* `capabilities.balanceDiffs` (`boolean`, default `true`) — set to `false` to skip the post-fill `tempo_simulateV1` round trip that derives [Balance Diffs](#balance-diffs) (~250-400ms saving).
* `capabilities.errors` (`boolean`) — when `true`, a failed fill returns a structured `capabilities.error` instead of a raw RPC error.

## Response

The relay enriches the standard `eth_fillTransaction` response with a `capabilities` object:

```ts
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. "USDC.e"). */
      symbol: string
    } | null
    /** AMM swap injected to cover an insufficient balance. */
    autoSwap?: {
      /** Stablecoin DEX calls prepended to the transaction (approve + buy). */
      calls: readonly { to: Address; data: Hex }[]
      /** 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. "pathUSD"). */
  name: string
  /** Token symbol (e.g. "pathUSD"). */
  symbol: string
  /** Token address. */
  token: Address
  /** Amount (hex-encoded). */
  value: Hex
}
```
