Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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:

  1. Openfort receives a transaction from your application.
  2. Openfort validates and signs the transaction as the fee payer.
  3. 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

MethodDescription
signAndSendTransactionSign and broadcast a transaction to the network
signTransactionSign a transaction without broadcasting
transferTransactionCreate a sponsored token transfer transaction

Fee estimation

MethodDescription
estimateTransactionFeeEstimate transaction fee in lamports and tokens
getSupportedTokensList tokens accepted for fee payment

Configuration

MethodDescription
getConfigGet server configuration and enabled methods
getPayerSignerGet payer and signer addresses
getBlockhashGet 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

  1. KoraClient initialization. Use apiKey with your Openfort publishable key prefixed with Bearer.
  2. Fee payer signer. Retrieve the signer address that pays transaction fees.
  3. Payment instruction. Get the payment instruction that transfers fee tokens to the sponsor.
  4. Transaction signing. The user signs first, then Kora co-signs as the fee payer.
  5. 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:

  1. Openfort's fee payer account becomes the first signer.
  2. The user signs the transaction for authorization.
  3. Openfort adds the fee payer signature.
  4. 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:

ClusterEndpointDescription
devnethttps://api.openfort.io/rpc/solana/devnetDevelopment and testing
mainnet-betahttps://api.openfort.io/rpc/solana/mainnet-betaProduction network

Next steps

Copyright © 2023-present Alamas Labs, Inc