> ## Documentation Index
> Fetch the complete documentation index at: https://www.zkcompression.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Gasless Transactions

> Abstract SOL fees so users never hold SOL. The Light SDK sponsors rent-exemption for Solana accounts and keeps accounts active through periodic top-ups paid by the fee payer. Sponsor top-ups and transaction fees by setting your application as the fee payer.

***

## Sponsor Top-ups and Transaction Fees

<Accordion title="Prerequisite: How Rent Sponsorship with Light Token works">
  Rent sponsorship is a built-in feature of the Light SDK’s that sponsors rent-exemption.
  This is dealt with under the hood in a way that doesn’t disrupt the UX of what your users are used to with SPL-token.

  **Rent-exemption: paid by a rent sponsor PDA.**

  **Top-ups: paid by the fee payer.**

  The `feePayer` on the transaction bumps a small virtual rent balance (766 lamports by default)
  on each write to keep the account active (hot balance).
  Set your application as the fee payer so users never interact with SOL.

  **Hot-Cold Lifecycle of Accounts.**

  Accounts get auto-compressed (cold balance) when the virtual rent balance goes below a threshold (eg 24h without write bump).
  The cold account's state is cryptographically preserved on the Solana ledger.
  Users only interact with hot accounts and load the cold balance in-flight when using the account again.

  <div className="block dark:hidden">
    <Frame>
      <img src="https://mintcdn.com/luminouslabs-cc5545c6/c8IlA_YQ_pxDvBKq/images/account-lifecycle.png?fit=max&auto=format&n=c8IlA_YQ_pxDvBKq&q=85&s=279bbee42bce24f419ba8d8c229f3b6e" alt="Account lifecycle" width="1199" height="805" data-path="images/account-lifecycle.png" />
    </Frame>
  </div>

  <div className="hidden dark:block">
    <Frame>
      <img src="https://mintcdn.com/luminouslabs-cc5545c6/c8IlA_YQ_pxDvBKq/images/account-lifecycle-dark.png?fit=max&auto=format&n=c8IlA_YQ_pxDvBKq&q=85&s=b41a6fc1f9c7a9d7df2dbc3e0b03639c" alt="Account lifecycle" width="1182" height="788" data-path="images/account-lifecycle-dark.png" />
    </Frame>
  </div>
</Accordion>

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

Light Token extends this: The `payer` parameter on any Light Token
instruction determines who pays rent top-ups in addition to transaction fees.
Set your application as the payer so users never interact with SOL.

In total, your sponsor covers three costs:

|                                     | Light                        | SPL / Token 2022          |
| :---------------------------------- | :--------------------------- | :------------------------ |
| **Transfer**                        | *\~\$0.001*                  | *\~\$0.001*               |
| **Create Token Account**            | **\~\$0.001** (0.000017 SOL) | **\~\$0.29** (0.0029 SOL) |
| **Transfer + Create Token Account** | **\~\$0.001** (0.000017 SOL) | **\~\$0.29** (0.0029 SOL) |

<Accordion title="Technical Cost Breakdown">
  |                                          |                                           |                                                                                                                                       |
  | :--------------------------------------- | :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ |
  | **Account creation**                     | 17,400 lamports                           | Initial bump on virtual rent balance. Rent-exemption is sponsored.                                                                    |
  | **Rent top-ups**                         | \~766 lamports per write                  | Fee payer bumps the virtual rent balance on each write to keep accounts active. Set `payer` parameter on any Light Token instruction. |
  | **Transaction fees**<br />+ Priority Fee | 5,000 lamports<br />+ Priority Fee per tx | Standard Solana fee payer. Set `feePayer` on the transaction.                                                                         |
</Accordion>

## Send a gasless transfer

<Note>
  Find a full code example [here](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/gasless-transactions/typescript/gasless-transfer.ts).
</Note>

<Tabs>
  <Tab title="TypeScript Client">
    <Steps>
      <Step title="Create a sponsor account">
        Generate or load a keypair for the sponsor who will pay transaction fees and rent top-ups.
        The sponsor needs SOL but doesn't need to hold the tokens being transferred.

        ```typescript theme={null}
        import { Keypair } from "@solana/web3.js";
        import { createRpc } from "@lightprotocol/stateless.js";

        const rpc = createRpc(RPC_ENDPOINT);

        // Sponsor: your application server
        const sponsor = Keypair.fromSecretKey(/* your server keypair */);

        // User: only signs to authorize the transfer
        const sender = Keypair.fromSecretKey(/* user's keypair */);
        ```
      </Step>

      <Step title="Create the transfer instruction">
        Create the transfer instruction with the sponsor as `payer` and the sender as `authority`.
        The sender owns the tokens and must sign the transfer.

        ```typescript theme={null}
        import { createTransferInterfaceInstructions } from "@lightprotocol/compressed-token/unified";

        const instructions = await createTransferInterfaceInstructions(
          rpc,
          sponsor.publicKey,      // payer: covers rent top-ups and transaction fees
          mint,
          amount,
          sender.publicKey,       // authority: user signs to authorize
          recipient.publicKey
        );
        ```
      </Step>

      <Step title="Send with both signers">
        Both the sponsor and sender must sign the transaction:

        |                       | Parameter               | What it does                                                                                     |
        | :-------------------- | :---------------------- | :----------------------------------------------------------------------------------------------- |
        | **Payer** (fee payer) | First positional arg    | Signs to authorize payment of rent top-ups and transaction fees. Can be your application server. |
        | **Authority** (owner) | `owner` / authority arg | Signs to authorize the token transfer. The account holder.                                       |

        ```typescript theme={null}
        import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";

        for (const ixs of instructions) {
          const tx = new Transaction().add(...ixs);
          // Both sponsor and sender must sign
          await sendAndConfirmTransaction(rpc, tx, [sponsor, sender]);
        }
        ```
      </Step>
    </Steps>

    <Accordion title="Full example">
      ```typescript theme={null}
      import "dotenv/config";
      import {
          Keypair,
          Transaction,
          sendAndConfirmTransaction,
      } from "@solana/web3.js";
      import { createRpc } from "@lightprotocol/stateless.js";
      import {
          getAssociatedTokenAddressInterface,
          getAtaInterface,
      } from "@lightprotocol/compressed-token";
      import { createTransferInterfaceInstructions } from "@lightprotocol/compressed-token/unified";
      import { homedir } from "os";
      import { readFileSync } from "fs";
      import { setup } from "./setup.js";

      // devnet:
      // const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
      // const rpc = createRpc(RPC_URL);
      // localnet:
      const rpc = createRpc();

      const sponsor = Keypair.fromSecretKey(
          new Uint8Array(
              JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")),
          ),
      );
      const sender = Keypair.generate();
      const recipient = Keypair.generate();

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

      (async function () {
          const { mint } = await setup(rpc, sponsor, sender, recipient);

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

          // Derive ATAs
          const senderAta = getAssociatedTokenAddressInterface(mint, sender.publicKey);
          const recipientAta = getAssociatedTokenAddressInterface(
              mint,
              recipient.publicKey,
          );

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

          // Create transfer instruction
          const instructions = await createTransferInterfaceInstructions(
              rpc,
              sponsor.publicKey,
              mint,
              500_000,
              sender.publicKey,
              recipient.publicKey,
          );

          // Send — sponsor pays fees, sender authorizes transfer
          let signature: string;
          for (const ixs of instructions) {
              const tx = new Transaction().add(...ixs);
              signature = await sendAndConfirmTransaction(rpc, tx, [sponsor, sender]);
          }

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

          // Verify balances
          const { parsed: senderBalance } = await getAtaInterface(
              rpc,
              senderAta,
              sender.publicKey,
              mint,
          );
          const { parsed: recipientBalance } = await getAtaInterface(
              rpc,
              recipientAta,
              recipient.publicKey,
              mint,
          );

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

          // Verify fee payer
          const tx = await rpc.getTransaction(signature!, {
              maxSupportedTransactionVersion: 0,
          });
          const feePayer = tx?.transaction.message.accountKeys[0];
          console.log(
              "\nNote: The first account in accountKeys is always the fee payer",
          );
          console.log("Fee Payer Address:", feePayer?.toBase58());
      })();
      ```
    </Accordion>
  </Tab>

  <Tab title="Rust Client">
    <Steps>
      <Step title="Create a sponsor account">
        Load or generate a keypair for the sponsor. The sponsor needs SOL but doesn't need to hold the tokens being transferred.

        ```rust theme={null}
        use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer};

        // Sponsor: your application server
        let sponsor: Keypair = /* your server keypair */;

        // User: only signs to authorize the transfer
        let sender: Keypair = /* user's keypair */;
        let recipient: Pubkey = /* recipient address */;
        let mint: Pubkey = /* e.g. USDC mint */;
        ```
      </Step>

      <Step title="Create the transfer instruction">
        Build the transfer instruction with the sponsor as `payer` and the sender as `authority`.

        ```rust theme={null}
        use light_token::instruction::{
            get_associated_token_address, TransferInterface, LIGHT_TOKEN_PROGRAM_ID,
        };

        let sender_ata = get_associated_token_address(&sender.pubkey(), &mint);
        let recipient_ata = get_associated_token_address(&recipient, &mint);

        let transfer_ix = TransferInterface {
            source: sender_ata,
            destination: recipient_ata,
            amount: 500_000,
            decimals: 6,
            authority: sender.pubkey(),       // user signs to authorize
            payer: sponsor.pubkey(),          // sponsor covers rent top-ups and transaction fees
            spl_interface: None,
            source_owner: LIGHT_TOKEN_PROGRAM_ID,
            destination_owner: LIGHT_TOKEN_PROGRAM_ID,
        }
        .instruction()?;
        ```
      </Step>

      <Step title="Send with both signers">
        Both the sponsor and sender must sign the transaction.

        ```rust theme={null}
        let sig = rpc
            .create_and_send_transaction(
                &[transfer_ix],
                &sponsor.pubkey(),          // fee payer
                &[&sponsor, &sender],       // both sign
            )
            .await?;

        println!("Tx: {sig}");
        ```
      </Step>
    </Steps>

    <Accordion title="Full example">
      ```rust theme={null}
      use light_client::rpc::Rpc;
      use light_program_test::{LightProgramTest, ProgramTestConfig};
      use light_token::instruction::{
          get_associated_token_address, CreateAssociatedTokenAccount,
          TransferInterface, LIGHT_TOKEN_PROGRAM_ID,
      };
      use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer};

      #[tokio::main]
      async fn main() -> Result<(), Box<dyn std::error::Error>> {
          let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(true, None)).await?;

          // Top-Up Sponsor: your application server, pays SOL for rent top-ups
          let sponsor = rpc.get_payer().insecure_clone();

          // User: only signs to authorize the transfer
          let sender: Keypair = /* user's keypair */;
          let recipient: Pubkey = /* recipient address */;
          let mint: Pubkey = /* e.g. USDC mint */;
          let decimals = 6u8;

          let sender_ata = get_associated_token_address(&sender.pubkey(), &mint);
          let recipient_ata = get_associated_token_address(&recipient, &mint);

          // Create recipient associated token account if it doesn't exist
          let create_ata_ix =
              CreateAssociatedTokenAccount::new(sponsor.pubkey(), recipient, mint).instruction()?;
          rpc.create_and_send_transaction(&[create_ata_ix], &sponsor.pubkey(), &[&sponsor])
              .await?;

          let transfer_ix = TransferInterface {
              source: sender_ata,
              destination: recipient_ata,
              amount: 500_000,
              decimals,
              authority: sender.pubkey(),
              payer: sponsor.pubkey(),
              spl_interface: None,
              source_owner: LIGHT_TOKEN_PROGRAM_ID,
              destination_owner: LIGHT_TOKEN_PROGRAM_ID,
          }
          .instruction()?;

          let sig = rpc
              .create_and_send_transaction(&[transfer_ix], &sponsor.pubkey(), &[&sponsor, &sender])
              .await?;

          println!("Tx: {sig}");

          Ok(())
      }
      ```
    </Accordion>
  </Tab>

  <Tab title="AI Prompt">
    <Prompt description="Gasless transactions for Light Token users" actions={["copy", "cursor"]}>
      {`---
            description: Gasless transactions for Light Token users
            allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
            ---

            ## Gasless transactions for Light Token users

            Context:
            - Guide: https://zkcompression.com/light-token/wallets/gasless-transactions
            - Skills and resources index: https://zkcompression.com/skill.md
            - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
            - Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments
            - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js

            How rent sponsorship works:
            - A rent sponsor PDA pays the rent-exemption cost on account creation so creators never lock up SOL
            - The fee payer bumps a small virtual rent balance (766 lamports) on each write to keep the account active (hot)
            - Set your application as the fee payer so users never pay rent or top-ups

            Key APIs:
            | Language   | Function                                        | Payer parameter                           |
            | TypeScript | createLightTokenTransferInstruction()            | 5th argument: \`sponsor.publicKey\`         |
            | TypeScript | createAtaInterface()                             | 2nd argument: \`sponsor\` (Keypair)         |
            | Rust       | TransferInterface { ..., payer: sponsor.pubkey() } | \`payer\` field                           |
            | Rust       | CreateAssociatedTokenAccount::new(sponsor, ...)  | 1st argument: sponsor pubkey              |

            ### 1. Index project
            - Grep \`feePayer|payer|createTransferInstruction|createLightTokenTransferInstruction|@lightprotocol|@solana/spl-token|Connection\` across src/
            - Glob \`**/*.ts\` and \`**/*.tsx\` for project structure
            - Identify: existing token operations, RPC config, fee payer setup, signing flow
            - Check package.json for existing @lightprotocol/* dependencies
            - Task subagent (Grep/Read/WebFetch) if project has multiple packages to scan in parallel

            ### 2. Read references
            - WebFetch the guide above — review TypeScript and Rust code examples
            - WebFetch skill.md — check for a dedicated skill and resources matching this task
            - TaskCreate one todo per phase below to track progress

            ### 3. Clarify intention
            - AskUserQuestion: what is the goal? (add sponsored top-ups to existing light-token code, new integration from scratch, migrate from user-pays to sponsor-pays)
            - AskUserQuestion: TypeScript or Rust client?
            - AskUserQuestion: which operations need sponsoring? (transfers only, ATA creation + transfers, all write operations)
            - Summarize findings and wait for user confirmation before implementing

            ### 4. Create plan
            - Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
            - Key pattern: set the \`payer\` parameter to the sponsor's public key on transfer and ATA creation instructions
            - The sponsor Keypair must sign the transaction alongside the user
            - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
            - Present the plan to the user for approval before proceeding

            ### 5. Implement
            - Add deps if missing: Bash \`npm install @lightprotocol/compressed-token @lightprotocol/stateless.js\`
            - Set up RPC: \`createRpc(RPC_ENDPOINT)\` with a ZK Compression endpoint (Helius, Triton)
            - Import from \`@lightprotocol/compressed-token\` for the token APIs
            - Set sponsor as payer: \`createLightTokenTransferInstruction(senderAta, recipientAta, sender.publicKey, amount, sponsor.publicKey)\`
            - Sign with both sponsor and sender: \`sendAndConfirmTransaction(rpc, tx, [sponsor, sender])\`
            - Follow the guide and the approved plan
            - Write/Edit to create or modify files
            - TaskUpdate to mark each step done

            ### 6. Verify
            - Bash \`tsc --noEmit\`
            - Bash run existing test suite if present
            - TaskUpdate to mark complete

            ### Tools
            - mcp__zkcompression__SearchLightProtocol("<query>") for API details
            - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
            - Task subagent with Grep/Read/WebFetch for parallel lookups
            - TaskList to check remaining work`}
    </Prompt>

    ```text theme={null}
    ---
    description: Gasless transactions for Light Token users
    allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__zkcompression
    ---

    ## Gasless transactions for Light Token users

    Context:
    - Guide: https://zkcompression.com/light-token/wallets/gasless-transactions
    - Skills and resources index: https://zkcompression.com/skill.md
    - SPL to Light reference: https://zkcompression.com/api-reference/solana-to-light-comparison
    - Dedicated skill: https://github.com/Lightprotocol/skills/tree/main/skills/payments
    - Packages: @lightprotocol/compressed-token, @lightprotocol/stateless.js

    How rent sponsorship works:
    - A rent sponsor PDA pays the rent-exemption cost on account creation so creators never lock up SOL
    - The fee payer bumps a small virtual rent balance (766 lamports) on each write to keep the account active (hot)
    - Set your application as the fee payer so users never pay rent or top-ups

    Key APIs:
    | Language   | Function                                        | Payer parameter                           |
    | TypeScript | createLightTokenTransferInstruction()            | 5th argument: `sponsor.publicKey`         |
    | TypeScript | createAtaInterface()                             | 2nd argument: `sponsor` (Keypair)         |
    | Rust       | TransferInterface { ..., payer: sponsor.pubkey() } | `payer` field                           |
    | Rust       | CreateAssociatedTokenAccount::new(sponsor, ...)  | 1st argument: sponsor pubkey              |

    ### 1. Index project
    - Grep `feePayer|payer|createTransferInstruction|createLightTokenTransferInstruction|@lightprotocol|@solana/spl-token|Connection` across src/
    - Glob `**/*.ts` and `**/*.tsx` for project structure
    - Identify: existing token operations, RPC config, fee payer setup, signing flow
    - Check package.json for existing @lightprotocol/* dependencies
    - Task subagent (Grep/Read/WebFetch) if project has multiple packages to scan in parallel

    ### 2. Read references
    - WebFetch the guide above — review TypeScript and Rust code examples
    - WebFetch skill.md — check for a dedicated skill and resources matching this task
    - TaskCreate one todo per phase below to track progress

    ### 3. Clarify intention
    - AskUserQuestion: what is the goal? (add sponsored top-ups to existing light-token code, new integration from scratch, migrate from user-pays to sponsor-pays)
    - AskUserQuestion: TypeScript or Rust client?
    - AskUserQuestion: which operations need sponsoring? (transfers only, ATA creation + transfers, all write operations)
    - Summarize findings and wait for user confirmation before implementing

    ### 4. Create plan
    - Based on steps 1–3, draft an implementation plan: which files to modify, what code to add, dependency changes
    - Key pattern: set the `payer` parameter to the sponsor's public key on transfer and ATA creation instructions
    - The sponsor Keypair must sign the transaction alongside the user
    - If anything is unclear or ambiguous, loop back to step 3 (AskUserQuestion)
    - Present the plan to the user for approval before proceeding

    ### 5. Implement
    - Add deps if missing: Bash `npm install @lightprotocol/compressed-token @lightprotocol/stateless.js`
    - Set up RPC: `createRpc(RPC_ENDPOINT)` with a ZK Compression endpoint (Helius, Triton)
    - Import from `@lightprotocol/compressed-token` for the token APIs
    - Set sponsor as payer: `createLightTokenTransferInstruction(senderAta, recipientAta, sender.publicKey, amount, sponsor.publicKey)`
    - Sign with both sponsor and sender: `sendAndConfirmTransaction(rpc, tx, [sponsor, sender])`
    - Follow the guide and the approved plan
    - Write/Edit to create or modify files
    - TaskUpdate to mark each step done

    ### 6. Verify
    - Bash `tsc --noEmit`
    - Bash run existing test suite if present
    - TaskUpdate to mark complete

    ### Tools
    - mcp__zkcompression__SearchLightProtocol("<query>") for API details
    - mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "<q>") for architecture
    - Task subagent with Grep/Read/WebFetch for parallel lookups
    - TaskList to check remaining work
    ```
  </Tab>
</Tabs>

## Related guides

<CardGroup cols={2}>
  <Card title="Verify payments" icon="magnifying-glass" href="/light-token/payments/verify-payments">
    Query balances and transaction history.
  </Card>

  <Card title="Batch payments" icon="layer-group" href="/light-token/payments/batch-payments">
    Pack multiple transfers into one transaction.
  </Card>
</CardGroup>

***

## Didn't find what you were looking for?

<Callout type="info">
  Reach out! [Telegram](https://t.me/swen_light) | [email](mailto:support@lightprotocol.com) | [Discord](https://discord.com/invite/7cJ8BhAXhu)
</Callout>
