Tip Jar
gasless-soldocs

Developer Documentation

Integrate gasless Solana payments

gasless-sol lets a sponsor wallet cover SOL gas while your users pay a small USDC fee — fee and transfer land atomically in one transaction, so the user never needs SOL. Pure TypeScript over @solana/web3.js; no on-chain program to deploy.

Install

Install the SDK alongside its peer dependencies.

bash
bun add gasless-sol @solana/web3.js @solana/spl-token @solana/wallet-adapter-react

Ships as ESM with full TypeScript types. Three entry points: gasless-sol (core), gasless-sol/react (hook), gasless-sol/server (API route).

Quickstart

Three files and you have gasless payments: a shared config, two API routes, and a button. Each is covered in detail below.

  1. 1. Define a config (fee token + amount).
  2. 2. Add the sponsor API routes.
  3. 3. Call the React hook from a button.

Configuration

One config object drives everything. Share it between the client and the sponsor route so both agree on the fee.

ts
// lib/gasless.ts
import type { LegionGaslessConfig } from "gasless-sol";

export const gaslessConfig: LegionGaslessConfig = {
  gasless: {
    fees: [
      // Devnet USDC mint. Swap for mainnet USDC on mainnet-beta.
      { token: "USDC", mintAddress: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", amount: 0.05 },
    ],
    defaultFeeToken: "USDC",
  },
};

Sponsor API routes

The sponsor co-signs server-side so the private key never reaches the client. createSponsorRoute validates that every transaction pays the configured fee before signing — without feeConfig the sponsor would pay gas for arbitrary transactions, so always pass it.

ts
// app/api/sponsor/route.ts
import { createSponsorRoute } from "gasless-sol/server";
import { gaslessConfig } from "@/lib/gasless";

export const { GET, POST } = createSponsorRoute({
  privateKeyEnvVar: "SPONSOR_PRIVATE_KEY", // server-only, never NEXT_PUBLIC
  feeConfig: gaslessConfig,                // enforces fee-to-sponsor on every tx
  rateLimit: 10,                           // requests/min/IP
  minSolBalance: 0.01,                     // reject if sponsor underfunded
});
ts
// app/api/sponsor-pubkey/route.ts
import { createSponsorPubkeyRoute } from "gasless-sol/server";

export const { GET } = createSponsorPubkeyRoute();

React hook

useGaslessTransaction handles the full flow: build → user signs → sponsor co-signs → submit → confirm. It reads the connected wallet from @solana/wallet-adapter-react. Amounts are in raw token units (USDC has 6 decimals, so 1 USDC = 1_000_000).

tsx
"use client";
import { useGaslessTransaction } from "gasless-sol/react";
import { gaslessConfig } from "@/lib/gasless";

export function TipButton({ creator }: { creator: string }) {
  const { sendGaslessTip, status, signature, error, isLoading } =
    useGaslessTransaction(gaslessConfig);

  return (
    <button
      disabled={isLoading}
      onClick={() =>
        sendGaslessTip({
          recipientAddress: creator,
          tipAmountRaw: 1_000_000, // 1 USDC
        })
      }
    >
      {isLoading ? status : "Send 1 USDC"}
    </button>
  );
}

Returned status cycles through idle → building → signing → sponsoring → submitting → confirming → confirmed (or error). On success, signature holds the tx hash for an Explorer link.

Core builder (advanced)

Need to drive the flow yourself (non-React, custom signing)? Use the builder directly. It returns an unsigned transaction with the sponsor as fee payer — you handle user-sign, POST to your sponsor route, and submit.

ts
import { LegionGasless } from "gasless-sol";
import { Connection, PublicKey } from "@solana/web3.js";
import { gaslessConfig } from "@/lib/gasless";

const legion = new LegionGasless(gaslessConfig);

const { transaction } = await legion.buildGaslessTipTransaction({
  connection: new Connection("https://api.devnet.solana.com"),
  sponsorPublicKey: new PublicKey(sponsorPubkey),
  senderPublicKey: new PublicKey(userPubkey),
  recipientPublicKey: new PublicKey(creatorPubkey),
  tipAmountRaw: 1_000_000, // 1 USDC
});
// → user partial-signs → POST to /api/sponsor → sponsor co-signs → submit

Environment

Two variables. The sponsor key is server-only — never prefix it with NEXT_PUBLIC_.

bash
# .env.local
NEXT_PUBLIC_SOLANA_NETWORK=devnet
SPONSOR_PRIVATE_KEY=<base58 secret of a funded devnet wallet>

Fund the sponsor with devnet SOL (faucet.solana.com); users need devnet USDC (faucet.circle.com) but zero SOL. Switch NEXT_PUBLIC_SOLANA_NETWORK to mainnet-beta and the config to the mainnet USDC mint for production.

How it works

Each tip is a single atomic transaction with the sponsor as fee payer. No custom program — just the SPL Token and System programs.

text
feePayer: sponsor            ← covers ~5000 lamports SOL gas
  ix[0] create ATA (idem.)   ← ensure sponsor's USDC account exists
  ix[1] USDC transfer        ← user pays the fee to sponsor
  ix[2] create ATA (idem.)   ← ensure recipient's USDC account exists
  ix[3] USDC transfer        ← user tips the recipient

All-or-nothing: fee + tip succeed together or the tx fails.
User holds 0 SOL the entire time.

See it live on the tip jar demo.