> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://viem.sh/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.

### 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 five-minute steps so identical retries
sign the same payload. `eth_signRawTransaction` requires a `validBefore` within this window.

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