Building Solana Copy Trading Bot with gRPC Streams
A comprehensive guide to building a high-performance Solana trading bot that leverages gRPC streams for real-time copy trading using Bitquery CoreCast.
Table of Contents
- Architecture Overview
- Bitquery CoreCast Streams
- Streaming Real-Time Solana DEX Trades
- Trade Execution with Jupiter API
- Code Walkthrough
- Configuration & Filtering
- Best Practices
- Output
Output
The final result of this project would appear as the one given below.
Architecture Overview
This Solana trading bot implements a streaming architecture for copy trading:
┌─────────────────────┐
│ Solana Blockchain │
│ (DEX Trades) │
└──────────┬──────────┘
│
▼
┌─────────────────────────────────┐
│ Bitquery CoreCast gRPC Stream │ ← Real-time data streaming
│ docs.bitquery.io/docs/grpc/ │
└──────────┬──────────────────────┘
│
▼
┌─────────────────────┐
│ gRPC Client │ ← @grpc/grpc-js
│ (CoreCast Proto) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Event Handler │ ← Trade filtering & strategy
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Jupiter Swap API │ ← Optimal trade execution
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Solana Transaction │ ← On-chain execution
└─────────────────────┘
Learn more about Bitquery CoreCast architecture.
Bitquery CoreCast Integration
Bitquery CoreCast provides real-time blockchain data streaming via gRPC for Solana trading bots.
// index.js - Loading Protocol Buffers
const { loadPackageDefination } = require('bitquery-corecast-proto');
const packageDefinition = loadPackageDefination();
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const solanaCorecast = protoDescriptor.solana_corecast;
Reference: Protobuf Loading Documentation
Streaming Real-Time Solana DEX Trades
Initializing the gRPC Client
The bot connects to Bitquery's CoreCast server using the given below code snippet:
// index.js - Client Initialization
function initializeClient() {
client = new solanaCorecast.CoreCast(
config.server.address, // corecast.bitquery.io
grpc.credentials.createSsl()
);
metadata = new grpc.Metadata();
metadata.add('authorization', config.server.authorization);
}
Refer to this document for any issues related to authorization.
Creating the Stream
Multiple Solana stream topics are available:
// index.js - Stream Creation
function startStream() {
const request = createRequest();
let stream;
switch (config.stream.type) {
case 'dex_trades': // Real-time DEX trades
stream = client.DexTrades(request, metadata);
break;
case 'dex_orders': // Order book updates
stream = client.DexOrders(request, metadata);
break;
case 'transactions': // All transactions
stream = client.Transactions(request, metadata);
break;
// ... more stream types
}
}
Handling Stream Events
The bot processes incoming trade messages in real-time:
// index.js - Event Handler
stream.on('data', async (message) => {
if (message.Trade) {
// Extract trade data from protobuf message
const marketAddress = toBase58(message.Trade.Market?.MarketAddress);
const inputMint = toBase58(message.Trade.Buy?.Currency?.MintAddress);
const outputMint = toBase58(message.Trade.Sell?.Currency?.MintAddress);
const buyAmount = message.Trade.Buy?.Amount;
// Apply trading strategy
if (approveTrade(buyAmount)) {
await executeTrades({ inputMint, outputMint, marketAddress, buyAmount });
}
}
});
stream.on('error', (error) => {
console.error('Stream error:', error);
});
stream.on('end', () => {
console.log('Stream ended');
});
Trade Execution with Jupiter API
Native SOL Conversion
Jupiter requires wrapped SOL (wSOL) instead of native SOL for trading. Our bot handles this conversion:
// trade.js - SOL Mint Conversion
const NATIVE_SOL_MINT = '11111111111111111111111111111111';
const WRAPPED_SOL_MINT = 'So11111111111111111111111111111111111111112';
const convertedInputMint = inputMint === NATIVE_SOL_MINT
? WRAPPED_SOL_MINT
: inputMint;
Fetching Swap Quotes
We use Jupiter's aggregation API to find optimal swap routes:
// trade.js - Quote Fetching
const jupiter = createJupiterApiClient({
basePath: 'https://quote-api.jup.ag/v6'
});
const quoteReq = {
inputMint: convertedInputMint,
outputMint: convertedOutputMint,
amount: amountInRaw,
slippageBps: slippageBps.toString(),
onlyDirectRoutes: false // Allow indirect routes for better liquidity
};
const quote = await jupiter.quoteGet(quoteReq);
Creating and Sending Transactions
Once we have a quote, we build and execute the swap transaction:
// trade.js - Transaction Execution
const swapReq = {
quoteResponse: quote,
userPublicKey: wallet.publicKey.toString(),
wrapAndUnwrapSOL: true, // Handle SOL wrapping automatically
asLegacyTransaction: true
};
const swapRes = await jupiter.swapPost({ swapRequest: swapReq });
// Deserialize and sign transaction
const txBuf = Buffer.from(swapRes.swapTransaction, 'base64');
const tx = Transaction.from(txBuf);
tx.sign([wallet]);
// Send to Solana network
const txSig = await connection.sendRawTransaction(
tx.serialize(),
{ skipPreflight: true, maxRetries: 3 }
);
// Wait for confirmation
await connection.confirmTransaction(txSig, 'confirmed');
Code Walkthrough
Helper Functions
Base58 Encoding
Solana addresses are encoded in base58. We convert byte arrays to base58 strings:
// index.js - Base58 Conversion
function toBase58(bytes) {
if (!bytes || bytes.length === 0) return 'undefined';
try {
return bs58.encode(bytes);
} catch (error) {
return 'invalid_address';
}
}
Configuration Management
The bot supports hot-reloading of configuration without restart:
// index.js - Config Watching
fs.watch('./config.yaml', (eventType, filename) => {
if (eventType === 'change') {
clearTimeout(watchTimeout);
watchTimeout = setTimeout(() => {
reloadAndRestart();
}, 300); // Debounce rapid changes
}
});
Trading Strategy
Implement your copy trading logic in the approveTrade() function:
// index.js - Trade Approval Logic
function approveTrade(buyAmount) {
// Example: Only approve large trades
if (buyAmount > 100 * 1000000000) {
console.log('Approving large trade:', buyAmount);
return true;
}
return false;
}
Strategy Ideas:
- Volume-based filtering
- Token whitelist/blacklist
- Risk management (max position size)
- Cooldown periods
- Multi-signal confirmation
Configuration & Filtering
Stream Configuration
Configure which on-chain activity to monitor:
# config.yaml
stream:
type: "dex_trades" # Real-time DEX trades
Available Stream Types:
dex_trades- DEX tradesdex_orders- Order book datadex_pools- Pool liquidity eventstransactions- All transactionstransfers- Token transfersbalances- Balance updates
Filters
Use filtering options to target specific trades:
# config.yaml
filters:
traders: # Copy trades from specific addresses
- "HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"
programs: # Filter by DEX programs
- "..."
pool: # Filter by liquidity pools
- "..."
signers: # Filter by transaction signers
- "..."
// index.js - Request Builder
function createRequest() {
const request = {};
if (config.filters.traders?.length > 0) {
request.trader = { addresses: config.filters.traders };
}
if (config.filters.programs?.length > 0) {
request.program = { addresses: config.filters.programs };
}
// ... more filter types
return request;
}
Best Practices
1. Error Handling
Implement comprehensive error handling for network failures:
// trade.js - Error Handling
try {
const quote = await jupiter.quoteGet(quoteReq);
} catch (error) {
console.error('Jupiter API error:', error.response?.data || error.message);
// Implement retry logic or fallback
}
2. Rate Limiting
To avoid being rate-limited by Jupiter:
// Add delays between trades
await new Promise(resolve => setTimeout(resolve, 1000));
3. Monitoring
Log all trade executions for analysis:
console.log('✅ Copy trade executed!', {
tx: txSig,
inputMint,
outputMint,
amount: buyAmount
});
4. Security
- Keep API keys in
secrets.json(never commit) - Use separate trading wallet
- Set maximum trade amounts
- Implement stop-loss mechanisms
5. Testing
Start with small amounts:
// Reduce trade amount for testing
amountInRaw: (buyAmount / 100).toString() // 1% of original
Additional Resources
Bitquery Documentation
- CoreCast Introduction
- Authentication Guidelines
- Best Practices for gRPC streams
- Other Examples
- Error Handling
External APIs
Get Started
Sign up for Bitquery CoreCast and start building your Solana copy trading bot today!
Ready to run this in production?
Get an API key and run these queries in minutes, or talk to us about plans and enterprise delivery.