Skip to main content
Tempo supports fee sponsorship through the awaitingFeePayer flag. This allows third parties to pay transaction fees on behalf of users, enabling gasless experiences.

How Fee Sponsorship Works

  1. User signs a transaction with awaitingFeePayer: true
  2. Transaction is sent to a fee payer service
  3. Fee payer adds their signature and broadcasts the transaction
  4. User’s transaction executes without paying gas fees

Transaction Expiry

When using fee sponsorship, you can set transaction validity windows:
ParameterTypeDescription
validAfternumberUnix timestamp after which the transaction becomes valid
validBeforenumberUnix timestamp after which the transaction expires

Example: Creating a Sponsored Transaction

import { FC, useState } from 'react';
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { isTempoWallet } from '@dynamic-labs/tempo';
import { encodeFunctionData, erc20Abi, Hex } from 'viem';

const GaslessTransactionButton: FC = () => {
  const { primaryWallet } = useDynamicContext();
  const [signedTx, setSignedTx] = useState<string | null>(null);

  const onCreateGaslessTx = async () => {
    if (!primaryWallet || !isTempoWallet(primaryWallet)) {
      throw new Error('Tempo wallet not found');
    }

    const publicClient = await primaryWallet.getPublicClient();
    const walletClient = await primaryWallet.getWalletClient();

    const fromAddress = primaryWallet.address as Hex;
    const toAddress = '0xRecipient...' as Hex;
    const tokenAddress = '0xToken...' as Hex;
    const amount = BigInt(1000000);

    // Encode transfer
    const callData = encodeFunctionData({
      abi: erc20Abi,
      functionName: 'transfer',
      args: [toAddress, amount],
    });

    // Get nonce
    const nonce = await publicClient.getTransactionCount({
      address: fromAddress,
    });

    const gasPrice = await publicClient.getGasPrice();
    const chainId = await publicClient.getChainId();

    // Set validity window (valid for next 10 minutes)
    const now = Math.floor(Date.now() / 1000);
    const validAfter = now;
    const validBefore = now + 600; // 10 minutes

    // Build transaction with fee sponsorship flag
    const tempoTx = {
      calls: [
        {
          data: callData,
          to: tokenAddress,
          value: BigInt(0),
        },
      ],
      chainId,
      gas: BigInt(300000),
      maxFeePerGas: gasPrice,
      maxPriorityFeePerGas: gasPrice / BigInt(10),
      nonce,
      // Fee sponsorship flags
      awaitingFeePayer: true,
      validAfter,
      validBefore,
    };

    // Sign the transaction (but don't broadcast)
    const signature = await walletClient.signTransaction(tempoTx);
    setSignedTx(signature);

    console.log('Signed transaction (awaiting fee payer):', signature);

    // In production, send this to your fee payer service:
    // await fetch('/api/sponsor-transaction', {
    //   method: 'POST',
    //   body: JSON.stringify({ signedTransaction: signature }),
    // });
  };

  return (
    <div>
      <button onClick={onCreateGaslessTx}>Create Gasless Transaction</button>
      {signedTx && (
        <div>
          <p>Signed transaction ready for sponsorship:</p>
          <code style={{ wordBreak: 'break-all', fontSize: '12px' }}>
            {signedTx}
          </code>
        </div>
      )}
    </div>
  );
};

Fee Payer Service (Server-Side)

The fee payer service receives the signed transaction, adds its signature for the fee payment, and broadcasts:
// Example server-side fee payer (pseudocode)
async function sponsorTransaction(signedUserTx: string) {
  // 1. Validate the transaction
  const tx = decodeTransaction(signedUserTx);

  // 2. Check if we want to sponsor this transaction
  if (!shouldSponsor(tx)) {
    throw new Error('Transaction not eligible for sponsorship');
  }

  // 3. Add fee payer signature
  const feePayerWallet = getFeePayerWallet();
  const sponsoredTx = await feePayerWallet.signAsFeePayer(signedUserTx);

  // 4. Broadcast
  const hash = await publicClient.sendRawTransaction({
    serializedTransaction: sponsoredTx,
  });

  return hash;
}

Best Practices

  1. Set reasonable validity windows: Use validAfter and validBefore to prevent stale transactions from being broadcast
  2. Validate transactions server-side: Check transaction parameters before sponsoring
  3. Rate limit: Implement rate limiting on your fee payer service to prevent abuse
  4. Monitor spending: Track gas expenditure to manage sponsor costs