> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://viem-4nvgmcugr-wevm.vercel.app/api/mcp` to find what you need.

# Fee Payer Plugin

## Overview

[`Relay.feePayer`](#relayfeepayer) sponsors transaction fees with a local account.
It signs filled transactions and raw Tempo transactions that request sponsorship.
A request can also supply an external fee-payer URL listed in `allowedFeePayers`.

## Recipes

Choose a local relay to run plugins in your application, or connect to a remote
relay. For remote mode, [configure the server](/tempo/guides/relay/run#add-plugins)
with the plugins shown in the local configuration.

### Sponsor Transactions

Send a transfer with [`token.transferSync`](/tempo/actions/token.transfer).
The relay sponsors the transaction fees and the action waits for its receipt.
Replace the keys and addresses with your own.

:::code-group
```ts twoslash [example.ts]
import { parseUnits } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { client } from './viem.config'

// [!code focus:start]
const { receipt } = await client.token.transferSync({
  account: privateKeyToAccount('0x...'),
  token: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e on Tempo mainnet
  to: '0x2222222222222222222222222222222222222222',
  amount: parseUnits('100', 6),
})

receipt.status
// @log: 'success'
// [!code focus:end]
```

```ts twoslash [viem.config.ts (Local Relay)] filename="viem.config.ts"
import { http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { createClient, Relay, withRelay } from 'viem/tempo'

export const client = createClient({
  transport: withRelay(http(), {
    plugins: [
      // [!code focus:start]
      Relay.feePayer({
        account: privateKeyToAccount('0x...'),
        name: 'Example Sponsor',
        url: 'https://example.com',
      }),
      // [!code focus:end]
    ],
  }),
})
```

```ts twoslash [viem.config.ts (Remote Relay)] filename="viem.remote.config.ts"
import { http } from 'viem'
import { createClient } from 'viem/tempo'

// See https://viem.sh/tempo/guides/relay/run for instructions on running a relay.
export const client = createClient({
  transport: http('https://relay.example.com/rpc'), // [!code focus]
})
```
:::

Run the local fee-payer configuration only in a trusted server environment.
Keep the signing key on your server. The sponsor needs a balance in the selected
fee token. The sender needs the USDC.e being transferred.

The fee payer signs over the gas limit and the fee token, so Viem treats the sender's values
as preferences. A `gas` value is a floor: Viem and the relay add gas for the fee payer's
signature. The fee payer chooses the fee token, and only a token set through
[`feeToken`](#optionsfeetoken) is guaranteed.

### Inspect Sponsorship

Use [`prepareTransactionRequest`](/docs/actions/wallet/prepareTransactionRequest)
to inspect sponsorship before signing and sending a transfer. Sponsor metadata
below is illustrative.

:::code-group
```ts twoslash [example.ts]
import { parseUnits } from 'viem'
import { prepareTransactionRequest } from 'viem/actions'
import { Actions } from 'viem/tempo'
import { client } from './viem.config'

const { _capabilities: capabilities } = await prepareTransactionRequest(client, {
  account: '0x1111111111111111111111111111111111111111',
  calls: [Actions.token.transfer.call(client, {
    token: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e on Tempo mainnet
    to: '0x2222222222222222222222222222222222222222',
    amount: parseUnits('100', 6),
  })],
})

// [!code focus:start]
capabilities?.sponsored
// @log: true
capabilities?.sponsor
// @log: {
// @log:   address: '0x1234567890abcdef1234567890abcdef12345678',
// @log:   name: 'Example Sponsor',
// @log:   url: 'https://example.com',
// @log: }
// [!code focus:end]
```

```ts twoslash [viem.config.ts (Local Relay)] filename="viem.config.ts"
import { http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { createClient, Relay, withRelay } from 'viem/tempo'

export const client = createClient({
  transport: withRelay(http(), {
    plugins: [
      // [!code focus:start]
      Relay.feePayer({
        account: privateKeyToAccount('0x...'),
        name: 'Example Sponsor',
        url: 'https://example.com',
      }),
      // [!code focus:end]
    ],
  }),
})
```

```ts twoslash [viem.config.ts (Remote Relay)] filename="viem.remote.config.ts"
import { http } from 'viem'
import { createClient } from 'viem/tempo'

// See https://viem.sh/tempo/guides/relay/run for instructions on running a relay.
export const client = createClient({
  transport: http('https://relay.example.com/rpc'), // [!code focus]
})
```
:::

### Apply a Sponsorship Policy

:::code-group
```ts twoslash [viem.config.ts (Local Relay)] filename="viem.config.ts"
import { http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { Addresses, createClient, Relay, withRelay } from 'viem/tempo'

const plugin = Relay.feePayer({
  account: privateKeyToAccount('0x...'),
  feeToken: Addresses.pathUsd,
  validate: (transaction) => // [!code focus]
    transaction.from === '0x0000000000000000000000000000000000000001', // [!code focus]
})

export const client = createClient({
  transport: withRelay(http(), {
    plugins: [plugin], // [!code focus]
  }),
})
```

```ts twoslash [viem.config.ts (Remote Relay)] filename="viem.remote.config.ts"
import { http } from 'viem'
import { createClient } from 'viem/tempo'

// See https://viem.sh/tempo/guides/relay/run for instructions on running a relay.
export const client = createClient({
  transport: http('https://relay.example.com/rpc'), // [!code focus]
})
```
:::

Only `true` approves sponsorship. Rejected fills fall back to sender-paid
transactions. Rejected raw submissions return an RPC error. An app-provided
external fee-payer URL uses that relay's policy, rather than this callback.

### Opt Out of Sponsorship

Set `feePayer: false` on the transfer to pay fees from the sender's balance.

:::code-group
```ts twoslash [example.ts]
import { parseUnits } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { client } from './viem.config'

const { receipt } = await client.token.transferSync({
  account: privateKeyToAccount('0x...'),
  token: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e on Tempo mainnet
  to: '0x2222222222222222222222222222222222222222',
  amount: parseUnits('100', 6),
  feePayer: false, // [!code focus]
})

receipt.status
// @log: 'success'
```

```ts twoslash [viem.config.ts (Local Relay)] filename="viem.config.ts"
import { http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { createClient, Relay, withRelay } from 'viem/tempo'

export const client = createClient({
  transport: withRelay(http(), {
    plugins: [
      // [!code focus:start]
      Relay.feePayer({
        account: privateKeyToAccount('0x...'),
        name: 'Example Sponsor',
        url: 'https://example.com',
      }),
      // [!code focus:end]
    ],
  }),
})
```

```ts twoslash [viem.config.ts (Remote Relay)] filename="viem.remote.config.ts"
import { http } from 'viem'
import { createClient } from 'viem/tempo'

// See https://viem.sh/tempo/guides/relay/run for instructions on running a relay.
export const client = createClient({
  transport: http('https://relay.example.com/rpc'), // [!code focus]
})
```
:::

### Use an External Sponsor

Pass the sponsor's URL as `feePayer` when sending the transfer. Allow the same
full URL in the relay configuration.

:::code-group
```ts twoslash [example.ts]
import { parseUnits } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { client } from './viem.config'

const { receipt } = await client.token.transferSync({
  account: privateKeyToAccount('0x...'),
  token: '0x20c000000000000000000000b9537d11c60e8b50', // USDC.e on Tempo mainnet
  to: '0x2222222222222222222222222222222222222222',
  amount: parseUnits('100', 6),
  feePayer: 'https://sponsor.example/relay', // [!code focus]
})

receipt.status
// @log: 'success'
```

```ts twoslash [viem.config.ts (Local Relay)] filename="viem.config.ts"
import { http } from 'viem'
import { createClient, Relay, withRelay } from 'viem/tempo'

export const client = createClient({
  transport: withRelay(http(), {
    plugins: [Relay.feePayer({
      allowedFeePayers: ['https://sponsor.example/relay'], // [!code focus]
    })],
  }),
})
```

```ts twoslash [viem.config.ts (Remote Relay)] filename="viem.remote.config.ts"
import { http } from 'viem'
import { createClient } from 'viem/tempo'

// See https://viem.sh/tempo/guides/relay/run for instructions on running a relay.
export const client = createClient({
  transport: http('https://relay.example.com/rpc'), // [!code focus]
})
```
:::

No local sponsor account is required.
The external service supplies the signature and sponsor metadata. Its URL must
match the configured allowlist, including the path and query.

### Record Sponsorship

:::code-group
```ts twoslash [viem.config.ts (Local Relay)] filename="viem.config.ts"
import { http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { createClient, Relay, withRelay } from 'viem/tempo'

const plugin = Relay.feePayer({
  account: privateKeyToAccount('0x...'),
  // [!code focus:start]
  async onSponsored(event) {
    const response = await fetch('https://example.com/sponsorships', {
      method: 'POST',
      body: JSON.stringify(event),
    })
    if (!response.ok) throw new Error('Sponsorship recording failed.')
    return { subsidized: false }
  },
  // [!code focus:end]
})

export const client = createClient({
  transport: withRelay(http(), {
    plugins: [plugin], // [!code focus]
  }),
})
```

```ts twoslash [viem.config.ts (Remote Relay)] filename="viem.remote.config.ts"
import { http } from 'viem'
import { createClient } from 'viem/tempo'

// See https://viem.sh/tempo/guides/relay/run for instructions on running a relay.
export const client = createClient({
  transport: http('https://relay.example.com/rpc'), // [!code focus]
})
```
:::

The callback is awaited before returning a signature or broadcasting. A thrown
error aborts the request. Fill events contain an unsigned sender envelope;
raw-submission events also include `transactionHash`. Returned subsidy details
appear as `sponsorship_details` on the Fetch JSON-RPC response envelope.

## `Relay.feePayer`

Creates a fee-payer plugin. Place [`Relay.accounts`](/tempo/relay/plugins/accounts)
before `feePayer` so fee-payer signing waits until the transaction reaches quorum.
Place `feeToken` after `feePayer` so sender-paid retries select a fee token again.

### Usage

```ts twoslash
import { privateKeyToAccount } from 'viem/accounts'
import { Relay } from 'viem/tempo'

const plugin = Relay.feePayer({
  account: privateKeyToAccount('0x...'),
})
```

Without an account, the plugin forwards requests to external fee-payer URLs in its allowlist.
External URLs require HTTPS and a public hostname or IP address; redirects are
rejected. The hosting application controls DNS and outbound network access.

### Parameters

#### options.account

* **Type:** `LocalAccount`

The local sponsor account. Omit for external fee-payer requests only.

```ts twoslash
import { privateKeyToAccount } from 'viem/accounts'
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ account: privateKeyToAccount('0x...') }) // [!code focus]
```

#### options.allowedFeePayers

* **Type:** `readonly string[]`
* **Default:** `[]`

Trusted external relay URLs. Requests must match a normalized full URL, including
its path and query. Only allow URLs controlled by trusted operators. Redirects
are rejected. `internal_allowUnsafeUrls` does not bypass this allowlist.

```ts twoslash
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ allowedFeePayers: ['https://relay.example/rpc'] }) // [!code focus]
```

#### options.feeToken

* **Type:** `Address | ((options: { chainId: number }) => Address | undefined)`

Preferred sponsor token, or a resolver called with the request's chain ID. It
overrides the fill request's token and a raw envelope's token, because senders
do not commit to the fee token of a sponsored envelope. External fee payers
choose their own token.

```ts twoslash
import { Addresses, Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ feeToken: Addresses.pathUsd }) // [!code focus]
Relay.feePayer({ feeToken: ({ chainId }) => chainId === 4217 ? Addresses.pathUsd : undefined }) // [!code focus]
```

#### options.internal\_allowUnsafeUrls

* **Type:** `boolean`
* **Default:** `false`

Allows HTTP and private external relay addresses for trusted local development.

```ts twoslash
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ internal_allowUnsafeUrls: true }) // [!code focus]
```

#### options.minimumBaseFee

* **Type:** `bigint`
* **Default:** `600_000_000n`

Lowest base fee, in attodollars per gas, that the target chains can ever charge.
`onRejected` fires only for fee caps below it. Lower it for chains whose base fee
can fall below the default, so a temporarily underpriced envelope keeps its commitment.

```ts twoslash
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ minimumBaseFee: 100_000_000n }) // [!code focus]
```

#### options.name

* **Type:** `string`

Display name included with the sponsor address in fill capabilities.

```ts twoslash
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ name: 'Example Sponsor' }) // [!code focus]
```

#### options.onRejected

* **Type:** `(event: Relay.feePayer.SponsoredEvent) => void | Promise<void>`

Called when a node rejects a sponsored raw broadcast with `gas price is less than basefee`
and the fee cap is below `minimumBaseFee`, so the envelope can never
be included. Use it to release a commitment recorded by `onSponsored`. Transport
failures, timeouts, and other rejections do not call it, because the envelope may still land.

```ts twoslash
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ onRejected: (event) => console.log(event.signPayload) }) // [!code focus]
```

#### options.onSponsored

* **Type:** `(event: Relay.feePayer.SponsoredEvent) => void | { subsidized: boolean } | Promise<void | { subsidized: boolean }>`

Records a signing commitment. Requests can repeat, so use `event.signPayload` as
an idempotency key when recording commitments. `event.feePayer` is the signing
fee-payer address, and raw submissions report the sender recovered from its signature.

```ts twoslash
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ onSponsored: () => ({ subsidized: false }) }) // [!code focus]
```

#### options.url

* **Type:** `string`

Display URL included in sponsor metadata. This value does not select an external relay.

```ts twoslash
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ url: 'https://example.com' }) // [!code focus]
```

#### options.validate

* **Type:** `(transaction) => Relay.feePayer.Validation | Promise<Relay.feePayer.Validation>`

Checks the prepared transaction, including its chain ID. Return `false` or a named
refusal: `billing_past_due`, `billing_required`, `fee_token_unsupported`,
`spend_limit_exceeded`, or `tx_fee_limit_exceeded`.

```ts twoslash
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ validate: () => 'spend_limit_exceeded' }) // [!code focus]
```

#### options.validity

* **Type:** `number`
* **Default:** `3600`

Longest on-chain validity, in seconds, of a returned fee-payer signature. Fills
without `validBefore` receive one, rounded to steps of at most five minutes so identical retries
sign the same payload. `eth_signRawTransaction` requires a `validBefore` within this window.
Expiring nonce transactions are capped at the protocol limit of 300 seconds, and generated
windows for them stay 30 seconds under it to allow for clock skew.

```ts twoslash
import { Relay } from 'viem/tempo'
// ---cut---
Relay.feePayer({ validity: 600 }) // [!code focus]
```

### Return Value

`Relay.Plugin`

A plugin for `Relay.create`, `Relay.handleRequest`, or local `withRelay`.

### Errors

| Error | Description |
| --- | --- |
| `RpcResponse.InvalidParamsError` | Sponsorship is rejected, a URL is invalid, chain IDs conflict, a raw transaction is not canonically encoded, its signature does not verify for its sender, or its `validBefore` is outside `validity`. |
| `RpcResponse.MethodNotFoundError` | A raw signing request has no configured sponsor account. |
| `Error` | The account cannot sign, or a policy or recording callback throws. |
