---
title: Kora Full Transaction Flow
description:
  This guide teaches you how to implement a complete gasless transaction flow
  using Kora RPC, including payment instructions and transaction signing.
date: 2025-08-26
---

_Last Updated: 2025-10-31_

## What You'll Build

In the Quick Start Guide, you learned how to set up Kora RPC and make basic
calls. Now we'll build a complete gasless transaction system that demonstrates
Kora's full capabilities. By the end of this guide, you'll have implemented a
transaction flow that:

- Creates multiple transfer instructions (SPL tokens and SOL)
- Obtains payment instructions from Kora for fee coverage
- Signs transactions with user keys while Kora handles gas fees
- Submits fully-signed transactions to the Solana network

The final result will be a working gasless transaction system:

```shell
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
KORA GASLESS TRANSACTION DEMO
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1/6] Initializing clients
  → Kora RPC: http://localhost:8080/
  → Solana RPC: http://127.0.0.1:8899

[2/6] Setting up keypairs
  → Sender: BYJVBqQ2xV9GECc84FeoPQy2DpgoonZQFQu97MMWTbBc
  → Destination: C8MC9E6nf9Am1rVqdDedDavm53uCJMiSwarEko1aXmny
  → Kora signer address: 3Z1Ef7YaxK8oUMoi6exf7wYZjZKWJJsrzJXSt1c3qrDE

[3/6] Creating demonstration instructions
  → Payment token: 9BgeTKqmFsPVnfYscfM6NvsgmZxei7XfdciShQ6D3bxJ
  ✓ Token transfer instruction created
  ✓ SOL transfer instruction created
  ✓ Memo instruction created
  → Total: 3 instructions

[4/6] Estimating Kora fee and assembling payment instruction
  → Fee payer: 3Z1Ef7Ya...
  → Blockhash: 7HZUaMqV...
  ✓ Estimate transaction built
  ✓ Payment instruction received from Kora

[5/6] Creating and signing final transaction (with payment)
  ✓ Final transaction built with payment
  ✓ Transaction signed by user

[6/6] Signing transaction with Kora and sending to Solana cluster
  ✓ Transaction co-signed by Kora
  ✓ Transaction submitted to network
  ⏳ Awaiting confirmation...

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SUCCESS: Transaction confirmed on Solana
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Transaction signature:
41hmwmkMfHR5mmhG9sNkjiakwHxpmr1H3Gi3bBL8v5PbsRrH7FhpUT8acHaf2mrPKNVD894dSYXfjp6LfAbVpcCE

View on explorer:
https://explorer.solana.com/tx/41hmwmkMfHR5mmhG9sNkjiakwHxpmr1H3Gi3bBL8v5PbsRrH7FhpUT8acHaf2mrPKNVD894dSYXfjp6LfAbVpcCE?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899
```

Let's build it step by step!

## Prerequisites

Before starting this tutorial, ensure you have:

- Completed the
  [Kora Quick Start Guide](/docs/tools/kora/getting-started/quick-start) - we
  will use the same testing environment as the quick start guide.
- [**Node.js**](https://nodejs.org/en/download) (LTS or later)
- [**Solana CLI**](https://solana.com/docs/intro/installation) v2.2.x or greater
- **Familiarity** with
  [Solana transactions](https://solana.com/docs/core/transactions) and
  [SPL tokens](https://solana.com/docs/tokens/basics)
- A running Kora RPC server with configured signers (see
  [Quick Start Guide](/docs/tools/kora/getting-started/quick-start) for
  instructions)

## Kora Transaction Flow

Kora enables gasless transactions by acting as a fee payer for your users'
transactions. The gasless transaction flow consists of these main steps:

1. **Transaction Creation** - Build the user's intended transaction (transfers,
   program calls, etc.)
2. **Fee Estimation** - Create an estimate transaction to calculate required
   fees
3. **Payment Instruction** - Get a payment instruction from Kora that specifies
   the fee amount
4. **User Signing** - User signs the transaction including the payment
   instruction
5. **Kora Co-signing** - Kora validates payment and co-signs as the fee payer
6. **Submission** - Submit the fully-signed transaction to Solana

_\*Note: Kora can be configured to not require payment, but we will be using it
to demonstrate the full flow._

## Project Setup

### Kora Server Considerations

- **Token Allowlist** - Only tokens configured in `kora.toml` can be used for
  payment - make sure the token defined in your `.env` is included in your
  [kora.toml](https://github.com/solana-foundation/kora/blob/v2.0.5/examples/getting-started/demo/server/kora.toml)
  allowlist.
- **Program Restrictions** - Transactions can only interact with whitelisted
  programs. We have preset the
  [kora.toml](https://github.com/solana-foundation/kora/blob/v2.0.5/examples/getting-started/demo/server/kora.toml)
  to allow interaction with the System Program, Token Program, Compute Unit
  Program, and Memo program.

### Client Setup

_This guide assumes you've completed the
[Quick Start](/docs/tools/kora/getting-started/quick-start) and have the demo
project set up. If not, please complete that first._

Navigate to your demo client directory:

```shell
cd kora/examples/getting-started/demo/client
```

> **Note**: The demo files are located in the
> [GitHub repository](https://github.com/solana-foundation/kora/tree/v2.0.5/examples/getting-started/demo)
> as they require a full development setup.

## Implementation

Before we start running the demo, let's walk through the
[full demo](https://github.com/solana-foundation/kora/blob/v2.0.5/examples/getting-started/demo/client/src/full-demo.ts)
implementation step by step:

### Imports and Configuration

Our demo starts with the necessary imports and configuration:

```ts
import { KoraClient } from "@solana/kora";
import {
  createKeyPairSignerFromBytes,
  getBase58Encoder,
  createNoopSigner,
  address,
  getBase64EncodedWireTransaction,
  partiallySignTransactionMessageWithSigners,
  Blockhash,
  Base64EncodedWireTransaction,
  partiallySignTransaction,
  TransactionVersion,
  Instruction,
  KeyPairSigner,
  Rpc,
  SolanaRpcApi,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  MicroLamports,
  appendTransactionMessageInstructions
} from "@solana/kit";
import { getAddMemoInstruction } from "@solana-program/memo";
import { createRecentSignatureConfirmationPromiseFactory } from "@solana/transaction-confirmation";
import {
  updateOrAppendSetComputeUnitLimitInstruction,
  updateOrAppendSetComputeUnitPriceInstruction
} from "@solana-program/compute-budget";
import dotenv from "dotenv";
import path from "path";

dotenv.config({ path: path.join(process.cwd(), "..", ".env") });

const CONFIG = {
  computeUnitLimit: 200_000,
  computeUnitPrice: 1_000_000n as MicroLamports,
  solanaRpcUrl: "http://127.0.0.1:8899",
  solanaWsUrl: "ws://127.0.0.1:8900",
  koraRpcUrl: "http://localhost:8080/"
};
```

We are importing the Kora Client from the
[Kora SDK](https://www.npmjs.com/package/@solana/kora) and a few types/helpers
from Solana Kit library for building transactions.

We are also creating a global configuration object that defines:

- **Compute Budget** - Units and price for transaction prioritization
- **Transaction Version** - Using V0 for address lookup table support
- **RPC Endpoints** - Local Solana and Kora RPC servers

Leave these defaults for now--after the demo, you can experiment with different
values to see how they affect the transaction flow.

### Utility Functions

The demo includes a helper function for loading keypairs from environment
variables:

```ts
async function getEnvKeyPair(envKey: string) {
  if (!process.env[envKey]) {
    throw new Error(`Environment variable ${envKey} is not set`);
  }
  const base58Encoder = getBase58Encoder();
  const b58SecretEncoded = base58Encoder.encode(process.env[envKey]);
  return await createKeyPairSignerFromBytes(b58SecretEncoded);
}
```

This function:

- Reads base58-encoded private keys from environment variables
- Encodes the private key string to a U8 byte array
- Converts them to keypair signer objects

### Step 1: Initialize Clients

First, we set up our connection to both Kora and Solana:

```ts
async function initializeClients() {
  console.log("\n[1/6] Initializing clients");
  console.log("  → Kora RPC:", CONFIG.koraRpcUrl);
  console.log("  → Solana RPC:", CONFIG.solanaRpcUrl);

  const client = new KoraClient({
    rpcUrl: CONFIG.koraRpcUrl
    // apiKey: process.env.KORA_API_KEY, // Uncomment if authentication is enabled
    // hmacSecret: process.env.KORA_HMAC_SECRET, // Uncomment if HMAC is enabled
  });

  const rpc = createSolanaRpc(CONFIG.solanaRpcUrl);
  const rpcSubscriptions = createSolanaRpcSubscriptions(CONFIG.solanaWsUrl);

  const confirmTransaction = createRecentSignatureConfirmationPromiseFactory({
    rpc,
    rpcSubscriptions
  });

  return { client, rpc, confirmTransaction };
}
```

This function:

- Creates a Kora client instance by passing in our Kora RPC URL.
- Establishes a Solana RPC connection with subscription support (we will use
  this for sending and confirming transactions to the Solana cluster)
- Sets up transaction confirmation utilities

_Note: Our
[kora.toml](https://github.com/solana-foundation/kora/blob/v2.0.5/examples/getting-started/demo/server/kora.toml)
file does not include any authentication, so we don't need to pass in an api key
or hmac secret, but we have left the commented out code in for reference._

### Step 2: Setup Keys

Load the required keypairs from environment variables and fetch the Kora signer
address:

```ts
async function setupKeys(client: KoraClient) {
  console.log("\n[2/6] Setting up keypairs");

  const testSenderKeypair = await getEnvKeyPair("TEST_SENDER_KEYPAIR");
  const destinationKeypair = await getEnvKeyPair("DESTINATION_KEYPAIR");
  const { signer_address } = await client.getPayerSigner();

  console.log("  → Sender:", testSenderKeypair.address);
  console.log("  → Destination:", destinationKeypair.address);
  console.log("  → Kora signer address:", signer_address);

  return { testSenderKeypair, destinationKeypair, signer_address };
}
```

Here we are using our `getEnvKeyPair` function to load the keypairs from the
environment variables. The keypairs represent:

- **Sender** - The user initiating the transaction and responsible for paying
  the Kora node in the payment token.
- **Destination** - The recipient of the transfers.

We also use the `getPayerSigner` method to fetch the Kora signer address. This
is the address that will be used to sign the transaction with Kora's signature.
It is important that we fetch a valid signer from the Kora node and use it
consistently throughout our transaction flow.

### Step 3: Create Demo Instructions

Next, we build a set of instructions that that our `testSender` wants to send to
the network. We will be using the Kora Client to build some of these
instructions and the @solana/programs library to build others to demonstrate how
to use both.

```ts
async function createInstructions(
  client: KoraClient,
  testSenderKeypair: KeyPairSigner,
  destinationKeypair: KeyPairSigner
) {
  console.log("\n[3/6] Creating demonstration instructions");

  const paymentToken = await client
    .getConfig()
    .then((config) => config.validation_config.allowed_spl_paid_tokens[0]);
  console.log("  → Payment token:", paymentToken);

  // Create token transfer (will initialize ATA if needed)
  const transferTokens = await client.transferTransaction({
    amount: 10_000_000, // 10 USDC (6 decimals)
    token: paymentToken,
    source: testSenderKeypair.address,
    destination: destinationKeypair.address
  });
  console.log("  ✓ Token transfer instruction created");

  // Create SOL transfer
  const transferSol = await client.transferTransaction({
    amount: 10_000_000, // 0.01 SOL (9 decimals)
    token: "11111111111111111111111111111111", // SOL mint address
    source: testSenderKeypair.address,
    destination: destinationKeypair.address
  });
  console.log("  ✓ SOL transfer instruction created");

  // Add memo instruction
  const memoInstruction = getAddMemoInstruction({
    memo: "Hello, Kora!"
  });
  console.log("  ✓ Memo instruction created");

  const instructions = [
    ...transferTokens.instructions,
    ...transferSol.instructions,
    memoInstruction
  ];

  console.log(`  → Total: ${instructions.length} instructions`);
  return { instructions, paymentToken };
}
```

There's quite a bit happening here, so let's walk through it step by step:

1. We use `getConfig` to get the payment token from Kora's configuration.
   Because we set up our server, we know there's only one token in the
   allowlist, so we can access it directly in the 1st position
   (`config.validation_config.allowed_spl_paid_tokens[0]`).
2. We create a token transfer instruction using the Kora Client's
   `transferTransaction` method. This is a helper method that makes it easy to
   create a token transfer instruction.
3. We create a SOL transfer instruction using the Kora Client's
   `transferTransaction` method. We are including this here to show how to build
   SOL transfers using the Kora Client--note that we use the Native SOL mint
   `11111111111111111111111111111111` to indicate we want to transfer SOL
   instead of an SPL token transfer.
4. We add a memo instruction using the @solana/programs library's
   `getAddMemoInstruction` function.
5. We combine all the instructions into a single array. We will use this array
   to build our estimate transaction in the next step.

### Step 4: Get Payment Instruction from Kora

Create a transaction that will generate a payment instruction to Kora in
exchange for the fees required to execute the transaction.

```ts
async function getPaymentInstruction(
  client: KoraClient,
  instructions: Instruction[],
  testSenderKeypair: KeyPairSigner,
  paymentToken: string
): Promise<{ paymentInstruction: Instruction }> {
  console.log("\n[4/6] Estimating Kora fee and assembling payment instruction");

  const { signer_address } = await client.getPayerSigner();
  const noopSigner = createNoopSigner(address(signer_address));
  const latestBlockhash = await client.getBlockhash();

  console.log("  → Fee payer:", signer_address.slice(0, 8) + "...");
  console.log("  → Blockhash:", latestBlockhash.blockhash.slice(0, 8) + "...");

  // Create estimate transaction to get payment instruction
  const estimateTransaction = pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayerSigner(noopSigner, tx),
    (tx) =>
      setTransactionMessageLifetimeUsingBlockhash(
        {
          blockhash: latestBlockhash.blockhash as Blockhash,
          lastValidBlockHeight: 0n
        },
        tx
      ),
    (tx) => appendTransactionMessageInstructions(instructions, tx),
    (tx) =>
      updateOrAppendSetComputeUnitPriceInstruction(CONFIG.computeUnitPrice, tx),
    (tx) =>
      updateOrAppendSetComputeUnitLimitInstruction(CONFIG.computeUnitLimit, tx)
  );

  const signedEstimateTransaction =
    await partiallySignTransactionMessageWithSigners(estimateTransaction);
  const base64EncodedWireTransaction = getBase64EncodedWireTransaction(
    signedEstimateTransaction
  );
  console.log("  ✓ Estimate transaction built");

  // Get payment instruction from Kora
  const paymentInstruction = await client.getPaymentInstruction({
    transaction: base64EncodedWireTransaction,
    fee_token: paymentToken,
    source_wallet: testSenderKeypair.address
  });
  console.log("  ✓ Payment instruction received from Kora");

  return { paymentInstruction: paymentInstruction.payment_instruction };
}
```

The Kora SDK has a helper method `getPaymentInstruction` that will calculate the
exact fees required for the transaction and create a payment transfer
instruction. Here's how we're using it:

1. First, we create an `estimateTransaction` that includes our desired
   instructions--this transaction will be simulated on the Kora server to
   estimate the fees required for the transaction.
2. We then partially sign the transaction to get a base64 encoded wire string.
3. We pass our base64 encoded wire transaction to the `getPaymentInstruction`
   method with the payment token and source of the payment. This will return an
   `Instruction` object that we can add to our transaction.

Key concepts here:

- **Valid Blockhash** - We use the `getBlockhash` method to get a valid
  blockhash for our transaction. This is required for estimating the transaction
  as it will simulate the transaction on the server.
- **Noop Signer** - Placeholder signer used when building transactions before
  Kora signs. This will allow us to specify a fee payer in our transaction
  before we have Kora's signature. For more information on Noop Signers, see
  [Solana Kit Documentation](https://www.solanakit.com/docs/concepts/signers#no-op-signers).
- **Partial Signing** - In order to get our transaction as a base64 encoded wire
  string (we need this to send the transaction via the Kora RPC), we need to
  partially sign the transaction. For more information on Partial Signers, see
  [Solana Kit Documentation](https://www.solanakit.com/docs/concepts/signers#partial-signers).

### Step 5: Create and Sign Final Transaction

Now that we have our payment instruction, we can create a final transaction that
includes our original instructions and the payment instruction.

```ts
async function getFinalTransaction(
  client: KoraClient,
  paymentInstruction: Instruction,
  testSenderKeypair: KeyPairSigner,
  instructions: Instruction[],
  signer_address: string
): Promise<Base64EncodedWireTransaction> {
  console.log("\n[5/6] Creating and signing final transaction (with payment)");
  const noopSigner = createNoopSigner(address(signer_address));

  // Build final transaction with payment instruction
  const newBlockhash = await client.getBlockhash();
  const fullTransaction = pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayerSigner(noopSigner, tx),
    (tx) =>
      setTransactionMessageLifetimeUsingBlockhash(
        {
          blockhash: newBlockhash.blockhash as Blockhash,
          lastValidBlockHeight: 0n
        },
        tx
      ),
    (tx) =>
      appendTransactionMessageInstructions(
        [...instructions, paymentInstruction],
        tx
      ),
    (tx) =>
      updateOrAppendSetComputeUnitPriceInstruction(CONFIG.computeUnitPrice, tx),
    (tx) =>
      updateOrAppendSetComputeUnitLimitInstruction(CONFIG.computeUnitLimit, tx)
  );
  console.log("  ✓ Final transaction built with payment");

  // Sign with user keypair
  const signedFullTransaction =
    await partiallySignTransactionMessageWithSigners(fullTransaction);
  const userSignedTransaction = await partiallySignTransaction(
    [testSenderKeypair.keyPair],
    signedFullTransaction
  );
  const base64EncodedWireFullTransaction = getBase64EncodedWireTransaction(
    userSignedTransaction
  );
  console.log("  ✓ Transaction signed by user");

  return base64EncodedWireFullTransaction;
}
```

We use the same `pipe` function to assemble our transaction. Our final
transaction includes:

- Our original instructions
- The payment instruction
- A fresh blockhash
- The same noop signer as previously used to build the estimate transaction

We then call the same `partiallySignTransactionMessageWithSigners` function to
get a base64 encoded wire string of the transaction. This time, however, we also
run `partiallySignTransaction` to sign the transaction with our
`testSenderKeypair`. Though our Kora node is paying the network fees, our
`testSender` still needs to sign to authorize the token payment and the other
transfer instructions we created. For Kora nodes that do not require payment,
certain instructions may not require this signing step. Finally, we return the
base64 encoded wire string of the transaction.

### Step 6: Submit Transaction

Finally, we need to get the Kora node to sign the transaction so we can send a
fully signed transaction to the network. We do this by calling the
`signTransaction` method on the Kora client.

```ts
async function submitTransaction(
  client: KoraClient,
  rpc: Rpc<SolanaRpcApi>,
  confirmTransaction: ReturnType<
    typeof createRecentSignatureConfirmationPromiseFactory
  >,
  signedTransaction: Base64EncodedWireTransaction,
  signer_address: string
) {
  console.log(
    "\n[6/6] Signing transaction with Kora and sending to Solana cluster"
  );

  // Get Kora's signature
  const { signed_transaction } = await client.signTransaction({
    transaction: signedTransaction,
    signer_key: signer_address
  });
  console.log("  ✓ Transaction co-signed by Kora");

  // Submit to Solana network
  const signature = await rpc
    .sendTransaction(signed_transaction as Base64EncodedWireTransaction, {
      encoding: "base64"
    })
    .send();
  console.log("  ✓ Transaction submitted to network");

  console.log("  ⏳ Awaiting confirmation...");
  await confirmTransaction({
    commitment: "confirmed",
    signature,
    abortSignal: new AbortController().signal
  });

  console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  console.log("SUCCESS: Transaction confirmed on Solana");
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  console.log("\nTransaction signature:");
  console.log(signature);

  return signature;
}
```

Here we are doing three things:

1. We call the `signTransaction` method on the Kora client to get the Kora node
   to sign the transaction. The node will introspect the transaction to ensure
   the payment is sufficient and then sign the transaction. _Note: some Kora
   nodes may enable `signTransaction` that do not require payment. You can check
   your node's configuration to see if this is enabled by running
   `getConfig()`._
2. We send the fully signed transaction to the Solana network using the Solana
   RPC client.
3. We wait for the transaction to be confirmed on the network.

### Main Orchestration Function

The main function ties everything together and calls each of our functions in
sequence:

```ts
async function main() {
  console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  console.log("KORA GASLESS TRANSACTION DEMO");
  console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

  try {
    // Step 1: Initialize clients
    const { client, rpc, confirmTransaction } = await initializeClients();

    // Step 2: Setup keys
    const { testSenderKeypair, destinationKeypair, signer_address } =
      await setupKeys(client);

    // Step 3: Create demo instructions
    const { instructions, paymentToken } = await createInstructions(
      client,
      testSenderKeypair,
      destinationKeypair
    );

    // Step 4: Get payment instruction from Kora
    const { paymentInstruction } = await getPaymentInstruction(
      client,
      instructions,
      testSenderKeypair,
      paymentToken
    );

    // Step 5: Create and partially sign final transaction
    const finalSignedTransaction = await getFinalTransaction(
      client,
      paymentInstruction,
      testSenderKeypair,
      instructions,
      signer_address
    );

    // Step 6: Get Kora's signature and submit to Solana cluster
    await submitTransaction(
      client,
      rpc,
      confirmTransaction,
      finalSignedTransaction,
      signer_address
    );
  } catch (error) {
    console.error("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    console.error("ERROR: Demo failed");
    console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    console.error("\nDetails:", error);
    process.exit(1);
  }
}
```

## Running the Full Demo

To run the complete gasless transaction demo:

### 1. Ensure Prerequisites

Set up three terminal windows:

1. Start your local test validator:

```bash
solana-test-validator -r
```

2. Start your Kora RPC server (from the examples/getting-started/demo/server
   directory):

```bash
kora rpc start --signers-config signers.toml
```

3. Initialize your environment (from the examples/getting-started/demo/client
   directory):

```bash
pnpm init-env
```

### 2. Run the Demo

```bash
# From the client directory
pnpm full-demo
```

### 3. Expected Output

You should see the step-by-step execution with a successful transaction at the
end. The transaction will:

- Transfer tokens from sender to destination
- Transfer SOL from sender to destination
- Include a "Hello, Kora!" memo message
- Pay fees to the Kora node operator in your configured SPL token
- Have transaction gas fees paid by the Kora node operator

## Recap: Understanding the Flow

Let's review what happens in this demonstration:

1. **User Intent** - User assembled a transaction that included a variety of
   instructions that they wanted to execute.
2. **Fee Estimation** - Kora calculated the transaction cost in the user's
   preferred token and created a payment instruction.
3. **Transaction Assembly** - User assembled a final transaction that included
   the user's intended instructions and the Kora payment instruction.
4. **Transaction Signing** - User partially signed the transaction with their
   keypair and sent to the Kora node for signing after verifying the payment was
   sufficient.
5. **Atomic Execution** - User sends transaction to the Solana and everything
   happens in a single transaction:
   - User's intended transfers execute
   - Payment for fees transfers to Kora
   - Kora pays the Solana network fees and signs the transaction

And like that, users do not need to hold SOL to pay for gas fees--they can pay
for everything in the tokens they already hold!

## Troubleshooting

### Common Issues

**Transaction Validation Fails**

- Verify all programs are whitelisted in `kora.toml`
- Check that token mints are in `allowed_spl_paid_tokens`
- Ensure transaction doesn't exceed `max_allowed_lamports`

**Generating Payment Instruction Fails**

- Confirm the estimate transaction has a fresh blockhash for simulation
- Verify Kora's payment address has initialized ATAs
- Check that the payment token is properly configured

**Signature Verification Fails**

- Ensure all required signers are included (Kora and any signers required for
  token payments or other instructions included in your transaction)
- Verify the transaction hasn't been modified after signing
- Check that keypairs are loaded correctly

## Wrap Up

Congratulations! You've successfully implemented a complete gasless transaction
flow with Kora.

Kora makes it possible to provide users with a seamless Web3 experience where
they never need to worry about gas fees or holding SOL. Whether you're building
a NeoBank, gaming platform, or liquid staking platform, Kora's gasless
transactions remove a major barrier to user adoption.

> **Tip:** For a simpler integration, check out the
> [Kit Client Guide](/docs/tools/kora/guides/kit-client). The
> `createKitKoraClient()` API handles blockhash management, fee estimation,
> payment instruction injection, and transaction submission automatically —
> reducing the manual steps shown in this guide to a few lines of code.

## Additional Resources

- **Need help?** Ask questions on
  [Solana Stack Exchange](https://solana.stackexchange.com/) with a `Kora` tag
- [**Kora Configuration Guide**](/docs/tools/kora/operators/configuration) -
  Detailed configuration options
- [**Signers Guide**](/docs/tools/kora/operators/signers) - Managing different
  signer types
- [**API Reference**](/docs/tools/kora/json-rpc-api) - Complete RPC method
  documentation
- [**GitHub Repository**](https://github.com/solana-foundation/kora) - Source
  code and examples
- [**Kora SDK**](https://www.npmjs.com/package/@solana/kora) - SDK for
  interacting with Kora RPC endpoints
