Overview
Fee sponsorship lets CDP pay the Solana network fee on behalf of your server wallet. Your wallet can send transactions without holding SOL to cover gas. This feature is in private preview and available to enterprise-scale integrations only. To opt in, apply for access. Once approved, passuseCdpSponsor: true when calling sendTransaction.
Sponsoring a transaction
Report incorrect code
Copy
Ask AI
import { CdpClient } from "@coinbase/cdp-sdk";
import {
address as solanaAddress,
appendTransactionMessageInstructions,
compileTransaction,
createNoopSigner,
createTransactionMessage,
getBase64EncodedWireTransaction,
pipe,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
const cdp = new CdpClient();
const account = await cdp.solana.getOrCreateAccount({ name: "my-account" });
// A more recent blockhash is set in the backend by CDP
const FAKE_BLOCKHASH = "SysvarRecentB1ockHashes11111111111111111111";
const instruction = getTransferSolInstruction({
source: createNoopSigner(solanaAddress(account.address)),
destination: solanaAddress("3KzDtddx4i53FBkvCzuDmRbaMozTZoJBb1TToWhz3JfE"),
amount: 1000n,
});
const txMsg = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayer(solanaAddress(account.address), tx),
(tx) =>
setTransactionMessageLifetimeUsingBlockhash(
{ blockhash: FAKE_BLOCKHASH, lastValidBlockHeight: 9999999n },
tx,
),
(tx) => appendTransactionMessageInstructions([instruction], tx),
);
const serializedTx = getBase64EncodedWireTransaction(compileTransaction(txMsg));
const result = await cdp.solana.sendTransaction({
network: "solana-mainnet",
transaction: serializedTx,
useCdpSponsor: true,
});
console.log("Transaction signature:", result.transactionSignature);
Rate limits
CDP enforces per-project sponsorship limits. If a limit is exceeded, the API returns a429 response. To request a limit increase, reach out in the Coinbase Developer Discord.
Advanced: Manual fee payer orchestration
If you need full control over fee payment, for example, to use an external relayer or to designate a specific CDP account as the fee payer for other accounts, you can manage the fee payer yourself withoutuseCdpSponsor.
This approach requires:
- A dedicated fee payer account with SOL for gas
- Building a transaction with
feePayerset to that account - Signing with both the sender and the fee payer
- Broadcasting the fully-signed transaction
Report incorrect code
Copy
Ask AI
import { CdpClient } from "@coinbase/cdp-sdk";
import "dotenv/config";
import {
Connection,
PublicKey,
SystemProgram,
Transaction,
} from "@solana/web3.js";
async function main(sourceAddress?: string) {
const cdp = new CdpClient();
// Required: Destination address to send SOL to
const destinationAddress = "3KzDtddx4i53FBkvCzuDmRbaMozTZoJBb1TToWhz3JfE";
// Amount of lamports to send (default: 1000 = 0.000001 SOL)
const lamportsToSend = 1000;
try {
const connection = new Connection("https://api.devnet.solana.com");
// Set up a dedicated fee payer account.
const feePayer = await cdp.solana.getOrCreateAccount({
name: "test-sol-account-fee-payer",
});
console.log("Fee payer address: " + feePayer.address);
// Request funds on the feePayer address to pay for the gas.
await requestFaucetAndWaitForBalance(cdp, feePayer.address, connection);
let fromAddress: string;
if (sourceAddress) {
fromAddress = sourceAddress;
console.log("Using existing SOL account:", fromAddress);
} else {
// Set up a source account.
const account = await cdp.solana.getOrCreateAccount({
name: "test-sol-account",
})
fromAddress = account.address;
console.log("Successfully created new SOL account:", fromAddress);
// Request funds on the source account for transaction amount.
await requestFaucetAndWaitForBalance(cdp, fromAddress, connection);
}
const balance = await connection.getBalance(new PublicKey(fromAddress));
if (balance < lamportsToSend) {
throw new Error(
`Insufficient balance: ${balance} lamports, need at least ${lamportsToSend} lamports`
);
}
const transaction = new Transaction();
transaction.add(
SystemProgram.transfer({
fromPubkey: new PublicKey(fromAddress),
toPubkey: new PublicKey(destinationAddress),
lamports: lamportsToSend,
})
);
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(feePayer.address);
const serializedTx = Buffer.from(
transaction.serialize({ requireAllSignatures: false })
).toString("base64");
// Sign with the funding account.
const signedTxResponse = await cdp.solana.signTransaction({
address: fromAddress,
transaction: serializedTx,
});
const signedBase64Tx = signedTxResponse.signature;
// Sign with the feePayer account.
const finalSignedTxResponse = await cdp.solana.signTransaction({
address: feePayer.address,
transaction: signedBase64Tx,
});
// Send the signed transaction to the network.
const signature = await connection.sendRawTransaction(Buffer.from(finalSignedTxResponse.signature, 'base64'));
const latestBlockhash = await connection.getLatestBlockhash();
const confirmation = await connection.confirmTransaction({
signature,
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
});
if (confirmation.value.err) {
throw new Error(
`Transaction failed: ${confirmation.value.err.toString()}`
);
}
console.log(
"Transaction confirmed:",
confirmation.value.err ? "failed" : "success"
);
console.log(
`Transaction explorer link: https://explorer.solana.com/tx/${signature}?cluster=devnet`
);
return {
fromAddress,
destinationAddress,
amount: lamportsToSend / 1e9,
signature,
success: !confirmation.value.err,
};
} catch (error) {
console.error("Error processing SOL transaction:", error);
throw error;
}
}
/**
* Sleeps for a given number of milliseconds
*
* @param {number} ms - The number of milliseconds to sleep
* @returns {Promise<void>} A promise that resolves when the sleep is complete
*/
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Requests funds from the faucet and waits for the balance to be available
*
* @param {CdpClient} cdp - The CDP client instance
* @param {string} address - The address to fund
* @param {Connection} connection - The Solana connection
* @param {string} token - The token to request (default: "sol")
* @returns {Promise<void>} A promise that resolves when the account is funded
*/
async function requestFaucetAndWaitForBalance(
cdp: CdpClient,
address: string,
connection: Connection,
): Promise<void> {
// Request funds from faucet
const faucetResp = await cdp.solana.requestFaucet({
address: address,
token: "sol",
});
console.log(
`Successfully requested SOL from faucet:`,
faucetResp.signature
);
// Wait until the address has balance
let balance = 0;
let attempts = 0;
const maxAttempts = 30;
while (balance === 0 && attempts < maxAttempts) {
balance = await connection.getBalance(new PublicKey(address));
if (balance === 0) {
console.log("Waiting for funds...");
await sleep(1000);
attempts++;
}
}
if (balance === 0) {
throw new Error("Account not funded after multiple attempts");
}
console.log("Account funded with", balance / 1e9, "SOL");
return;
}
const sourceAddress = process.argv.length > 2 ? process.argv[2] : undefined;
main(sourceAddress).catch(console.error);
What to read next
- Sending Transactions: Send Solana transactions without fee sponsorship
- Batching Instructions: Batch multiple instructions into a single transaction