Pump.fun Trading API: Client-Side Signing for Full Wallet Custody
The pumpdev.io Trading API lets you buy and sell pump.fun tokens programmatically with full wallet custody. Your private keys never leave your machine. The API generates unsigned transactions that you sign locally and broadcast through your own Solana RPC endpoint.
This is the foundation for building pump.fun trading bots, sniper bots, copy trading systems, and automated DeFi tools on Solana.
API Endpoint: POST https://pumpdev.io/api/trade-local
Why Use the pumpdev.io Trading API
- Non-custodial — Private keys stay on your server. The API never sees them.
- Client-side signing — Sign transactions locally with any Solana wallet or keypair.
- RPC flexibility — Broadcast through your own RPC for speed and reliability.
- Full trade control — Buy with the pair's quote asset, sell by percentage, or sell exact token amounts.
- Bot-ready — Combine with the WebSocket Data API for real-time automated trading.
- Same shape on another chain — a bot written against this endpoint runs on Robinhood Chain by changing the URL; see the PONS Trading API.
Prerequisites
- Node.js 16+ (or any environment supporting ES modules)
@solana/web3.jsandbs58packages installed- A Solana wallet keypair (base58-encoded private key)
- A Solana RPC endpoint (public or private)
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
publicKey | string | Yes | Your wallet public key |
action | string | Yes | "buy" or "sell" |
mint | string | Yes | Token mint address |
amount | number/string | Yes | Amount to trade |
denominatedInSol | string | No | Legacy field name. Set "true" when a buy amount is denominated in the pair's quote asset: SOL for SOL pairs, USDC for USDC pairs. Keep "false" for sell percentages or exact token amounts |
slippage | number | No | Slippage percentage (default: 90) |
priorityFee | number | No | Priority fee in SOL (default: 0.00005) |
computeUnits | number | No | Override compute unit limit. If omitted, PumpDev auto-selects a trade-specific limit |
jitoTip | number | No | Jito tip in SOL — only provide if sending via Jito (default: 0) |
creator | string | No | Creator public key. Used only as a fallback when the bonding curve is not readable yet (a just-created mint). When PumpDev can read the curve, the on-chain creator wins — a stale value here would make pump.fun reject the transaction with a creator_vault seeds error |
closeTokenAccount | boolean | No | Close the token ATA after selling to reclaim ~0.002 SOL rent. Auto-enabled on "100%" sells. Set false to disable |
Quote mint handling: You do not pass
quoteMintto trade endpoints. PumpDev resolves it from the token on-chain and builds the correct V2 trade automatically. For SOL pairs, keep using native SOL. For USDC pairs, send the buyamountin USDC units withdenominatedInSol: "true". Buying a non-SOL pair spends that token (USDC, PUMP, xStocks…) from your wallet, so hold enough of it first — otherwise the request fails withInsufficient quote token balance.
Amount Formats
For buy transactions:
0.1withdenominatedInSol: "true"= Buy with 0.1 SOL25withdenominatedInSol: "true"on a USDC-quoted token = Buy with 25 USDC
For sell transactions:
"100%"= Sell all tokens"50%"= Sell half your tokens1000000withdenominatedInSol: "false"= Sell exact token amount
Response
Returns a binary serialized unsigned VersionedTransaction. Deserialize it with VersionedTransaction.deserialize(), sign with your keypair, then send via any Solana RPC connection.
How to Buy Pump.fun Tokens
Execute a buy order by spending the pair's quote asset to acquire tokens. For SOL pairs that quote asset is native SOL; for USDC pairs it is USDC.
import { VersionedTransaction, Connection, Keypair } from '@solana/web3.js';
import bs58 from 'bs58';
// Configuration
const API_URL = 'https://pumpdev.io';
const RPC_URL = 'https://api.mainnet-beta.solana.com';
const PRIVATE_KEY = 'YourBase58PrivateKey';
async function buyToken() {
const keypair = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY));
const publicKey = keypair.publicKey.toBase58();
// 1. Build buy transaction
const response = await fetch(`${API_URL}/api/trade-local`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey: publicKey,
action: 'buy',
mint: 'TokenMintAddress',
amount: 0.1, // Buy with 0.1 SOL
denominatedInSol: 'true',
slippage: 15 // 15% slippage tolerance
})
});
if (response.status !== 200) {
const error = await response.json();
console.error('API Error:', error);
return;
}
// 2. Deserialize the transaction
const data = await response.arrayBuffer();
const tx = VersionedTransaction.deserialize(new Uint8Array(data));
// 3. Sign with your wallet
tx.sign([keypair]);
// 4. Send to Solana
const connection = new Connection(RPC_URL, 'confirmed');
try {
const signature = await connection.sendTransaction(tx, {
skipPreflight: false,
maxRetries: 3
});
console.log('Transaction sent!');
console.log('Signature:', signature);
// Wait for confirmation
const confirmation = await connection.confirmTransaction(signature, 'confirmed');
if (confirmation.value.err) {
console.error('Transaction failed:', confirmation.value.err);
} else {
console.log('Transaction confirmed!');
}
} catch (err) {
console.error('Send error:', err.message);
}
}
buyToken();
Buy a USDC-Quoted Coin
await fetch(`${API_URL}/api/trade-local`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey: publicKey,
action: 'buy',
mint: 'UsdcQuotedTokenMint',
amount: 25, // Buy with 25 USDC
denominatedInSol: 'true',
slippage: 15
})
});
How to Sell Pump.fun Tokens
Convert your token holdings back to the pair's quote asset by selling tokens through the bonding curve or PumpSwap.
import { VersionedTransaction, Connection, Keypair } from '@solana/web3.js';
import bs58 from 'bs58';
const API_URL = 'https://pumpdev.io';
const RPC_URL = 'https://api.mainnet-beta.solana.com';
const PRIVATE_KEY = 'YourBase58PrivateKey';
async function sellToken() {
const keypair = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY));
const publicKey = keypair.publicKey.toBase58();
// 1. Build sell transaction
const response = await fetch(`${API_URL}/api/trade-local`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey: publicKey,
action: 'sell',
mint: 'TokenMintAddress',
amount: '100%', // Sell all tokens
denominatedInSol: 'false',
slippage: 15
})
});
if (response.status !== 200) {
const error = await response.json();
console.error('API Error:', error);
return;
}
// 2. Deserialize and sign
const data = await response.arrayBuffer();
const tx = VersionedTransaction.deserialize(new Uint8Array(data));
tx.sign([keypair]);
// 3. Send to Solana
const connection = new Connection(RPC_URL, 'confirmed');
try {
const signature = await connection.sendTransaction(tx, {
skipPreflight: false,
maxRetries: 3
});
console.log('Sell transaction sent!');
console.log('Signature:', signature);
} catch (err) {
console.error('Send error:', err.message);
}
}
sellToken();
Executing Partial Sells
Sell a specific percentage or exact amount of your token holdings rather than liquidating your entire position.
// Sell 50% of tokens
const response = await fetch(`${API_URL}/api/trade-local`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey: publicKey,
action: 'sell',
mint: 'TokenMintAddress',
amount: '50%', // Sell half
denominatedInSol: 'false',
slippage: 15
})
});
// Or sell exact token amount
const response2 = await fetch(`${API_URL}/api/trade-local`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey: publicKey,
action: 'sell',
mint: 'TokenMintAddress',
amount: 1000000, // Sell 1,000,000 tokens
denominatedInSol: 'false',
slippage: 15
})
});
Building an Automated Pump.fun Trading Bot
Combine our WebSocket data feeds with the Trading API to create a Solana sniper bot that detects new pump.fun token launches and executes trades based on configurable criteria — fully automated and non-custodial.
import WebSocket from 'ws';
import { VersionedTransaction, Connection, Keypair } from '@solana/web3.js';
import bs58 from 'bs58';
const API_URL = 'https://pumpdev.io';
const WS_URL = 'wss://pumpdev.io/ws';
const RPC_URL = 'https://api.mainnet-beta.solana.com';
const PRIVATE_KEY = 'YourBase58PrivateKey';
// Bot settings
const BUY_AMOUNT_SOL = 0.01;
const SLIPPAGE = 20;
const MAX_MARKET_CAP_SOL = 50;
const MIN_INITIAL_BUY_SOL = 0.5;
let keypair;
let publicKey;
let connection;
let isBuying = false;
async function main() {
keypair = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY));
publicKey = keypair.publicKey.toBase58();
connection = new Connection(RPC_URL, 'confirmed');
console.log('Sniper Bot Started');
console.log('Wallet:', publicKey);
console.log('Buy amount:', BUY_AMOUNT_SOL, 'SOL');
const ws = new WebSocket(WS_URL);
ws.on('open', () => {
console.log('Connected to WebSocket');
// Subscribe to new token creations
ws.send(JSON.stringify({ method: 'subscribeNewToken' }));
console.log('Watching for new tokens...');
});
ws.on('message', async (data) => {
const message = JSON.parse(data.toString());
if (message.txType === 'create') {
await handleNewToken(message);
}
});
ws.on('error', (err) => console.error('WebSocket error:', err.message));
}
async function handleNewToken(event) {
const { mint, name, symbol } = event;
const quoteAmount = event.quoteAmount ?? event.solAmount ?? 0;
const marketCap = event.marketCapQuote ?? event.marketCapSol ?? 0;
const quoteLabel =
event.quoteMint === 'So11111111111111111111111111111111111111112'
? 'SOL'
: event.quoteMint;
console.log(`\nNew Token: ${name} (${symbol})`);
console.log(` Mint: ${mint}`);
console.log(` Initial buy: ${quoteAmount} ${quoteLabel}`);
console.log(` Market cap: ${marketCap.toFixed(2)} ${quoteLabel}`);
// Check criteria
if (event.quoteMint !== 'So11111111111111111111111111111111111111112') {
console.log(' Skip: Non-SOL quote mint');
return;
}
if (marketCap > MAX_MARKET_CAP_SOL) {
console.log(' Skip: Market cap too high');
return;
}
if (quoteAmount < MIN_INITIAL_BUY_SOL) {
console.log(' Skip: Initial buy too low');
return;
}
if (isBuying) {
console.log(' Skip: Already buying');
return;
}
console.log(' Matches criteria! Buying...');
await buyToken(mint);
}
async function buyToken(mint) {
isBuying = true;
try {
// 1. Build transaction
const response = await fetch(`${API_URL}/api/trade-local`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey: publicKey,
action: 'buy',
mint: mint,
amount: BUY_AMOUNT_SOL,
denominatedInSol: 'true',
slippage: SLIPPAGE,
priorityFee: 0.001
})
});
if (response.status !== 200) {
const error = await response.json();
console.log(' API Error:', error.error);
return;
}
// 2. Sign and send
const data = await response.arrayBuffer();
const tx = VersionedTransaction.deserialize(new Uint8Array(data));
tx.sign([keypair]);
const signature = await connection.sendTransaction(tx, {
skipPreflight: true,
maxRetries: 2
});
console.log(' Transaction sent:', signature);
} catch (err) {
console.log(' Error:', err.message);
} finally {
isBuying = false;
}
}
main();
Error Handling & Troubleshooting
The API returns structured JSON error responses with descriptive messages to help diagnose issues:
{
"error": "publicKey is required"
}
Common API Errors
| Error Message | Root Cause | Solution |
|---|---|---|
Missing required fields: publicKey, action, mint, amount | One or more required fields missing | Include all required fields in the request body |
action must be 'buy' or 'sell' | Invalid action parameter value | Use exactly "buy" or "sell" |
Invalid publicKey or mint address | Malformed Solana address format | Verify address format and encoding |
Token bonding curve not found | Token doesn't exist on Pump.fun | Verify the mint address |
PumpSwap pool not found. Use Jupiter or Raydium for this token. | Graduated token without a PumpSwap pool | Use Jupiter or Raydium instead |
No token balance found | Wallet doesn't hold tokens for sell | Ensure you own tokens for this mint |
Invalid percentage value | Sell percentage is invalid (must be 1-100) | Use a valid percentage like "50%" or "100%" |
Calculated sell amount is zero | Token balance too small for the given percentage | Use "100%" or a larger percentage |
Frequently Asked Questions
Do I need to share my private key with the API?
No. The Trading API only receives your public key. It returns an unsigned transaction that you sign locally before broadcasting. Your private key never leaves your environment.
What is the difference between the Trading API and Lightning Trade?
The Trading API requires you to sign and send the transaction yourself (client-side). Lightning Trade handles signing and broadcasting for you using a pre-configured Lightning wallet — ideal for simpler setups or rapid prototyping.
What slippage should I use for a sniper bot?
For new token launches with high volatility, use 15-25% slippage. For established tokens with deeper liquidity, 5-10% is typically sufficient.
Can I use a custom RPC endpoint?
Yes. Pass any Solana-compatible RPC URL to new Connection(RPC_URL). Using a private RPC (e.g., Helius, QuickNode, Triton) is recommended for sniper bots to reduce latency and avoid rate limits.
Why is my first request slower than the rest?
The first HTTPS request from a fresh process pays DNS + TCP + TLS handshake costs (a few hundred ms) before the API even sees it — server processing time is the same for every request. For latency-sensitive bots, warm up the connection at startup with one or two GET /api/health calls, and send a periodic keep-alive ping (e.g., every 30s) while idle so trades reuse the warm connection instead of re-handshaking.
Next Steps
- Lightning Trade — One-call buy/sell (no signing required)
- Lightning Setup — Create a Lightning wallet + API key
- Jito Bundles — Multi-wallet atomic trades & launches
- Token Creation — Create new tokens
- Claim Fees — Claim creator fees
- Data API — Real-time WebSocket data for trading bots
- PONS Trading API — the same endpoint on Robinhood Chain