Sending Transactions through Carbium RPC
This guide explains how to get unsigned transactions from the Carbium Quote API and submit them using Carbium RPC for fast, reliable execution on Solana.
Overview
The Carbium workflow:
- Get Quote with Transaction - Fetch a quote with
user_accountparameter to receive an unsigned transaction - Deserialize & Sign - Decode the base64 transaction and sign with your wallet
- Send via Carbium RPC - Submit the signed transaction through Carbium's optimized RPC
Prerequisites
npm install @solana/web3.js bs58 undici dotenvEnvironment variables (.env):
CARBIUM_API_KEY=your-api-key
CARBIUM_RPC_API_KEY=your-rpc-key
SOLANA_PRIVATE_KEY=your-base58-private-keyStep 1: Get a Quote with Unsigned Transaction
When you include the user_account parameter, Carbium returns the quote plus a ready-to-sign transaction in the txn field.
import 'dotenv/config';
import { fetch } from 'undici';
const CARBIUM_API_KEY = process.env.CARBIUM_API_KEY;
const WALLET_ADDRESS = 'YOUR_WALLET_PUBLIC_KEY';
// Token mints
const SOL = 'So11111111111111111111111111111111111111112';
const USDC = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
async function getQuoteWithTransaction(srcMint, dstMint, amountIn, slippageBps = 50) {
const url = new URL('https://api.carbium.io/api/v2/quote');
url.searchParams.set('src_mint', srcMint);
url.searchParams.set('dst_mint', dstMint);
url.searchParams.set('amount_in', String(amountIn));
url.searchParams.set('slippage_bps', String(slippageBps));
url.searchParams.set('user_account', WALLET_ADDRESS); // Required for transaction
const response = await fetch(url, {
headers: {
'X-API-KEY': CARBIUM_API_KEY,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Quote failed: ${response.status} ${await response.text()}`);
}
const quote = await response.json();
// quote.txn contains the base64-encoded unsigned transaction
if (!quote.txn) {
throw new Error('Quote missing transaction - ensure user_account is provided');
}
return quote;
}
// Example: Get quote for 0.1 SOL -> USDC with 50 bps slippage
const quote = await getQuoteWithTransaction(SOL, USDC, 100_000_000, 50);
console.log('Expected output:', quote.destAmountOut);
console.log('Transaction ready:', quote.txn ? 'YES' : 'NO');Quote Response Structure
{
"srcMint": "So11111111111111111111111111111111111111112",
"dstMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"srcAmountIn": "100000000",
"destAmountOut": "18950000",
"slippageBps": 50,
"txn": "AQAAAA...base64-encoded-transaction..."
}Step 2: Deserialize and Sign the Transaction
The txn field contains a Solana VersionedTransaction encoded in base64. Deserialize it, sign with your wallet, then prepare for sending.
import { Connection, Keypair, VersionedTransaction } from '@solana/web3.js';
import bs58 from 'bs58';
const CARBIUM_RPC_URL = `https://rpc-service.carbium.io/?apiKey=${process.env.CARBIUM_RPC_API_KEY}`;
const PRIVATE_KEY = process.env.SOLANA_PRIVATE_KEY;
// Initialize wallet from base58 private key
function initWallet(privateKeyB58) {
const privateKeyBytes = bs58.decode(privateKeyB58);
return Keypair.fromSecretKey(privateKeyBytes);
}
// Deserialize and sign the transaction
function signTransaction(txnBase64, wallet) {
// Decode from base64
const txnBuffer = Buffer.from(txnBase64, 'base64');
// Deserialize as VersionedTransaction
const transaction = VersionedTransaction.deserialize(txnBuffer);
// Sign with wallet
transaction.sign([wallet]);
return transaction;
}
const wallet = initWallet(PRIVATE_KEY);
const signedTx = signTransaction(quote.txn, wallet);Step 3: Send Transaction via Carbium RPC
Submit the signed transaction through Carbium RPC for optimized routing and fast confirmation.
async function sendTransaction(connection, signedTransaction) {
const sendStart = Date.now();
// Send with skipPreflight for maximum speed
const signature = await connection.sendTransaction(signedTransaction, {
skipPreflight: true, // Skip simulation for speed
maxRetries: 3,
preflightCommitment: 'processed'
});
console.log(`TX sent in ${Date.now() - sendStart}ms: ${signature}`);
return signature;
}
// Confirm using HTTP polling (more reliable than WebSocket)
async function confirmTransaction(connection, signature, timeout = 60000) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const status = await connection.getSignatureStatus(signature);
if (status.value?.err) {
throw new Error(`Transaction failed: ${JSON.stringify(status.value.err)}`);
}
if (status.value?.confirmationStatus === 'confirmed' ||
status.value?.confirmationStatus === 'finalized') {
return { success: true, status: status.value.confirmationStatus };
}
// Poll every 500ms
await new Promise(resolve => setTimeout(resolve, 500));
}
throw new Error(`Transaction not confirmed within ${timeout / 1000}s`);
}Complete Example
Here's a full working example putting it all together:
import 'dotenv/config';
import { Connection, Keypair, VersionedTransaction } from '@solana/web3.js';
import { fetch } from 'undici';
import bs58 from 'bs58';
// Configuration
const CARBIUM_API_KEY = process.env.CARBIUM_API_KEY;
const CARBIUM_RPC_URL = `https://rpc-service.carbium.io/?apiKey=${process.env.CARBIUM_RPC_API_KEY}`;
const PRIVATE_KEY = process.env.SOLANA_PRIVATE_KEY;
// Token mints
const TOKENS = {
SOL: 'So11111111111111111111111111111111111111112',
USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
USDT: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'
};
class CarbiumSwapExecutor {
constructor() {
this.connection = new Connection(CARBIUM_RPC_URL, 'confirmed');
this.wallet = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY));
this.walletAddress = this.wallet.publicKey.toBase58();
}
async getQuoteWithTransaction(srcMint, dstMint, amountIn, slippageBps = 50) {
const url = new URL('https://api.carbium.io/api/v2/quote');
url.searchParams.set('src_mint', srcMint);
url.searchParams.set('dst_mint', dstMint);
url.searchParams.set('amount_in', String(amountIn));
url.searchParams.set('slippage_bps', String(slippageBps));
url.searchParams.set('user_account', this.walletAddress);
const response = await fetch(url, {
headers: {
'X-API-KEY': CARBIUM_API_KEY,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Quote failed: ${response.status}`);
}
const quote = await response.json();
if (!quote.txn) {
throw new Error('Quote missing transaction');
}
return quote;
}
signTransaction(txnBase64) {
const transaction = VersionedTransaction.deserialize(
Buffer.from(txnBase64, 'base64')
);
transaction.sign([this.wallet]);
return transaction;
}
async sendAndConfirm(signedTransaction, timeout = 60000) {
// Send transaction
const signature = await this.connection.sendTransaction(signedTransaction, {
skipPreflight: true,
maxRetries: 3,
preflightCommitment: 'processed'
});
console.log(`TX sent: ${signature}`);
// Confirm via HTTP polling
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const status = await this.connection.getSignatureStatus(signature);
if (status.value?.err) {
throw new Error(`TX failed: ${JSON.stringify(status.value.err)}`);
}
if (status.value?.confirmationStatus === 'confirmed' ||
status.value?.confirmationStatus === 'finalized') {
return { success: true, signature, status: status.value.confirmationStatus };
}
await new Promise(resolve => setTimeout(resolve, 500));
}
throw new Error('Transaction confirmation timeout');
}
async executeSwap(srcMint, dstMint, amountIn, slippageBps = 50) {
console.log(`Swapping ${amountIn} ${srcMint.slice(0,4)}... -> ${dstMint.slice(0,4)}...`);
// Step 1: Get quote with transaction
const quote = await this.getQuoteWithTransaction(srcMint, dstMint, amountIn, slippageBps);
console.log(`Expected output: ${quote.destAmountOut}`);
// Step 2: Sign transaction
const signedTx = this.signTransaction(quote.txn);
// Step 3: Send and confirm
const result = await this.sendAndConfirm(signedTx);
console.log(`Swap complete: ${result.signature}`);
return result;
}
}
// Usage
async function main() {
const executor = new CarbiumSwapExecutor();
// Swap 0.01 SOL to USDC (10_000_000 lamports = 0.01 SOL)
const result = await executor.executeSwap(
TOKENS.SOL,
TOKENS.USDC,
10_000_000, // 0.01 SOL in lamports
50 // 0.5% slippage
);
console.log('Success:', result);
}
main().catch(console.error);API Reference
Quote Endpoint
GET https://api.carbium.io/api/v2/quote
| Parameter | Required | Description |
|---|---|---|
src_mint | Yes | Source token mint address |
dst_mint | Yes | Destination token mint address |
amount_in | Yes | Input amount in smallest units (lamports/decimals) |
slippage_bps | No | Slippage tolerance in basis points (default: 50) |
user_account | Yes* | Wallet public key (*required to get transaction) |
Headers:
X-API-KEY: your-api-key
Accept: application/json
Carbium RPC Endpoint
https://rpc-service.carbium.io/?apiKey=your-rpc-key
Standard Solana JSON-RPC compatible. Optimized for:
- Low-latency transaction submission
- Reliable confirmation
- Geographic routing
Best Practices
Speed Optimization
// Use skipPreflight for faster execution (you already have a valid quote)
const signature = await connection.sendTransaction(signedTx, {
skipPreflight: true,
maxRetries: 3
});Error Handling
async function executeWithRetry(executor, srcMint, dstMint, amount, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await executor.executeSwap(srcMint, dstMint, amount);
} catch (error) {
console.error(`Attempt ${i + 1} failed:`, error.message);
if (i === maxRetries - 1) throw error;
// Wait before retry
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
}Rate Limiting
The Carbium API has rate limits. For high-frequency usage, implement a token bucket:
class RateLimiter {
constructor(maxRPS = 3) {
this.tokens = maxRPS;
this.maxTokens = maxRPS;
this.lastRefill = Date.now();
}
async acquire() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.maxTokens);
this.lastRefill = now;
if (this.tokens >= 1) {
this.tokens--;
return;
}
const waitMs = ((1 - this.tokens) / this.maxTokens) * 1000;
await new Promise(r => setTimeout(r, waitMs));
this.tokens = 0;
}
}Common Token Mints
| Token | Mint Address |
|---|---|
| SOL | So11111111111111111111111111111111111111112 |
| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
| USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB |
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
Quote missing transaction | user_account not provided | Add user_account parameter |
Transaction failed | Insufficient balance or slippage | Check balance, increase slippage |
Confirmation timeout | Network congestion | Retry with higher priority fee |
401 Unauthorized | Invalid API key | Check X-API-KEY header |
Updated about 20 hours ago
