Solana Paymaster
The Openfort Solana Paymaster enables you to sponsor transaction fees for your users on Solana. By integrating with Openfort, you can provide gasless experiences where users don't need to hold SOL to interact with your application.
Overview
Solana has native support for fee sponsorship through fee payers. A fee payer is an account that covers transaction fees on behalf of the user.
Fee sponsorship on Solana works as follows:
- Openfort receives a transaction from your application.
- Openfort validates and signs the transaction as the fee payer.
- Openfort returns the signed transaction for execution.
When the transaction is executed on Solana, the fee payer account pays for the transaction fees instead of the user.
Getting started
To use Solana fee sponsorship, make JSON-RPC requests to:
https://api.openfort.io/rpc/solana/{cluster}Replace {cluster} with devnet or mainnet-beta. Include your Openfort publishable key in the Authorization header:
Authorization: Bearer {{YOUR_OPENFORT_PUBLISHABLE_KEY}}Get your public key from the Openfort Dashboard.
Available endpoints
Transaction signing
| Method | Description |
|---|---|
signAndSendTransaction | Sign and broadcast a transaction to the network |
signTransaction | Sign a transaction without broadcasting |
transferTransaction | Create a sponsored token transfer transaction |
Fee estimation
| Method | Description |
|---|---|
estimateTransactionFee | Estimate transaction fee in lamports and tokens |
getSupportedTokens | List tokens accepted for fee payment |
Configuration
| Method | Description |
|---|---|
getConfig | Get server configuration and enabled methods |
getPayerSigner | Get payer and signer addresses |
getBlockhash | Get latest blockhash from the network |
Full SDK example
This comprehensive example demonstrates a complete gasless transaction flow using the Kora SDK with Openfort's fee sponsorship:
import { KoraClient } from "@solana/kora";
import {
createKeyPairSignerFromBytes,
getBase58Encoder,
createNoopSigner,
address,
getBase64EncodedWireTransaction,
partiallySignTransactionMessageWithSigners,
partiallySignTransaction,
createSolanaRpc,
createSolanaRpcSubscriptions,
pipe,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
type Blockhash,
type Base64EncodedWireTransaction,
type TransactionVersion,
type Instruction,
type KeyPairSigner,
type MicroLamports,
} from "@solana/kit";
import { createRecentSignatureConfirmationPromiseFactory } from "@solana/transaction-confirmation";
import {
updateOrAppendSetComputeUnitLimitInstruction,
updateOrAppendSetComputeUnitPriceInstruction,
} from "@solana-program/compute-budget";
// Configuration
const CONFIG = {
computeUnitLimit: 200_000,
computeUnitPrice: 1_000_000n as MicroLamports,
transactionVersion: 0,
solanaRpcUrl: "https://api.devnet.solana.com",
solanaWsUrl: "wss://api.devnet.solana.com",
koraRpcUrl: "https://api.openfort.io/rpc/solana/devnet",
};
// Initialize the Kora client with API key authentication
const client = new KoraClient({
rpcUrl: CONFIG.koraRpcUrl,
apiKey: "Bearer {{PUBLISHABLE_KEY}}",
});
// Initialize Solana RPC connections
const rpc = createSolanaRpc(CONFIG.solanaRpcUrl);
const rpcSubscriptions = createSolanaRpcSubscriptions(CONFIG.solanaWsUrl);
const confirmTransaction = createRecentSignatureConfirmationPromiseFactory({
rpc,
rpcSubscriptions,
});
async function executeGaslessTransaction(
senderKeypair: KeyPairSigner,
destinationAddress: string
) {
// Step 1: Get the fee payer signer address from Kora
const { signer_address } = await client.getPayerSigner();
const noopSigner = createNoopSigner(address(signer_address));
// Step 2: Get available payment tokens
const config = await client.getConfig();
const paymentToken = config.validation_config.allowed_spl_paid_tokens[0];
// Step 3: Create transfer instructions
const transferTokens = await client.transferTransaction({
amount: 100_000, // 0.10 USDC (6 decimals)
token: paymentToken,
source: senderKeypair.address,
destination: destinationAddress,
signer_key: signer_address,
});
const instructions = transferTokens.instructions;
// Step 4: Build estimate transaction to get payment instruction
const latestBlockhash = await client.getBlockhash();
const estimateTransaction = pipe(
createTransactionMessage({ version: CONFIG.transactionVersion as TransactionVersion }),
(tx) => setTransactionMessageFeePayerSigner(noopSigner, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash({
blockhash: latestBlockhash.blockhash as Blockhash,
lastValidBlockHeight: 0n,
}, tx),
(tx) => updateOrAppendSetComputeUnitPriceInstruction(CONFIG.computeUnitPrice, tx),
(tx) => updateOrAppendSetComputeUnitLimitInstruction(CONFIG.computeUnitLimit, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx),
);
const signedEstimate = await partiallySignTransactionMessageWithSigners(estimateTransaction);
const base64Estimate = getBase64EncodedWireTransaction(signedEstimate);
// Step 5: Get payment instruction from Kora
const { payment_instruction } = await client.getPaymentInstruction({
transaction: base64Estimate,
fee_token: paymentToken,
source_wallet: senderKeypair.address,
signer_key: signer_address,
});
// Step 6: Build final transaction with payment instruction
const newBlockhash = await client.getBlockhash();
const fullTransaction = pipe(
createTransactionMessage({ version: CONFIG.transactionVersion as TransactionVersion }),
(tx) => setTransactionMessageFeePayerSigner(noopSigner, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash({
blockhash: newBlockhash.blockhash as Blockhash,
lastValidBlockHeight: 0n,
}, tx),
(tx) => updateOrAppendSetComputeUnitPriceInstruction(CONFIG.computeUnitPrice, tx),
(tx) => updateOrAppendSetComputeUnitLimitInstruction(CONFIG.computeUnitLimit, tx),
(tx) => appendTransactionMessageInstructions([...instructions, payment_instruction], tx),
);
// Step 7: Sign with user keypair
const signedFull = await partiallySignTransactionMessageWithSigners(fullTransaction);
const userSigned = await partiallySignTransaction([senderKeypair.keyPair], signedFull);
const base64Full = getBase64EncodedWireTransaction(userSigned);
// Step 8: Get Kora's signature and send to network
const { signed_transaction } = await client.signTransaction({
transaction: base64Full,
signer_key: signer_address,
});
const signature = await rpc
.sendTransaction(signed_transaction as Base64EncodedWireTransaction, { encoding: "base64" })
.send();
// Step 9: Confirm transaction
await confirmTransaction({
commitment: "confirmed",
signature,
abortSignal: new AbortController().signal,
});
return signature;
}Key concepts
- KoraClient initialization. Use
apiKeywith your Openfort publishable key prefixed withBearer. - Fee payer signer. Retrieve the signer address that pays transaction fees.
- Payment instruction. Get the payment instruction that transfers fee tokens to the sponsor.
- Transaction signing. The user signs first, then Kora co-signs as the fee payer.
- Submission. Send the fully signed transaction to the Solana network.
Fee payer mechanics
On Solana, the fee payer is determined by the first signer of the transaction. When Openfort sponsors a transaction:
- Openfort's fee payer account becomes the first signer.
- The user signs the transaction for authorization.
- Openfort adds the fee payer signature.
- The transaction is submitted with Openfort's account paying fees.
Solana's fee sponsorship is handled at the transaction level using native multi-signature capabilities.
Supported clusters
Openfort supports the following Solana clusters:
| Cluster | Endpoint | Description |
|---|---|---|
devnet | https://api.openfort.io/rpc/solana/devnet | Development and testing |
mainnet-beta | https://api.openfort.io/rpc/solana/mainnet-beta | Production network |
Next steps
- Endpoint Reference - Detailed documentation for all Solana Paymaster methods
- Error Reference - Common errors and how to resolve them
- Solana Integration - Sample application using Openfort with Solana