The @abstraxn/solana-relayer package lets your app build, sign, and relay Solana transactions — gaslessly. The relayer reserves a fee-payer on your behalf so end-users never need SOL for fees. Gas sponsorship works the same way as the Paymaster on EVM chains — you fund a gas pool from the dashboard and the relayer draws from it to cover transaction fees on behalf of your users.
What you can do:
- Send native SOL transfers
- Send SPL-token transfers (with automatic ATA creation)
- Submit arbitrary program instructions
- Track transaction status until finality
- Sponsor gas for your users (same model as EVM Paymaster)
Installation
npm install @abstraxn/solana-relayer
Setup
The Solana relayer URL follows this pattern:
https://solana-paymaster.abstraxn.com/api/v1/{chainId}/?apikey={your_api_key}
| Placeholder | Description | Example |
|---|
{chainId} | Solana chain identifier from your dashboard | 103 (devnet), 101 (mainnet-beta) |
{your_api_key} | API key from your Abstraxn app | ak_live_abc123... |
Get Your Relayer URL
- Visit https://dashboard.abstraxn.com/ and log in or sign up
- Go to Apps → create or select an app with Solana as a supported chain
- Navigate to Paymaster — if your app has Solana selected, you will see the Solana paymaster link
- Copy the Paymaster URL — this is your relayer URL
The Solana Relayer uses the same gas sponsorship model as the EVM Paymaster. Fund your gas pool from the Paymaster section in the dashboard, and the relayer draws from it to cover transaction fees on behalf of your users.
Your relayer URL contains your API key. Store it in environment variables — never hardcode it in client-side code.
Create the Client
import { SolanaRelayer } from "@abstraxn/solana-relayer";
const client = new SolanaRelayer({
relayerUrl: process.env.SOLANA_RELAYER_URL!,
// e.g. "https://solana-paymaster.abstraxn.com/api/v1/103/?apikey=your_api_key"
});
That’s it — one config, one client. All four SDK methods use this instance.
Core Flow
Every relayed transaction follows four steps:
buildTx → signTx → sendTx → getTxStatus
| Step | What happens |
|---|
| buildTx | SDK fetches an available relayer, reserves it as fee-payer, and builds a VersionedTransaction |
| signTx | User’s wallet adapter signs the transaction |
| sendTx | Signed transaction is submitted to the relayer hub along with the reservation token |
| getTxStatus | Poll until the transaction reaches confirmed or failed |
sendTx must be called after a successful buildTx — the SDK stores the reservation token from the build step internally.
Native SOL Transfer
Send SOL from one wallet to another, with gas paid by the relayer:
import { Connection, PublicKey } from "@solana/web3.js";
import { SolanaRelayer } from "@abstraxn/solana-relayer";
const client = new SolanaRelayer({
relayerUrl: process.env.SOLANA_RELAYER_URL!,
});
const connection = new Connection(
"https://api.devnet.solana.com",
"confirmed",
);
// 1. Build the transaction
const { tx, lastValidBlockHeight } = await client.buildTx({
connection,
sender: new PublicKey("<user-wallet-pubkey>"),
to: new PublicKey("<recipient-pubkey>"),
lamports: 1_000_000, // 0.001 SOL
});
// 2. Sign with the user's wallet
const serializedUserSignedTx = await client.signTx({
wallet: walletAdapter, // e.g. from @solana/wallet-adapter-react
transaction: tx,
});
// 3. Submit to the relayer
const submit = await client.sendTx({
signedTransaction: serializedUserSignedTx,
lastValidBlockHeight,
});
// 4. Track status
const finalStatus = await client.getTxStatus({
txnId: submit.txnId,
});
console.log(finalStatus.status); // "confirmed" or "failed"
console.log(finalStatus.signature); // on-chain transaction signature
SPL Token Transfer
Transfer any SPL token. If the recipient’s Associated Token Account (ATA) does not exist, the SDK automatically creates it with the relayer as rent payer.
const { tx, lastValidBlockHeight } = await client.buildTx({
connection,
sender: new PublicKey("<owner-pubkey>"),
mint: new PublicKey("<token-mint-address>"),
recipientOwner: new PublicKey("<recipient-owner-pubkey>"),
amountUi: "1.25", // human-readable amount
decimals: 6, // fallback — SDK uses on-chain decimals when available
splTokenProgramId: new PublicKey("<token-program-id>"),
splAssociatedTokenProgramId: new PublicKey("<ata-program-id>"),
});
// Then sign, send, and track status as above
const signed = await client.signTx({ wallet: walletAdapter, transaction: tx });
const submit = await client.sendTx({ signedTransaction: signed, lastValidBlockHeight });
const status = await client.getTxStatus({ txnId: submit.txnId });
Token transfer parameters
| Parameter | Type | Required | Description |
|---|
connection | Connection | Yes | Solana RPC connection |
sender | PublicKey | Yes | Token owner’s public key |
mint | PublicKey | Yes | SPL token mint address |
recipientOwner | PublicKey | Yes | Recipient’s wallet address (not ATA) |
amountUi | string | Yes | Human-readable amount (e.g. "1.25") |
decimals | number | Yes | Fallback decimals if RPC lookup fails |
splTokenProgramId | PublicKey | Yes | Token program (Token or Token-2022) |
splAssociatedTokenProgramId | PublicKey | Yes | Associated Token Account program |
When the mint supports jsonParsed RPC, the SDK reads on-chain decimals and uses TransferChecked. The decimals parameter is only a fallback. If the sender or recipient ATA is missing, the SDK prepends a createAssociatedTokenAccount instruction with the relayer as payer.
Custom Program Transaction
For any instruction set that isn’t a simple transfer — swaps, NFT mints, program calls — pass your own instructions:
Static instructions
import { TransactionInstruction, PublicKey } from "@solana/web3.js";
const instructions: TransactionInstruction[] = [
// Your custom instructions here
];
const { tx, lastValidBlockHeight } = await client.buildTx({
connection,
sender: new PublicKey("<signer-pubkey>"),
instructions,
});
Dynamic instructions with buildInstructions
When your instructions need the relayer’s public key before they can be built (e.g. ATA creation where the relayer pays rent), use the buildInstructions callback:
const { tx, lastValidBlockHeight } = await client.buildTx({
connection,
sender: new PublicKey("<signer-pubkey>"),
buildInstructions: async ({ feePayer }) => {
// feePayer is the reserved relayer's public key
return [
createAssociatedTokenAccountInstruction(
feePayer, // payer (relayer)
ata, // ATA to create
recipient, // owner of the new ATA
mint, // token mint
),
createTransferCheckedInstruction(
senderAta, // source
mint, // mint
ata, // destination
senderPubkey, // owner
amount, // raw amount
decimals, // token decimals
),
];
},
});
instructions, buildInstructions, and transaction are mutually exclusive — pass exactly one. Passing more than one will throw an error.
buildTx accepts exactly one of three transaction types:
| Type | Required fields | Use case |
|---|
| Native transfer | to + lamports | Send SOL |
| Token transfer | mint + recipientOwner + amountUi + decimals + splTokenProgramId + splAssociatedTokenProgramId | Send SPL tokens |
| Custom | instructions, buildInstructions, or transaction (pick one) | Any program call |
All variants require connection and sender.
API Reference
new SolanaRelayer(config)
| Option | Type | Description |
|---|
relayerUrl | string | Full relayer endpoint URL (includes API key) |
buildTx(params) → { tx, lastValidBlockHeight }
Fetches an available relayer, reserves it, and builds a VersionedTransaction. Returns the transaction and block height for expiry tracking.
signTx(params) → serializedTransaction
| Parameter | Type | Description |
|---|
wallet | Wallet Adapter | Solana wallet adapter instance |
transaction | VersionedTransaction | Transaction from buildTx |
sendTx(params) → { txnId }
| Parameter | Type | Description |
|---|
signedTransaction | string | Serialized signed transaction from signTx |
lastValidBlockHeight | number | Block height from buildTx |
getTxStatus(params) → { status, signature }
| Parameter | Type | Description |
|---|
txnId | string | Transaction ID from sendTx |
Returns:
| Field | Type | Values |
|---|
status | string | "confirmed" or "failed" |
signature | string | On-chain transaction signature |
How It Works Under the Hood
The SDK communicates with sol-relayer-hub using a JSON-RPC style protocol over a single POST endpoint:
POST <relayerUrl>
Content-Type: application/json
{ "method": "<method>", "params": [...], "id": 1, "jsonrpc": "2.0" }
| RPC Method | SDK Method | Purpose |
|---|
sol_getAvailableRelayer | buildTx | Reserve a fee-payer relayer |
sol_sendTx / sol_sendProgrammeTx | sendTx | Submit signed transaction |
sol_getTxStatus | getTxStatus | Poll transaction status |
You never call these directly — the SDK handles serialization, reservation tokens, and retries.
Integrating with Wallet Adapters
The signTx method accepts any standard Solana wallet adapter. Here’s a React example using @solana/wallet-adapter-react:
import { useWallet, useConnection } from "@solana/wallet-adapter-react";
import { PublicKey } from "@solana/web3.js";
import { SolanaRelayer } from "@abstraxn/solana-relayer";
function SendButton() {
const { publicKey, wallet } = useWallet();
const { connection } = useConnection();
const handleSend = async () => {
if (!publicKey || !wallet) return;
const client = new SolanaRelayer({
relayerUrl: process.env.NEXT_PUBLIC_SOLANA_RELAYER_URL!,
});
const { tx, lastValidBlockHeight } = await client.buildTx({
connection,
sender: publicKey,
to: new PublicKey("11111111111111111111111111111112"),
lamports: 1_000_000,
});
const signed = await client.signTx({
wallet: wallet.adapter,
transaction: tx,
});
const { txnId } = await client.sendTx({
signedTransaction: signed,
lastValidBlockHeight,
});
// Poll until confirmed
let status;
do {
await new Promise((r) => setTimeout(r, 2000));
status = await client.getTxStatus({ txnId });
} while (status.status !== "confirmed" && status.status !== "failed");
console.log("Final:", status.status, status.signature);
};
return (
<button onClick={handleSend} disabled={!publicKey}>
Send 0.001 SOL (Gasless)
</button>
);
}
Troubleshooting
Q: buildTx fails with “no relayer available”
All relayers are currently reserved. Wait a moment and retry — relayers are released after submission or timeout.
Q: sendTx returns an error
- Ensure you call
sendTx after a successful buildTx (the reservation token is stored internally).
- Check that the
lastValidBlockHeight has not expired — if the block height has passed, rebuild the transaction.
Q: Token transfer fails with “account not found”
- Verify the
mint address is correct on the target network (devnet vs mainnet).
- Ensure
splTokenProgramId matches the token standard (Token vs Token-2022).
Q: Transaction stuck in pending
- Check Solana network status for congestion.
- Verify the relayer hub is healthy via its health endpoint.
Security Notes
- Store the relayer URL (which contains your API key) in server-side environment variables.
- Validate all user inputs (addresses, amounts) before building transactions.
- The relayer only pays gas from your sponsored pool — it cannot move user funds. Users must still sign with their own wallet.
- Monitor your gas pool balance in the dashboard to avoid failed sponsorships.
Useful Links