Skip to main content

Solana Paymaster RPC Methods

JSON-RPC API reference for Candide's Solana Paymaster, a hosted Kora endpoint. The paymaster acts as the transaction fee payer, so your users can transact from accounts that hold zero SOL and pay the network fee in USDT instead. Kora is the Solana Foundation's open fee-payer standard: any Kora client, such as @solana/kora, works against this endpoint unchanged.

Visit the dashboard to get an API key. The endpoint URL is:

https://api.candide.dev/api/v3/solana/YOUR_API_KEY

You can also keep the key out of the URL by sending it in an x-api-key header against https://api.candide.dev/api/v3/solana.

To check connectivity and discover the fee payer address in one call:

curl https://api.candide.dev/api/v3/solana/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "getPayerSigner", "params": []}'

Every example request and response on this page was captured against the live endpoint. Long base64 strings are shortened with ... for readability; everything else is verbatim, including the mainnet transaction in signAndSendTransaction.

Conventions

These differ from Candide's EVM Paymaster API, so read them before reusing patterns from there:

  • Transactions are the base64-encoded serialized wire format (what transaction.serialize() returns in @solana/web3.js), not hex.
  • Amounts and fees are plain decimal integers in base units (USDT has 6 decimals), not hex strings.
  • Methods that take arguments use a named params object ("params": {...}); methods without arguments take "params": [].
  • Method names are camelCase; response fields are snake_case (signer_address, fee_in_token).

Transaction Flow

A gasless transaction goes through the API like this:

  1. Call getPayerSigner and set signer_address as the transaction's fee payer.
  2. Build the transaction, or have the paymaster build a token transfer for you with transferTransaction.
  3. Call estimateTransactionFee to get the fee in the fee token (fee_in_token).
  4. Add an SPL transfer of fee_in_token base units from the user to payment_address as the last instruction. Adding it changes the transaction, so re-estimate and adjust until the quoted fee stops changing (it converges in one step).
  5. Have the user sign, then call signAndSendTransaction to co-sign and broadcast, or signTransaction if you want to broadcast yourself.

The Pay Gas in USDT on Solana guide walks through the same flow with Tether's WDK handling steps 2 to 5 behind one transfer() call.

getPaymentInstruction is not available

Newer Kora versions describe a getPaymentInstruction method that builds the fee payment instruction of step 4 for you. It is not enabled on this endpoint and returns error -32600, "Unsupported method - getPaymentInstruction not supported". Build the payment instruction as a plain SPL transfer instead.

Supported Tokens

TokenMint AddressDecimalsNetwork
USDTEs9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB6Solana Mainnet

Query getSupportedTokens for the live list; new tokens are added there first.

Sponsorship Mode

The Solana Paymaster currently supports one sponsorship mode:

  • Token gas payments (available): the user pays the network fee in a supported SPL token inside the same transaction, and the paymaster fronts the SOL. Every transaction must include the fee payment; this is the mode this API implements. It is the Solana counterpart of ERC-20 gas payments on Candide's EVM Paymaster.
  • Full gas sponsorship (not yet available on Solana): the app covers the fee so the user pays nothing. On EVM this is offered through InstaGas gas policies.

Current Policy

The endpoint validates every transaction against the policy in getConfig. As of this writing, the rules that most often reject a transaction are:

  • USDT is the only fee token. Mainnet USDT (Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB) is the only accepted value for fee_token and the only token that may be transferred. Any other mint fails with "Token ... is not supported".
  • The fee payment must be included. signTransaction and signAndSendTransaction reject transactions that do not pay at least the quoted fee to the payment address, with "Insufficient token payment. Required ... lamports".
  • Only five programs may be invoked: System, SPL Token, Associated Token Account, Compute Budget, and Address Lookup Table. Token-2022 is not among them, so Token-2022 mints are rejected.
  • The fee payer spends at most 9,000,000 lamports per transaction (fees plus rent), transactions are capped at 10 signatures, and durable nonce transactions are not accepted.

Methods

getPayerSigner

Returns the paymaster's fee payer address and the address that fee payments must be sent to. Set signer_address as the transaction fee payer; send the USDT fee to payment_address.

Invocation

{ "method": "getPayerSigner", "params": [] }

Return

{ "result": { signer_address: string, payment_address: string } }
{
"jsonrpc": "2.0",
"id": 1,
"method": "getPayerSigner",
"params": []
}

getSupportedTokens

Returns the mint addresses of the SPL tokens accepted for fee payment.

Invocation

{ "method": "getSupportedTokens", "params": [] }

Return

{ "result": { tokens: string[] } }
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSupportedTokens",
"params": []
}

estimateTransactionFee

Prices a transaction. Returns the total fee in lamports and in the requested fee token, including rent for any accounts the transaction creates and the paymaster margin. The transaction must already name the paymaster's fee payer; it does not need to be signed.

Invocation

{ "method": "estimateTransactionFee", "params": { transaction: string, fee_token: string } }

Return

{
"result": {
fee_in_lamports: number,
fee_in_token: number,
signer_pubkey: string,
payment_address: string
}
}
{
"jsonrpc": "2.0",
"id": 1,
"method": "estimateTransactionFee",
"params": {
"transaction": "AmkNdD6EK2jjAaPzHhOfmyTqxYGGM0cemhhypHFRe8Wy...",
"fee_token": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
}
}

transferTransaction

Builds an unsigned SPL token transfer with the paymaster set as fee payer. Use it when you do not want to construct the transaction yourself. The returned transaction does not include the fee payment: estimate it, append the payment transfer, and re-sign before submitting.

source and destination are wallet (owner) addresses; the paymaster resolves the associated token accounts. If the destination's token account does not exist, the transaction includes its creation and the rent shows up in the fee quote.

Invocation

{ "method": "transferTransaction", "params": { amount: number, token: string, source: string, destination: string } }

Return

{
"result": {
transaction: string,
message: string,
blockhash: string,
signer_pubkey: string
}
}
{
"jsonrpc": "2.0",
"id": 1,
"method": "transferTransaction",
"params": {
"amount": 100000,
"token": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
"source": "A3scQhn4rbkybHyCd1C5rcAQ3ZKHTLwsCrXw7fTSo2zN",
"destination": "A3scQhn4rbkybHyCd1C5rcAQ3ZKHTLwsCrXw7fTSo2zN"
}
}

amount is in base units: 100000 is 0.1 USDT.

signTransaction

Validates a transaction against the policy and, if it passes, signs it with the fee payer key. The transaction is not broadcast: submit the returned signed_transaction yourself. The transaction must include the fee payment to the payment address, or it is rejected with "Insufficient token payment".

Invocation

{ "method": "signTransaction", "params": { transaction: string } }

Return

{ "result": { signed_transaction: string, signer_pubkey: string } }
{
"jsonrpc": "2.0",
"id": 1,
"method": "signTransaction",
"params": {
"transaction": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..."
}
}

The user has already signed; the fee payer's signature slot is still empty (the leading zero bytes).

signAndSendTransaction

Same validation and signing as signTransaction, then broadcasts the transaction to the network and returns its signature. This is the last call of the transaction flow. It returns as soon as the transaction is submitted; poll getSignatureStatuses on your Solana RPC to wait for confirmation.

Invocation

{ "method": "signAndSendTransaction", "params": { transaction: string } }

Return

{
"result": {
signature: string,
signed_transaction: string,
signer_pubkey: string
}
}
{
"jsonrpc": "2.0",
"id": 1,
"method": "signAndSendTransaction",
"params": {
"transaction": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..."
}
}

getBlockhash

Returns a recent blockhash from the Solana RPC node the paymaster is connected to. Useful when your app does not have its own RPC connection at transaction-build time.

Invocation

{ "method": "getBlockhash", "params": [] }

Return

{ "result": { blockhash: string } }
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlockhash",
"params": []
}

getConfig

Returns the paymaster's full configuration: fee payer addresses, the validation policy every transaction is checked against, and which RPC methods are enabled. Use it to discover limits programmatically instead of hardcoding the current policy.

Invocation

{ "method": "getConfig", "params": [] }

Return

{
"result": {
fee_payers: string[],
validation_config: object,
enabled_methods: object
}
}
{
"jsonrpc": "2.0",
"id": 1,
"method": "getConfig",
"params": []
}

Errors

Failures use standard JSON-RPC 2.0 error objects. The messages below were captured from the live endpoint:

{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32000, "message": "..." } }
CodeExample messageCause
-32000Invalid transaction: Insufficient token payment. Required 10553 lamportsThe transaction does not transfer at least the quoted fee to the payment address. The required amount is expressed in lamports; convert with estimateTransactionFee
-32000Invalid request: Token EPjFW...Dt1v is not supportedfee_token or a transferred token is not in the allowed token list (USDT only)
-32000Invalid transaction: Failed to deserialize transaction: io error: unexpected end of filetransaction is not a base64-encoded serialized Solana transaction
-32600Unsupported method - getPaymentInstruction not supportedThe method does not exist or is disabled on this endpoint