---
title: Fee Abstraction
description:
  Learn how fee sponsorship works and implement fee abstraction with Kora.
---

Every Solana transaction requires SOL to pay network fees. But users coming to
your payment application expect to transact in stablecoins—not manage a second
token balance. Fee abstraction removes this friction by having someone else pay
the fees.

This guide covers two levels:

1. **How fee sponsorship works** — the underlying Solana primitive
2. **Fee abstraction at scale with Kora** — a production-ready fee abstraction
   service

## How Fee Sponsorship Works

Solana transactions have a designated fee payer—the account that pays the
network fee. By default, this is the first signer. But you can specify a
different account as the fee payer, allowing a third party (the "sponsor") to
cover fees on behalf of the sender.

Both the sender and sponsor must sign the transaction:

- The **sender** signs to authorize the transfer of their tokens
- The **sponsor** signs to authorize payment of the network fee

<Callout>
  See [How Payments Work on Solana](/docs/payments/how-payments-work) for core
  payment concepts.
</Callout>

The steps below show the core flow. See the [Demo](#demo) for complete runnable
code.

<ScrollyCoding>

## !!steps Create a Sponsor Account

Generate a separate keypair for the sponsor who will pay transaction fees. The
sponsor needs SOL for fees but doesn't need to hold the tokens being
transferred.

<CodePlaceholder title="Sponsor Transaction Fee" />

```ts !! title="Sponsor Transaction Fee"
const sponsor = await generateKeyPairSigner();
```

## !!steps Create Transfer Instruction

Create the token transfer instruction with the sender as authority. The sender
owns the tokens and must sign the transfer.

<CodePlaceholder title="Sponsor Transaction Fee" />

```ts !! title="Sponsor Transaction Fee"
const sponsor = await generateKeyPairSigner();

// !focus(1:8)
const transferInstruction = getTransferCheckedInstruction({
  source: senderAta,
  mint: mint.address,
  destination: recipientAta,
  authority: sender, // Sender signs for the transfer
  amount: 250_000n, // adjusted for the mint's decimals
  decimals: 6
});
```

## !!steps Send with Sponsor as Fee Payer

Create the client with the sponsor set as the fee payer via `payer(sponsor)`,
then call `client.sendTransaction`. The sponsor signs to pay the network fee
while the sender signs to authorize the transfer—`sendTransaction` collects
every referenced signer automatically.

<CodePlaceholder title="Sponsor Transaction Fee" />

```ts !! title="Sponsor Transaction Fee"
const sponsor = await generateKeyPairSigner();

const transferInstruction = getTransferCheckedInstruction({
  source: senderAta,
  mint: mint.address,
  destination: recipientAta,
  authority: sender,
  amount: 250_000n,
  decimals: 6
});

// !focus(1:5)
const client = createClient()
  // !mark
  .use(payer(sponsor)) // Sponsor pays the transaction fees
  .use(solanaLocalRpc());

const { context } = await client.sendTransaction([transferInstruction]);
```

</ScrollyCoding>

### Demo

<CodeTabs flags="r">

```ts !! title="Demo"
// !collapse(1:15) collapsed
// Click ">" icon on left to expand demo imports
import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";
import type { KeyPairSigner } from "@solana/kit";
import { solanaLocalRpc } from "@solana/kit-plugin-rpc";
import { payer } from "@solana/kit-plugin-signer";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
  getCreateAssociatedTokenInstructionAsync,
  getInitializeMintInstruction,
  getMintSize,
  TOKEN_2022_PROGRAM_ADDRESS,
  findAssociatedTokenPda,
  getMintToInstruction,
  getTransferCheckedInstruction
} from "@solana-program/token-2022";

// Generate keypairs for sender, recipient, and sponsor (fee payer)
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// !mark
const sponsor = await generateKeyPairSigner();

console.log("Sender Address:", sender.address);
console.log("Recipient Address:", recipient.address);
console.log("Sponsor Address (Fee Payer):", sponsor.address);

// Demo Setup: Create client, mint account, token accounts, and fund with initial tokens
const { client, mint } = await demoSetup(sender, recipient, sponsor);

console.log("\nMint Address:", mint.address);

// Derive the Associated Token Accounts addresses (ATAs) for sender and recipient
const [senderAta] = await findAssociatedTokenPda({
  mint: mint.address,
  owner: sender.address,
  tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});

const [recipientAta] = await findAssociatedTokenPda({
  mint: mint.address,
  owner: recipient.address,
  tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});

console.log("Sender Token Account:", senderAta.toString());
console.log("Recipient Token Account:", recipientAta.toString());

// =============================================================================
// Sponsored Token Payment Demo
// =============================================================================

// Create instruction to transfer tokens from sender to recipient
// Transferring 250,000 base units = 0.25 tokens (with 6 decimals)
const transferInstruction = getTransferCheckedInstruction({
  source: senderAta,
  mint: mint.address,
  destination: recipientAta,
  authority: sender, // Sender signs to authorize the transfer
  amount: 250_000n, // 0.25 tokens
  decimals: 6
});

// Send transaction with sponsor as fee payer using @solana/kit
// The sponsor (client.payer) pays fees; the sender signs for the transfer
// !mark
const { context } = await client.sendTransaction([transferInstruction]);
const signature = context.signature;

console.log("\n=== Sponsored Token Payment Complete ===");
console.log("Transaction Signature:", signature.toString());

// Fetch final token account balances from the RPC
const senderBalance = await client.rpc.getTokenAccountBalance(senderAta).send();
const recipientBalance = await client.rpc
  .getTokenAccountBalance(recipientAta)
  .send();

console.log("\nSender Token Account Balance:", senderBalance.value.uiAmount);
console.log(
  "Recipient Token Account Balance:",
  recipientBalance.value.uiAmount
);

// Fetch transaction details
const transaction = await client.rpc
  .getTransaction(signature, {
    encoding: "jsonParsed",
    maxSupportedTransactionVersion: 0
  })
  .send();

const feePayer = transaction?.transaction.message.accountKeys?.[0];
console.log("\nNote: The first account in accountKeys is always the fee payer");
console.log("Fee Payer Address:", feePayer);

// =============================================================================
// Demo Setup Helper Function
// =============================================================================
// !collapse(1:1000) collapsed

/**
 * Sets up for a sponsored token transfer demo:
 * - Creates @solana/kit client pointing to a local validator
 * - Airdrops SOL to sponsor for transaction fees
 * - Generates mint keypair and creates/initializes mint account
 * - Creates associated token accounts for sender and recipient
 * - Mints initial tokens to sender
 *
 * @param sender - The sender's keypair (will be used as mint authority)
 * @param recipient - The recipient's keypair
 * @param sponsor - The sponsor's keypair (will pay all transaction fees)
 * @returns Returns client instance and mint address
 */
async function demoSetup(
  sender: KeyPairSigner,
  recipient: KeyPairSigner,
  sponsor: KeyPairSigner
) {
  // Create @solana/kit client with the sponsor set as the fee payer
  const client = createClient().use(payer(sponsor)).use(solanaLocalRpc());

  // Fund sponsor with SOL for transaction fees
  await client.airdrop(sponsor.address, lamports(1_000_000_000n));

  // Generate keypair to use as address of mint
  const mint = await generateKeyPairSigner();

  // Get default mint account size (in bytes), no extensions enabled
  const space = BigInt(getMintSize());

  // Get minimum balance for rent exemption
  const rent = await client.rpc.getMinimumBalanceForRentExemption(space).send();

  // Instruction to create new account for mint (token program)
  // Invokes the system program (sponsor pays the rent)
  const createAccountInstruction = getCreateAccountInstruction({
    payer: sponsor,
    newAccount: mint,
    lamports: rent,
    space,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Instruction to initialize mint account data
  // Invokes the token 2022 program
  const initializeMintInstruction = getInitializeMintInstruction({
    mint: mint.address,
    decimals: 6,
    mintAuthority: sender.address
  });

  // Create instruction for sender's ATA (sponsor pays)
  const createSenderAtaInstruction =
    await getCreateAssociatedTokenInstructionAsync({
      payer: sponsor,
      mint: mint.address,
      owner: sender.address,
      tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
    });

  // Create instruction for recipient's ATA (sponsor pays)
  const createRecipientAtaInstruction =
    await getCreateAssociatedTokenInstructionAsync({
      payer: sponsor,
      mint: mint.address,
      owner: recipient.address,
      tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
    });

  // Derive sender's associated token account address (ATA)
  const [senderAta] = await findAssociatedTokenPda({
    mint: mint.address,
    owner: sender.address,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Create instruction to mint initial tokens to sender
  // Sender is the mint authority and must sign
  const mintToInstruction = getMintToInstruction({
    mint: mint.address,
    token: senderAta,
    mintAuthority: sender, // Pass signer, not just address
    amount: 1_000_000n // Mint 1.00 tokens (1,000,000 base units with 6 decimals)
  });

  // Combine all setup instructions
  const setupInstructions = [
    createAccountInstruction, // Create mint account
    initializeMintInstruction, // Initialize mint
    createSenderAtaInstruction, // Create sender's ATA
    createRecipientAtaInstruction, // Create recipient's ATA
    mintToInstruction // Mint tokens to sender
  ];

  // Send transaction using @solana/kit with sponsor as fee payer
  await client.sendTransaction(setupInstructions);

  return {
    client,
    mint
  };
}
```

</CodeTabs>

<Callout type="caution">
  When you create a token account for an end user, they can close it and reclaim
  the SOL used for rent. Consider charging users for account creation in
  stablecoins, or factor this cost into your product economics.
</Callout>

## Fee Abstraction at Scale with Kora

The fee payer primitive is powerful, but building a production gasless system
requires more: managing sponsor wallets, handling token conversions (so users
can "pay" fees in USDC), rate limiting, and security controls.

[Kora](/docs/tools/kora) handles this complexity. It's a JSON-RPC server that
provides fee abstraction so users never need SOL. You can fully sponsor fees or
accept fee payment in any token.

Deploy Kora with a single command:

```bash
cargo install kora-cli
kora --config path/to/kora.toml rpc start --signers-config path/to/signers.toml
```

Then point a Kit client at your Kora server and send transactions with the same
API used above:

```bash
pnpm add @solana/kora
```

```typescript
import { address } from "@solana/kit";
import { createKitKoraClient } from "@solana/kora";

// A Kora-backed Kit client: Kora is the fee payer, and users pay
// fees in the SPL token of your choice instead of SOL
const kora = await createKitKoraClient({
  endpoint: "https://your-kora-instance",
  rpcUrl: "https://api.mainnet-beta.solana.com",
  feeToken: address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), // USDC
  feePayerWallet: sender // Signs to authorize the token fee payment
});

// Same instruction, same call—Kora sponsors the network fee
const { context } = await kora.sendTransaction([transferInstruction]);
console.log("Signature:", context.signature);
```

### Kora Resources

<Cards>
  <Card
    title="Kora Quick Start"
    href="/docs/tools/kora/getting-started/quick-start"
  >
    Get Kora running locally in minutes.
  </Card>
  <Card title="Full Transaction Demo" href="/docs/tools/kora/guides/full-demo">
    Complete fee abstraction transaction implementation guide.
  </Card>
  <Card title="API Reference" href="/docs/tools/kora/json-rpc-api">
    JSON-RPC methods and SDK documentation.
  </Card>
  <Card title="Node Operator Guide" href="/docs/tools/kora/operators">
    Deploy and configure your own Kora instance.
  </Card>
</Cards>
