# Sponsor User Fees

:::info
This guide is intended to be low-level. If you are looking for a high-level abstraction, check out
[Viem's Sponsor User Fees guide](https://viem.sh/tempo/guides/sponsor-fees).
:::

## Overview

Tempo fee sponsorship separates transaction authorization from fee payment. The sender signs the
transaction's execution fields, while a fee payer signs a distinct payload that commits to the
sender address and selected fee token.

Ox exposes both digests and assembles the dual-signed envelope. Coordinating the two signers,
applying sponsorship policy, and funding the fee payer remain application responsibilities.

## Recipes

### Create a Sponsor-Ready Envelope

Set `feePayerSignature` to `null` when calling
[`TxEnvelopeTempo.from`](/tempo/reference/TxEnvelopeTempo/from). This marker tells the sender
signing encoder that a fee payer will complete the envelope.

```ts twoslash
import { TxEnvelopeTempo } from 'ox/tempo'

// [!code focus:start]
const envelope = TxEnvelopeTempo.from({
  calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe' }],
  chainId: 4217,
  feePayerSignature: null, // [!code hl]
  feeToken: '0x20c0000000000000000000000000000000000001',
  gas: 21_000n,
  maxFeePerGas: 2_000_000_000n,
  nonce: 0n,
  nonceKey: 1n,
})
// @log: {
// @log:   calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe' }],
// @log:   feePayerSignature: null,
// @log:   feeToken: '0x20c0000000000000000000000000000000000001',
// @log:   type: 'tempo',
// @log:   ...
// @log: }
// [!code focus:end]
```

### Have the Sender Sign First

The sender signs [`TxEnvelopeTempo.getSignPayload`](/tempo/reference/TxEnvelopeTempo/getSignPayload).
The sponsor signs
[`TxEnvelopeTempo.getFeePayerSignPayload`](/tempo/reference/TxEnvelopeTempo/getFeePayerSignPayload)
with the sender address.

```ts twoslash
import { Address, Secp256k1 } from 'ox'
import { TxEnvelopeTempo } from 'ox/tempo'

// 1. Set up the sender and fee payer keys.
const senderPrivateKey = Secp256k1.randomPrivateKey()
const sender = Address.fromPublicKey(
  Secp256k1.getPublicKey({ privateKey: senderPrivateKey }),
)
const feePayerPrivateKey = Secp256k1.randomPrivateKey()

// 2. Build a sponsor-ready envelope.
const envelope = TxEnvelopeTempo.from({
  calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe' }],
  chainId: 4217,
  feePayerSignature: null,
  feeToken: '0x20c0000000000000000000000000000000000001',
  gas: 21_000n,
  maxFeePerGas: 2_000_000_000n,
  nonce: 0n,
  nonceKey: 1n,
})

// [!code focus:start]
// 3. Sign the sender's transaction commitment.
const senderSignature = Secp256k1.sign({
  payload: TxEnvelopeTempo.getSignPayload(envelope),
  privateKey: senderPrivateKey,
})

// 4. Sign the fee payer commitment with the sender address.
const feePayerSignature = Secp256k1.sign({
  payload: TxEnvelopeTempo.getFeePayerSignPayload(envelope, { sender }),
  privateKey: feePayerPrivateKey,
})
// @log: {
// @log:   senderSignature: { r: '0x...', s: '0x...', yParity: 0 },
// @log:   feePayerSignature: { r: '0x...', s: '0x...', yParity: 1 },
// @log: }
// [!code focus:end]
```

### Have the Fee Payer Sign First

When the fee payer knows the sender address and final transaction fields, it can approve the
transaction before the sender signs. The fee payer commits to the sender and `feeToken`; the sender
then signs the approved envelope without committing to the fee token.

```ts twoslash
import { Address, Secp256k1 } from 'ox'
import { TxEnvelopeTempo } from 'ox/tempo'

// 1. Set up the sender and fee payer keys.
const senderPrivateKey = Secp256k1.randomPrivateKey()
const sender = Address.fromPublicKey(
  Secp256k1.getPublicKey({ privateKey: senderPrivateKey }),
)
const feePayerPrivateKey = Secp256k1.randomPrivateKey()

// 2. Build the final sponsor-ready envelope.
const envelope = TxEnvelopeTempo.from({
  calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe' }],
  chainId: 4217,
  feePayerSignature: null,
  feeToken: '0x20c0000000000000000000000000000000000001',
  gas: 21_000n,
  maxFeePerGas: 2_000_000_000n,
  nonce: 0n,
  nonceKey: 1n,
})

// [!code focus:start]
// 3. Have the fee payer approve the known sender and fee token first.
const feePayerSignature = Secp256k1.sign({
  payload: TxEnvelopeTempo.getFeePayerSignPayload(envelope, { sender }),
  privateKey: feePayerPrivateKey,
})

// 4. Attach the fee payer approval before sender signing.
const approved = TxEnvelopeTempo.from(envelope, { feePayerSignature })

// 5. Have the sender sign the approved envelope.
const senderSignature = Secp256k1.sign({
  payload: TxEnvelopeTempo.getSignPayload(approved),
  privateKey: senderPrivateKey,
})

// 6. Serialize the envelope with both signatures.
const serialized = TxEnvelopeTempo.serialize(approved, {
  signature: senderSignature,
})
// @log: '0x76...'
// [!code focus:end]
```

`TxEnvelopeTempo.getSignPayload` normalizes the attached fee payer signature to the sponsorship
marker, which keeps `feeToken` outside the sender commitment. Do not change fields covered by the
fee payer signature after it signs.

### Assemble and Serialize the Sponsored Envelope

Attach both signatures with [`TxEnvelopeTempo.from`](/tempo/reference/TxEnvelopeTempo/from), then
serialize the completed envelope for `eth_sendRawTransaction`.

```ts twoslash
import { Address, Secp256k1 } from 'ox'
import { TxEnvelopeTempo } from 'ox/tempo'

// 1. Set up the sender and fee payer keys.
const senderPrivateKey = Secp256k1.randomPrivateKey()
const sender = Address.fromPublicKey(
  Secp256k1.getPublicKey({ privateKey: senderPrivateKey }),
)
const feePayerPrivateKey = Secp256k1.randomPrivateKey()

// 2. Build a sponsor-ready envelope.
const envelope = TxEnvelopeTempo.from({
  calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe' }],
  chainId: 4217,
  feePayerSignature: null,
  feeToken: '0x20c0000000000000000000000000000000000001',
  gas: 21_000n,
  maxFeePerGas: 2_000_000_000n,
  nonce: 0n,
  nonceKey: 1n,
})

// 3. Produce the sender and fee payer signatures.
const senderSignature = Secp256k1.sign({
  payload: TxEnvelopeTempo.getSignPayload(envelope),
  privateKey: senderPrivateKey,
})
const feePayerSignature = Secp256k1.sign({
  payload: TxEnvelopeTempo.getFeePayerSignPayload(envelope, { sender }),
  privateKey: feePayerPrivateKey,
})

// [!code focus:start]
// 4. Attach both signatures to the envelope.
const sponsored = TxEnvelopeTempo.from(envelope, {
  feePayerSignature,
  signature: senderSignature,
})

// 5. Serialize the completed sponsored envelope.
const serialized = TxEnvelopeTempo.serialize(sponsored)
// @log: '0x76...'
// [!code focus:end]
```

## Best Practices

### Authorize the Exact Sender

The fee payer payload includes the sender address. Derive it from sender key material or verify an
authenticated sender claim before signing, especially when the fee payer signs first.

### Let the Fee Payer Control Its Token

When the sponsorship marker is present, the sender sign payload does not commit to `feeToken`.
The fee payer payload does commit to it, which lets the sponsor select the asset it will spend.

### Keep the Fee Payer Key Isolated

Treat the fee payer as a policy-controlled signing service. Check allowed senders, calls, fee
caps, rate limits, and balances before releasing a signature.

### Coordinate Either Signing Order

Either party may sign first once the sender address and committed fields are known. The recipes
above show both orders. In a fee-payer-first flow, the sponsor must receive the sender address
before signing. Do not mutate fields covered by either signature after producing it.

## See More

<Cards>
  <Card icon="lucide:coins" title="Pay Fees in a Stablecoin" description="Select a TIP-20 token without fee sponsorship." to="/tempo/guides/transaction-envelopes/pay-fees-in-a-stablecoin" />

  <Card icon="lucide:square-function" title="getFeePayerSignPayload" description="Review the fee payer digest API reference." to="/tempo/reference/TxEnvelopeTempo/getFeePayerSignPayload" />

  <Card icon="lucide:book-open" title="Viem: Sponsor User Fees" description="Coordinate and submit sponsored transactions with a Viem client." to="https://viem.sh/tempo/guides/sponsor-fees" />
</Cards>
