Copy trading on Solana is one of the most requested features in crypto trading bots. Here's how I implemented it in @solscanitbot and the architectural decisions behind it.
The Problem
You want to automatically mirror another wallet's trades. When they buy a token, you buy it. When they sell, you sell. Simple concept, surprisingly tricky to implement.
Two Approaches
1. WebSocket Monitoring (Complex)
- Subscribe to account changes via Helius WebSocket
- Parse transaction logs in real-time
- High complexity, many edge cases
2. Snapshot-Diff (Simple, What I Use)
- Take snapshot of target wallet's token holdings
- Wait 60 seconds
- Take new snapshot
- Diff the two: new tokens = buys, missing tokens = sells
I chose snapshot-diff. Here's why:
Why Snapshot-Diff Wins
- Simpler code — ~50 lines vs 200+ for WebSocket
- More reliable — WebSocket connections drop; polling always works
- Easier to debug — you can log each snapshot
- 60-second latency is fine — for copy trading, seconds don't matter
Implementation
async function checkCopyTrades() {
for (const [chatId, targets] of Object.entries(copyTargets)) {
for (const target of targets) {
const currentHoldings = await getWalletTokens(target.wallet);
const prevHoldings = target.lastSnapshot || {};
// Find new tokens (buys)
for (const [mint, amount] of Object.entries(currentHoldings)) {
if (!prevHoldings[mint]) {
await executeCopyBuy(chatId, mint, target.buyAmount);
}
}
// Find removed tokens (sells)
for (const [mint, amount] of Object.entries(prevHoldings)) {
if (!currentHoldings[mint]) {
await executeCopySell(chatId, mint);
}
}
target.lastSnapshot = currentHoldings;
}
}
}
Edge Cases
- Partial sells: If they reduce position but don't fully exit, compare amounts
- Multiple targets: Users can copy multiple wallets simultaneously
- Position sizing: Don't copy the whale's 100 SOL buy with your 0.5 SOL
- Failed trades: Retry once, then notify the user
Getting Wallet Token Holdings
Use getTokenAccountsByOwner RPC call:
const accounts = await fetch(RPC_URL, {
method: 'POST',
body: JSON.stringify({
jsonrpc: '2.0', id: 1,
method: 'getTokenAccountsByOwner',
params: [walletAddress, { programId: TOKEN_PROGRAM_ID }, { encoding: 'jsonParsed' }]
})
});
Live in Action
Try it free: /copy <wallet_address> in @solscanitbot
Or get the full source code with all 42 commands for 2 SOL.
Top comments (0)