Skip to main content

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​


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:

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​

External APIs​

Get Started​

Sign up for Bitquery CoreCast and start building your Solana copy trading bot today!

Build with Bitquery

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.