> Feedback: If these docs are stale, missing, or confusing, post sanitized feedback to `https://docs.tempo.xyz/api/feedback` with `source: "mcp"`, a short `message`, and any relevant `toolName`, `relatedResource`, or `client`.
# Sponsor User Fees

Enable gasless transactions by sponsoring transaction fees for your users. Tempo's native fee sponsorship allows applications to pay fees on behalf of users, improving UX and removing friction from payment flows.

:::tip\[Hosted fee payer]
Use Tempo's [hosted fee payer endpoints](/docs/developer-tools/fee-payer) for testnet development or approved mainnet integrations. Run your own fee payer when you need custom sponsorship policy, accounting, or operational control.
:::

## Demo

<Demo.Container name="Sponsor User Fees">
  <Connect stepNumber={1} />

  <AddFunds stepNumber={2} />

  <SendRelayerSponsoredPayment stepNumber={3} last />
</Demo.Container>

## Steps

::::steps

### Set up the fee payer service

:::tip
Tempo provides a public testnet fee payer service at `https://sponsor.moderato.tempo.xyz` that you can use for development and testing. See [Hosted Fee Payer](/docs/developer-tools/fee-payer) for endpoint details, or follow the instructions below to run your own.
:::

To sponsor transactions with your own infrastructure, run a JSON-RPC relay that validates incoming requests, fills Tempo Transactions, signs the fee payer payload, and broadcasts approved transactions. The fee payer account must be funded with the stablecoin it will use for fees.

Your relay should expose the same core JSON-RPC methods described in the [Hosted Fee Payer API reference](/docs/developer-tools/fee-payer#api-reference), then apply your own sponsorship policy before signing.

### Configure your client to use the fee payer service

<PublicTestnetSponsorTip />

:::code-group

```ts twoslash [Tempo Wallet]
// @noErrors
import { tempo } from 'viem/chains'
import { createConfig, http } from 'wagmi'
import { tempoWallet } from 'wagmi/connectors'

export const config = createConfig({
  connectors: [tempoWallet({
    feePayer: 'https://sponsor.moderato.tempo.xyz', // [!code focus]
  })],
  chains: [tempo],
  multiInjectedProviderDiscovery: false,
  transports: {
    [tempo.id]: http(),
  },
})
```

```ts twoslash [WebAuthn]
// @noErrors
import { tempo } from 'viem/chains'
import { withRelay } from 'viem/tempo'
import { createConfig, http } from 'wagmi'
import { webAuthn } from 'wagmi/tempo'

export const config = createConfig({
  connectors: [webAuthn({ authUrl: '/auth' })],
  chains: [tempo],
  multiInjectedProviderDiscovery: false,
  transports: {
    [tempo.id]: withRelay( // [!code focus]
      http(), // [!code focus]
      http('https://sponsor.moderato.tempo.xyz'), // [!code focus]
    ), // [!code focus]
  },
})
```

:::

### Sponsor your user's transactions

Now transactions will be sponsored by your relay. For more details on how to send a transaction, see the [Send a payment](/docs/guide/payments/send-a-payment) guide.

:::info
You can also build your own fee paying service. See [Build your own fee paying service](#build-your-own-fee-paying-service) below.
:::

:::code-group

```tsx twoslash [SendSponsoredPayment.tsx] filename="SendSponsoredPayment.tsx"
import { Hooks } from 'wagmi/tempo'
import { parseUnits } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'

const alphaUsd = '0x20c0000000000000000000000000000000000001'

// @noErrors
function SendSponsoredPayment() {
  const sponsorAccount = privateKeyToAccount('0x...')
  const sendPayment = Hooks.token.useTransferSync() // [!code hl]
  const metadata = Hooks.token.useGetMetadata({
    token: alphaUsd,
  })

  return (
    <form onSubmit={
      (event) => {
        event.preventDefault()
        const formData = new FormData(event.target as HTMLFormElement)

        const recipient = (formData.get('recipient') ||
          '0x0000000000000000000000000000000000000000') as `0x${string}`

        sendPayment.mutate({ // [!code hl]
          amount: parseUnits('100', metadata.data.decimals), // [!code hl]
          feePayer: sponsorAccount, // [!code focus]
          to: recipient, // [!code hl]
          token: alphaUsd, // [!code hl]
        }) // [!code hl]
      }
    }>
      <div>
        <label htmlFor="recipient"> Recipient address </label>
        <input type="text" placeholder="0x..." />
      </div>
      <button type="submit" disabled={sendPayment.isPending}>
        Send Payment
      </button>
    </form>
  )
}
```

```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts"
// @noErrors
// [!include ~/snippets/wagmi.config.ts:withFeePayer]
```

:::

### Next Steps

Now that you've implemented fee sponsorship, you can:

* Learn more about the [Tempo Transaction](/docs/protocol/transactions/spec-tempo-transaction#fee-payer-signature-details) type and fee payer signature details
* Use Tempo Transactions to sponsor multiple calls in a single signed operation
* Learn how to [Pay Fees in Any Stablecoin](/docs/guide/payments/pay-fees-in-any-stablecoin)

::::

## Build your own fee paying service

Instead of using the hosted fee payer service, you can build your own.

The sender must indicate sponsorship **before signing**, because the transaction's signing hash differs based on whether it will be sponsored:

* **Sponsored transactions**: The `fee_token` field is omitted from the sender's signing payload, and a `0x00` marker is used. This allows the fee payer to choose the fee token.
* **Non-sponsored transactions**: The `fee_token` is included in the sender's signing payload.

The SDKs handle this automatically. Here's how each SDK manages the dual-signing flow:

<Tabs stateKey="library">
  <Tab title="Viem">
    In Viem, pass a local account to the `feePayer` parameter to handle both signatures in your Node.js backend.

    ```ts twoslash
    // @noErrors
    import { createClient, http, parseUnits } from 'viem'
    import { privateKeyToAccount } from 'viem/accounts'
    import { tempoModerato } from 'viem/chains'

    const client = createClient({
      chain: tempoModerato,
      transport: http(),
    })

    const senderAccount = privateKeyToAccount('0x...')
    const feePayerAccount = privateKeyToAccount('0x...') // [!code hl]

    // When feePayer is an Account object, Viem:
    // 1. Has the sender sign the transaction (with feeToken skipped)
    // 2. Recovers the sender address from their signature
    // 3. Has the fee payer sign a separate hash (includes sender + feeToken)
    // 4. Serializes the dual-signed transaction
    const { receipt } = await client.token.transferSync({
      account: senderAccount,
      amount: parseUnits('10.5', 6),
      to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb',
      token: '0x20c0000000000000000000000000000000000000',
      feePayer: feePayerAccount, // [!code hl]
    })
    ```

    :::info
    Alternatively, use the [`withRelay`](https://viem.sh/tempo/transports/withRelay) transport to delegate signing to a remote fee payer service. See the [Steps section above](#configure-your-client-to-use-the-fee-payer-service) for setup.
    :::
  </Tab>

  <Tab title="Rust">
    :::code-group

    ```rust [example.rs]
    use alloy::{
        primitives::{address, U256},
        providers::Provider,
        signers::{SignerSync, local::PrivateKeySigner},
        sol_types::SolCall,
    };
    use tempo_alloy::{
        contracts::precompiles::ITIP20, primitives::transaction::Call,
        rpc::TempoTransactionRequest,
    };

    mod provider;

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let provider = provider::get_provider().await?;

        let tx = TempoTransactionRequest {
            calls: vec![Call {
                to: address!("0x20c0000000000000000000000000000000000000").into(),
                input: ITIP20::transferCall {
                    to: address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"),
                    amount: U256::from(10_500_000),
                }
                .abi_encode()
                .into(),
                value: U256::ZERO,
            }],
            ..Default::default()
        };

        // Step 1: Build the transaction (sender signs, fee_token excluded from hash)
        let mut tempo_tx = provider.fill(tx).await?.build_aa()?;
        let sender_addr = provider.default_signer_address();

        // Step 2: Compute the fee payer hash (includes sender address + fee_token)
        let fee_payer_hash = tempo_tx.fee_payer_signature_hash(sender_addr); // [!code hl]

        // Step 3: Fee payer counter-signs
        let fee_payer: PrivateKeySigner = "0x...".parse()?; // [!code hl]
        tempo_tx.fee_payer_signature = Some(fee_payer.sign_hash_sync(&fee_payer_hash)?); // [!code hl]

        // Step 4: Broadcast
        let pending = provider.send_transaction(tempo_tx).await?;

        println!("Transaction hash: {:?}", pending.tx_hash());

        Ok(())
    }
    ```

    ```rust [provider.rs]
    // [!include ~/snippets/rust-signer-provider.rs:setup]
    ```

    :::
  </Tab>

  <Tab title="Python">
    :::code-group

    ```python [example.py]
    from pytempo import TempoTransaction
    from pytempo.contracts import TIP20
    from provider import w3, account

    TOKEN = "0x20c0000000000000000000000000000000000000"

    # Step 1: Sender creates and signs (fee_token excluded from signing hash)
    tx = TempoTransaction.create(
        chain_id=w3.eth.chain_id,
        gas_limit=100_000,
        max_fee_per_gas=w3.eth.gas_price * 2,
        max_priority_fee_per_gas=w3.eth.gas_price,
        nonce=w3.eth.get_transaction_count(account.address),
        awaiting_fee_payer=True, # [!code hl]
        calls=(
            TIP20(TOKEN).transfer(
                to="0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb",
                amount=10_500_000,
            ),
        ),
    )

    signed_by_sender = tx.sign(account.key.hex())

    # Step 2: Fee payer counter-signs (includes sender address + fee_token)
    FEE_PAYER_KEY = "0x..."
    fully_signed = signed_by_sender.sign(FEE_PAYER_KEY, for_fee_payer=True) # [!code hl]

    # Step 3: Broadcast
    tx_hash = w3.eth.send_raw_transaction(fully_signed.encode())
    ```

    ```python [provider.py]
    from web3 import Web3
    from eth_account import Account

    w3 = Web3(Web3.HTTPProvider("https://rpc.presto.tempo.xyz"))
    account = Account.from_key("0x...")
    ```

    :::
  </Tab>

  <Tab title="Go">
    :::code-group

    ```go [main.go]
    package main

    import (
       "context"
       "log"
       "math/big"

       "github.com/ethereum/go-ethereum/common"
       "github.com/tempoxyz/tempo-go/pkg/signer"
       "github.com/tempoxyz/tempo-go/pkg/transaction"
    )

    func main() {
       senderSigner, _ := signer.NewSigner("0x...") // sender key
       feePayerSigner, _ := signer.NewSigner("0x...") // fee payer key
       c := newClient()
       ctx := context.Background()

       nonce, _ := c.GetTransactionCount(ctx, senderSigner.Address().Hex())

       token := common.HexToAddress("0x20c0000000000000000000000000000000000000")
       recipient := common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb")

       // Step 1: Sender creates tx and signs (fee_token excluded from hash)
       tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdModerato)).
               SetNonce(nonce).
               SetGas(100_000).
               SetMaxFeePerGas(big.NewInt(25_000_000_000)).
               SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)).
               SetSponsored(true). // [!code hl]
               AddCall(token, big.NewInt(0), buildTransferData(recipient, big.NewInt(10_500_000))).
               Build()

       _ = transaction.SignTransaction(tx, senderSigner)

       // Step 2: Fee payer sets fee token and counter-signs
       tx.FeeToken = common.HexToAddress("0x20c0000000000000000000000000000000000001") // [!code hl]
       _ = transaction.AddFeePayerSignature(tx, feePayerSigner) // [!code hl]

       // Step 3: Broadcast
       serialized, _ := transaction.Serialize(tx, nil)
       txHash, _ := c.SendRawTransaction(ctx, serialized)

       log.Printf("Sponsored transaction hash: %s", txHash)
    }
    ```

    ```go [provider.go]
    // [!include ~/snippets/go-provider.go:setup]
    ```

    :::
  </Tab>
</Tabs>

## Best practices

1. **Set sponsorship limits**: Implement daily or per-user limits to control costs
2. **Monitor expenses**: Track sponsorship costs regularly to stay within budget
3. **Consider selective sponsorship**: Only sponsor fees for specific operations or user segments
4. **Educate users**: Clearly communicate when fees are being sponsored

### Security Considerations

* **Transaction-specific**: Fee payer signatures are tied to specific transactions
* **No delegation risk**: Fee payer can't execute arbitrary transactions
* **Balance checks**: Network verifies fee payer has sufficient balance
* **Signature validation**: Both signatures must be valid

## Learning Resources

<Cards>
  <Card description="Learn more about fees and how they work on Tempo" to="/docs/protocol/fees/spec-fee" icon="lucide:book" title="Fee Specification" />

  <Card description="Technical specification for TempoTransactions and fee payer signature details" to="/docs/protocol/transactions/spec-tempo-transaction" icon="lucide:file-code" title="TempoTransaction Spec" />

  <Card description="Understand fee token selection and how to pay with different stablecoins" to="/docs/guide/payments/pay-fees-in-any-stablecoin" icon="lucide:coins" title="Pay Fees in Any Stablecoin" />
</Cards>
