Gasless Token Swap
Build a backend-assisted gasless swap flow with Carbium's documented v1 swap surface while keeping API keys server-side and user signing inside the wallet.
Gasless Token Swap
A gasless token swap is still a wallet-approved Solana transaction. The difference is that your product asks Carbium for a gasless-capable swap build when the user has the input token but does not have enough SOL to pay the normal network fee.
Use this page when you are building the integration path. If you only need the product concept and user-flow decision, start with Gasless Swaps.
Part of the Carbium Solana infrastructure stack.
The safe shape
Do not ask users to paste private keys into a CLI, form, support chat, or .env file. A production gasless flow should keep three boundaries separate:
| Layer | Owns | Must not contain |
|---|---|---|
| Wallet client | user consent and transaction signing | Carbium API keys |
| Your backend | Carbium Swap API key, request policy, logs, relay coordination | user private keys |
| Carbium Swap API | building the swap transaction payload | user custody |
The wallet signs. Your backend requests the transaction and relays the signed payload. Carbium builds the transaction for the documented route.
Never design a gasless flow around exported private keys. If a user has to paste a private key, the integration is solving the wrong problem.
Where gasless fits
Use gasless only when it solves the no-SOL fee problem. It is not a universal default for every route.
Good fits:
- a wallet user holds a token but no SOL
- an onboarding flow needs one successful swap before the user can fund SOL normally
- a mobile or embedded app wants fewer off-product funding steps
- a support path needs to distinguish "no SOL for fees" from normal swap failures
Use the normal quote and swap path when the user already has enough SOL for fees or when the route does not support gasless handling.
Request the transaction from your backend
The documented gasless flag lives on the older provider-specific v1 swap builder:
GET https://api.carbium.io/api/v1/swapThat surface uses the v1 parameter family: owner, fromMint, toMint, amount, slippage, provider, and optional execution fields such as gasless.
A backend route can build the request like this:
const CARBIUM_SWAP_BASE = "https://api.carbium.io/api/v1/swap";
type GaslessSwapInput = {
owner: string;
fromMint: string;
toMint: string;
amount: string;
slippageBps: string;
provider: string;
};
export async function buildGaslessSwap(input: GaslessSwapInput) {
const url = new URL(CARBIUM_SWAP_BASE);
url.searchParams.set("owner", input.owner);
url.searchParams.set("fromMint", input.fromMint);
url.searchParams.set("toMint", input.toMint);
url.searchParams.set("amount", input.amount);
url.searchParams.set("slippage", input.slippageBps);
url.searchParams.set("provider", input.provider);
url.searchParams.set("gasless", "true");
const response = await fetch(url, {
headers: {
"X-API-KEY": process.env.CARBIUM_API_KEY!,
"accept": "application/json",
},
});
if (!response.ok) {
throw new Error(`Gasless swap build failed: ${response.status}`);
}
return response.json();
}Keep the Swap API key in a backend environment variable or secret manager. The browser or mobile client should send user intent and a wallet public key, not the Carbium credential.
Return only the unsigned transaction payload
The v1 swap guide documents a base64 transaction payload. Different response paths may wrap the payload in data, base64Transaction, or another transaction field, so normalize the response before returning it to the client.
function extractBase64Transaction(payload: any): string {
const candidate =
payload?.data?.base64Transaction ??
payload?.data?.transaction ??
payload?.base64Transaction ??
payload?.transaction ??
payload?.data;
if (typeof candidate !== "string" || candidate.length === 0) {
throw new Error("Swap response did not include a base64 transaction");
}
return candidate;
}
export async function handleGaslessSwapRequest(req: Request) {
const input = await req.json();
const payload = await buildGaslessSwap(input);
const transactionBase64 = extractBase64Transaction(payload);
return Response.json({
transactionBase64,
});
}Before returning a transaction to the wallet, your backend should validate the request policy: allowed providers, supported token pairs, maximum size, slippage bounds, and any app-specific risk checks.
Let the wallet sign
The client receives a base64 transaction payload and asks the connected wallet to sign it.
import { VersionedTransaction } from "@solana/web3.js";
async function signGaslessSwap(transactionBase64: string, wallet: any) {
const transaction = VersionedTransaction.deserialize(
Buffer.from(transactionBase64, "base64")
);
const signed = await wallet.signTransaction(transaction);
return Buffer.from(signed.serialize()).toString("base64");
}The user still reviews the wallet prompt. Gasless changes the fee path; it does not remove consent.
If the wallet rejects the prompt, stop the flow. Do not rebuild and retry automatically unless the user explicitly starts a new attempt.
Relay and confirm server-side
After signing, send the serialized signed transaction back to your backend. Relay it through Carbium RPC or your chosen Solana submission path, then confirm before marking the swap complete.
import { Connection, VersionedTransaction } from "@solana/web3.js";
const connection = new Connection(
`https://rpc.carbium.io/?apiKey=${process.env.CARBIUM_RPC_KEY}`,
"confirmed"
);
export async function submitSignedSwap(signedBase64: string) {
const transaction = VersionedTransaction.deserialize(
Buffer.from(signedBase64, "base64")
);
const signature = await connection.sendRawTransaction(
transaction.serialize(),
{ skipPreflight: false, maxRetries: 3 }
);
const confirmation = await connection.confirmTransaction(
signature,
"confirmed"
);
return { signature, confirmation };
}Persist the signature and request ID before retrying. If a worker restarts or the user refreshes, check signature status first instead of building a second transaction for the same intent.
Failure handling
A gasless branch needs clearer errors than a normal swap button because several systems are involved.
| Symptom | Likely boundary | First check |
|---|---|---|
401 or 403 from Swap API | Backend auth | Confirm X-API-KEY uses the Swap API key, not the RPC key |
| Route cannot be built | Swap route | Check provider, pair, amount, slippage, and whether gasless is supported for that path |
| No transaction payload returned | Build response | Confirm the response shape and route support before sending anything to the wallet |
| Wallet rejects signing | User consent | Stop cleanly and let the user start over |
| Signed transaction fails or times out | RPC or on-chain execution | Check transaction logs, blockhash age, confirmation status, and retry policy |
Use Swap API Errors Reference for pre-sign Swap API failures and Debug Solana Transaction Simulation for simulation or preflight problems.
Production checklist
Before shipping a gasless swap integration, verify:
- the Swap API key never reaches browser or mobile code
- users never paste or export private keys
gasless=trueis enabled only on the branch that needs fee assistance- the UI explains output amount, token mints, and signing intent before the wallet prompt
- route misses are surfaced as route availability, not generic downtime
- signed transactions are submitted once and tracked by signature
- support logs separate quote/build failure, wallet rejection, RPC submission, and on-chain failure
Build gasless as a backend-assisted wallet flow: Carbium builds, the wallet signs, your backend relays, and the user stays in control. Start broader product setup at carbium.io.
Updated 1 day ago
