Fee Sponsorship
Overview
Fee sponsorship is a native feature in Solana that allows for one account to pay transaction fees for another account. This is useful if you want to abstract away holding SOL from your users or provide a fee-free experience. This is also often referred to as "gas abstraction" on other blockchains.
How fee payment works
Every transaction requires a transaction fee, paid in SOL. This fee is paid by the "fee payer" of the transaction. Any EOA that signs the transaction can be the fee payer which enables native fee sponsorship or "gas abstraction" for Solana. This capability extends to all transaction types including token transfers, DeFi actions, and more. For more information on how fees are determined see the Transaction Fees page.
How to sponsor fees on a transaction
To sponsor fees for other users, you need to create a transaction with the
feePayer field set to the public key of the EOA that will pay the fees. The
following example shows how to transfer a token from one user to another and
sponsor the fees for the transaction.
import {airdropFactory,appendTransactionMessageInstructions,createSolanaRpc,createSolanaRpcSubscriptions,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners} from "@solana/kit";import { getCreateAccountInstruction } from "@solana-program/system";import {getCreateAssociatedTokenInstructionAsync,getInitializeMintInstruction,getMintSize,TOKEN_PROGRAM_ADDRESS,findAssociatedTokenPda,getMintToInstruction,getTransferInstruction,fetchToken} from "@solana-program/token";// Create Connection, local validator in this exampleconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate keypairs for fee payer, sender and recipientconst feePayer = await generateKeyPairSigner();const sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();console.log("Fee Payer Address:", feePayer.address.toString());console.log("Sender Address:", sender.address.toString());console.log("Recipient Address:", recipient.address.toString());// Fund fee payerawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: feePayer.address,lamports: lamports(1_000_000_000n),commitment: "confirmed"});// Generate keypair to use as address of mintconst mint = await generateKeyPairSigner();console.log("Mint Address:", mint.address.toString());// Get default mint account size (in bytes), no extensions enabledconst space = BigInt(getMintSize());// Get minimum balance for rent exemptionconst rent = await rpc.getMinimumBalanceForRentExemption(space).send();// Get latest blockhash to include in transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Instruction to create new account for mint (token program)// Invokes the system programconst createAccountInstruction = getCreateAccountInstruction({payer: feePayer,newAccount: mint,lamports: rent,space,programAddress: TOKEN_PROGRAM_ADDRESS});// Instruction to initialize mint account data// Invokes the token programconst initializeMintInstruction = getInitializeMintInstruction({mint: mint.address,decimals: 2,mintAuthority: sender.address});// Derive the ATAs for sender and recipientconst [senderAssociatedTokenAddress] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_PROGRAM_ADDRESS});const [recipientAssociatedTokenAddress] = await findAssociatedTokenPda({mint: mint.address,owner: recipient.address,tokenProgram: TOKEN_PROGRAM_ADDRESS});console.log("Sender Associated Token Account Address:",senderAssociatedTokenAddress.toString());console.log("Recipient Associated Token Account Address:",recipientAssociatedTokenAddress.toString());// Create instruction for sender's ATAconst createSenderAtaInstruction =await getCreateAssociatedTokenInstructionAsync({payer: feePayer,mint: mint.address,owner: sender.address});// Create instruction for recipient's ATAconst createRecipientAtaInstruction =await getCreateAssociatedTokenInstructionAsync({payer: feePayer,mint: mint.address,owner: recipient.address});// Create instruction to mint tokens to senderconst mintToInstruction = getMintToInstruction({mint: mint.address,token: senderAssociatedTokenAddress,mintAuthority: sender.address,amount: 100n});// Combine all instructions in orderconst instructions = [createAccountInstruction, // Create mint accountinitializeMintInstruction, // Initialize mintcreateSenderAtaInstruction, // Create sender's ATAcreateRecipientAtaInstruction, // Create recipient's ATAmintToInstruction // Mint tokens to sender];// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions(instructions, tx));// Sign transaction message with all required signersconst signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Send and confirm transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });// Get transaction signatureconst transactionSignature = getSignatureFromTransaction(signedTransaction);console.log("Transaction Signature:", transactionSignature);console.log("Successfully minted 1.0 tokens");// Get a fresh blockhash for the transfer transactionconst { value: transferBlockhash } = await rpc.getLatestBlockhash().send();// Create instruction to transfer tokensconst transferInstruction = getTransferInstruction({source: senderAssociatedTokenAddress,destination: recipientAssociatedTokenAddress,authority: sender.address,amount: 50n // 0.50 tokens with 2 decimals});// Create transaction message for token transferconst transferTxMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(transferBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));// Sign transaction message with all required signersconst signedTransferTx =await signTransactionMessageWithSigners(transferTxMessage);// Send and confirm transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransferTx,{ commitment: "confirmed" });// Get transaction signatureconst transactionSignature2 = getSignatureFromTransaction(signedTransferTx);console.log("Transaction Signature:", transactionSignature2);console.log("Successfully transferred 0.5 tokens");const senderTokenAccount = await fetchToken(rpc, senderAssociatedTokenAddress, {commitment: "confirmed"});const recipientTokenAccount = await fetchToken(rpc,recipientAssociatedTokenAddress,{commitment: "confirmed"});const senderBalance = senderTokenAccount.data.amount;const recipientBalance = recipientTokenAccount.data.amount;console.log("=== Final Balances ===");console.log("Sender balance:", Number(senderBalance) / 100, "tokens");console.log("Recipient balance:", Number(recipientBalance) / 100, "tokens");
How to run a fee relayer service
A fee relayer service is a service that allows you to relay transactions for other users. This is useful if you want to provide an application that removes the complexity of users having to manage and procure SOL. Kora, by the Solana Foundation, is a fee relayer service that allows you to relay transactions for other users. In addition to providing fee sponsorship, it can also accept fee payment in configured tokens to still be compensated for transaction processing. More information can be found in the Kora documentation.
Is this page helpful?