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:
- Call
getPayerSignerand setsigner_addressas the transaction's fee payer. - Build the transaction, or have the paymaster build a token transfer for you with
transferTransaction. - Call
estimateTransactionFeeto get the fee in the fee token (fee_in_token). - Add an SPL transfer of
fee_in_tokenbase units from the user topayment_addressas the last instruction. Adding it changes the transaction, so re-estimate and adjust until the quoted fee stops changing (it converges in one step). - Have the user sign, then call
signAndSendTransactionto co-sign and broadcast, orsignTransactionif 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.
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
| Token | Mint Address | Decimals | Network |
|---|---|---|---|
| USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB | 6 | Solana 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 forfee_tokenand the only token that may be transferred. Any other mint fails with"Token ... is not supported". - The fee payment must be included.
signTransactionandsignAndSendTransactionreject 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 } }
- Example Request
- Example Response
- Response Type
{
"jsonrpc": "2.0",
"id": 1,
"method": "getPayerSigner",
"params": []
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"signer_address": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz",
"payment_address": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz"
}
}
| key | type | description |
|---|---|---|
signer_address | string | The paymaster's fee payer address. Set this as the transaction fee payer |
payment_address | string | Address that receives fee payments. The fee is paid to this address's associated token account of the fee token |
getSupportedTokens
Returns the mint addresses of the SPL tokens accepted for fee payment.
Invocation
{ "method": "getSupportedTokens", "params": [] }
Return
{ "result": { tokens: string[] } }
- Example Request
- Example Response
- Response Type
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSupportedTokens",
"params": []
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tokens": ["Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"]
}
}
| key | type | description |
|---|---|---|
tokens | string[] | Mint addresses of the SPL tokens accepted for fee payment |
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
}
}
- Example Request
- Example Response
- Request Types
- Response Type
{
"jsonrpc": "2.0",
"id": 1,
"method": "estimateTransactionFee",
"params": {
"transaction": "AmkNdD6EK2jjAaPzHhOfmyTqxYGGM0cemhhypHFRe8Wy...",
"fee_token": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"fee_in_lamports": 10553,
"fee_in_token": 818,
"signer_pubkey": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz",
"payment_address": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz"
}
}
fee_in_token is in base units of fee_token: 818 is 0.000818 USDT.
| key | type | description |
|---|---|---|
transaction | string | Base64-encoded serialized transaction, with the paymaster's signer address set as the fee payer |
fee_token | string | Mint address of the SPL token to price the fee in. Must be one of the tokens returned by getSupportedTokens |
| key | type | description |
|---|---|---|
fee_in_lamports | number | Total fee in lamports, including the network fee, any rent for accounts the transaction creates, and the paymaster margin |
fee_in_token | number | The same fee denominated in base units of fee_token (USDT has 6 decimals). This is the amount the transaction must transfer to payment_address |
signer_pubkey | string | The paymaster's fee payer address |
payment_address | string | Address the fee payment must be sent to |
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
}
}
- Example Request
- Example Response
- Request Types
- Response Type
{
"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.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"transaction": "AmkNdD6EK2jjAaPzHhOfmyTqxYGGM0cemhhypHFRe8Wy...",
"message": "AgECBbHnR3w2nftr0eljJIxb7sUoC7kqYxLzxCJsijx6...",
"blockhash": "FFkAKnzBcUvKv3erAh3KoGsSMv4zmJSip6e1fduDWb3B",
"signer_pubkey": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz"
}
}
| key | type | description |
|---|---|---|
amount | number | Transfer amount in base units of the token (USDT has 6 decimals) |
token | string | Mint address of the SPL token to transfer |
source | string | Wallet address of the sender (owner address, not the token account) |
destination | string | Wallet address of the recipient (owner address, not the token account) |
| key | type | description |
|---|---|---|
transaction | string | Base64-encoded unsigned transaction with the paymaster set as fee payer. It does not include the fee payment instruction |
message | string | Base64-encoded transaction message |
blockhash | string | The recent blockhash the transaction was built with |
signer_pubkey | string | The paymaster's fee payer address |
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 } }
- Example Request
- Example Response
- Request Types
- Response Type
{
"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).
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"signed_transaction": "AgBZ71/kM9Ccb3i4Arc1npZnyHlETph7p7SY747zb7gt...",
"signer_pubkey": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz"
}
}
| key | type | description |
|---|---|---|
transaction | string | Base64-encoded transaction, signed by every required signer except the fee payer, and containing an SPL transfer of at least fee_in_token to the payment address |
| key | type | description |
|---|---|---|
signed_transaction | string | Base64-encoded transaction with the paymaster's fee payer signature added. Not broadcast; submit it yourself |
signer_pubkey | string | The paymaster's fee payer address that signed |
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
}
}
- Example Request
- Example Response
- Request Types
- Response Type
{
"jsonrpc": "2.0",
"id": 1,
"method": "signAndSendTransaction",
"params": {
"transaction": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..."
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"signature": "1QdRg6ThqLHa776Dq7FHjjzJTS3ZG8Me6FPgqTAa7mND1Wfc86Qk9B39i2p1dy5tvG9AuVHYLbHNTqdVUsJk1UZ",
"signed_transaction": "AgBZ71/kM9Ccb3i4Arc1npZnyHlETph7p7SY747zb7gt...",
"signer_pubkey": "CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz"
}
}
This response is a real mainnet transaction: view it on the explorer.
| key | type | description |
|---|---|---|
transaction | string | Base64-encoded transaction, signed by every required signer except the fee payer, and containing an SPL transfer of at least fee_in_token to the payment address |
| key | type | description |
|---|---|---|
signature | string | Transaction signature (base58). Use it to track confirmation on any Solana RPC or explorer |
signed_transaction | string | Base64-encoded fully signed transaction as broadcast to the network |
signer_pubkey | string | The paymaster's fee payer address that signed |
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 } }
- Example Request
- Example Response
- Response Type
{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlockhash",
"params": []
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"blockhash": "kZEkyRwERAqSXNULXRtg2rcnvUgKJPXyXGgXqFsPErk"
}
}
| key | type | description |
|---|---|---|
blockhash | string | A recent blockhash from the Solana RPC node the paymaster is connected to |
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
}
}
- Example Request
- Example Response
- Response Type
{
"jsonrpc": "2.0",
"id": 1,
"method": "getConfig",
"params": []
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"fee_payers": ["CyTi1U4TQt8MddAt54cez6rTJKZWvfjXNLvd3dVeveBz"],
"validation_config": {
"max_allowed_lamports": 9000000,
"max_signatures": 10,
"allowed_programs": [
"11111111111111111111111111111111",
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
"ComputeBudget111111111111111111111111111111",
"AddressLookupTab1e1111111111111111111111111"
],
"allowed_tokens": ["Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"],
"allowed_spl_paid_tokens": ["Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"],
"disallowed_accounts": [],
"price_source": "Jupiter",
"fee_payer_policy": {
"system": {
"allow_transfer": false,
"allow_assign": false,
"allow_create_account": true,
"allow_allocate": false,
"nonce": {
"allow_initialize": false,
"allow_advance": false,
"allow_withdraw": false,
"allow_authorize": false
}
},
"spl_token": {
"allow_transfer": false,
"allow_burn": false,
"allow_close_account": false,
"allow_approve": false,
"allow_revoke": false,
"allow_set_authority": false,
"allow_mint_to": false,
"allow_initialize_mint": false,
"allow_initialize_account": false,
"allow_initialize_multisig": false,
"allow_freeze_account": false,
"allow_thaw_account": false
},
"token_2022": {
"allow_transfer": false,
"allow_burn": false,
"allow_close_account": false,
"allow_approve": false,
"allow_revoke": false,
"allow_set_authority": false,
"allow_mint_to": false,
"allow_initialize_mint": false,
"allow_initialize_account": false,
"allow_initialize_multisig": false,
"allow_freeze_account": false,
"allow_thaw_account": false
}
},
"price": { "type": "margin", "margin": 0.05 },
"token_2022": {
"blocked_mint_extensions": ["permanent_delegate"],
"blocked_account_extensions": []
},
"allow_durable_transactions": false
},
"enabled_methods": {
"liveness": true,
"estimate_transaction_fee": true,
"get_supported_tokens": true,
"get_payer_signer": true,
"sign_transaction": true,
"sign_and_send_transaction": true,
"transfer_transaction": true,
"get_blockhash": true,
"get_config": true
}
}
}
| key | type | description |
|---|---|---|
fee_payers | string[] | The paymaster's fee payer addresses |
validation_config | object | The policy every transaction is validated against |
validation_config.max_allowed_lamports | number | Maximum lamports the fee payer will spend on a single transaction (fees plus rent) |
validation_config.max_signatures | number | Maximum number of signatures allowed in a transaction |
validation_config.allowed_programs | string[] | Program IDs a transaction may invoke. Transactions touching any other program are rejected |
validation_config.allowed_tokens | string[] | Mint addresses of tokens that may be transferred |
validation_config.allowed_spl_paid_tokens | string[] | Mint addresses of tokens accepted as fee payment |
validation_config.disallowed_accounts | string[] | Accounts that transactions may not reference |
validation_config.price_source | string | Oracle used to price the fee token against SOL |
validation_config.fee_payer_policy | object | Per-instruction permissions for what the fee payer key itself may be used for, per program (system, spl_token, token_2022) |
validation_config.price | object | Fee pricing model. type is free, fixed, or margin; margin is the markup applied over the network cost |
validation_config.token_2022 | object | Token-2022 mint and account extensions that are blocked |
validation_config.allow_durable_transactions | boolean | Whether durable nonce transactions are accepted |
enabled_methods | object | Map of RPC method names (snake_case) to whether they are enabled on this endpoint |
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": "..." } }
| Code | Example message | Cause |
|---|---|---|
| -32000 | Invalid transaction: Insufficient token payment. Required 10553 lamports | The transaction does not transfer at least the quoted fee to the payment address. The required amount is expressed in lamports; convert with estimateTransactionFee |
| -32000 | Invalid request: Token EPjFW...Dt1v is not supported | fee_token or a transferred token is not in the allowed token list (USDT only) |
| -32000 | Invalid transaction: Failed to deserialize transaction: io error: unexpected end of file | transaction is not a base64-encoded serialized Solana transaction |
| -32600 | Unsupported method - getPaymentInstruction not supported | The method does not exist or is disabled on this endpoint |