Skip to main content

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.
Account lifecycle
Account lifecycle
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:
Account creation11,000 lamportsInitial bump on virtual rent balance. Rent-exemption is sponsored.
Rent top-ups~766 lamports per writeFee payer bumps the virtual rent balance on each write to keep accounts active. Set payer parameter on any Light Token instruction.
Transaction fees
+ Priority Fee
5,000 lamports
+ Priority Fee per tx
Standard Solana fee payer. Set feePayer on the transaction.
LightSPL / Token 2022
Transfer~$0.001~$0.001
Create Token Account~$0.001 (0.00001 SOL)~$0.30 (0.0029 SOL)
Transfer + Create Token Account~$0.001 (0.00001 SOL)~$0.30 (0.0029 SOL)

Send a gasless transfer

Find a full code example here.
1

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.
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 */);
2

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.
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
);
3

Send with both signers

Both the sponsor and sender must sign the transaction:
ParameterWhat it does
Payer (fee payer)First positional argSigns to authorize payment of rent top-ups and transaction fees. Can be your application server.
Authority (owner)owner / authority argSigns to authorize the token transfer. The account holder.
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]);
}
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());
})();

Basic payment

Send a single token transfer.

Receive payments

Load cold accounts and prepare to receive.

Didn’t find what you were looking for?

Reach out! Telegram | email | Discord