How to Build a Solana KOL Copy-Trading Bot with TypeScript
Build a copy-trading bot that mirrors Solana KOL wallet trades in real-time using the MadeOnSol API. Full TypeScript tutorial with webhooks, trade filtering, and Jupiter swap execution.
Build a copy-trading bot that mirrors Solana KOL wallet trades in real-time using the MadeOnSol API. Full TypeScript tutorial with webhooks, trade filtering, and Jupiter swap execution.

Disclosure: This article contains affiliate links. If you sign up through them, MadeOnSol may earn a commission at no extra cost to you. This never affects our rankings, ratings, or reviews.
Copy-trading KOL wallets is one of the most popular strategies in Solana trading. Instead of manually watching wallet trackers, you can build a bot that automatically mirrors trades from top-performing wallets the moment they happen.
This tutorial shows you how to build a KOL copy-trading bot using the MadeOnSol API, which tracks 950+ curated KOL wallets in real-time with PnL data, coordination signals, KOL affinity analysis, momentum detection, and deployer enrichment.
A TypeScript bot that:
mkdir kol-copy-bot && cd kol-copy-bot
npm init -y
npm install madeonsol @solana/web3.js
Create a .env file:
MADEONSOL_API_KEY=msk_your_key_here
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
WALLET_PRIVATE_KEY=your_base58_private_key
MAX_SOL_PER_TRADE=0.1
// src/client.ts
import { MadeOnSol } from "madeonsol";
export const client = new MadeOnSol({
apiKey: process.env.MADEONSOL_API_KEY!,
});
The SDK auto-detects the msk_ prefix and authenticates directly with MadeOnSol's API.
Before copy-trading, you want to know which KOLs are actually profitable. The leaderboard endpoint ranks wallets by realized PnL:
// src/find-kols.ts
import { client } from "./client";
async function findTopKols() {
// Get top KOLs by PnL over the last 7 days
const { leaderboard } = await client.kol.leaderboard({ period: "7d" });
// Filter for consistent performers
const profitable = leaderboard.filter(
(kol) => kol.win_rate && kol.win_rate > 0.5 && kol.trade_count > 10
);
console.log(`Found ${profitable.length} profitable KOLs:`);
for (const kol of profitable.slice(0, 10)) {
console.log(
` ${kol.kol_name || kol.wallet} — PnL: ${kol.total_pnl_usd.toFixed(0)} USD, Win rate: ${(kol.win_rate! * 100).toFixed(0)}%`
);
}
return profitable.map((k) => k.wallet);
}
Building a product on Solana data?
Skip the Geyser pipeline — embed KOL flow, deployer reputation, and the all-DEX firehose over REST, WebSocket, or webhooks. The Business tier (€400/mo) is the self-serve embed license at 500k calls/day — Enterprise adds white-label & redistribution.
Tools mentioned
Live health scores, average ratings, and direct links on MadeOnSol.

Transaction debugging for Solana with full source-code-level tracing

No-code Solana token creation and management toolkit

Restaking infrastructure for Solana middleware security

Human-readable transaction classification API for Solana and EVM chains

Guaranteed Solana transaction inclusion via ahead-of-time blockspace auctions
Get this data via API
Stream real-time KOL trades, PnL rankings, and coordination signals programmatically.
New customers try Pro free for 5 days — card, cancel anytime.
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({ apiKey: "msk_your_key" });
// Real-time KOL trades
const { trades } = await client.kol.feed({ limit: 10, action: "buy" });
// KOL convergence signals
const { tokens } = await client.kol.coordination({ min_kols: 3 });Building a product on Solana data?
MadeOnSol isn't an RPC or another generic token API — it's Solana memecoin intelligence that's painful to build in-house, pulled from dual-region gRPC shred streams: sub-second from the on-chain event to your app.
Signals you'd otherwise build
KOL & smart-money flow from 1,000+ labeled wallets, deployer reputation, coordination clusters, linked-wallet entity resolution, and an all-DEX firehose.
Embed it in your product
The Business tier (€400/mo, self-serve) licenses you to display MadeOnSol data inside your own product — 500k calls/day, 10 WS + 5 firehose connections. Enterprise above adds white-label & redistribution rights and custom endpoints.
Evaluate first, commit later
Test everything on a free ULTRA key — no commitment. If it fits, we scope volume or white-label pricing.
Keep reading

Developer Tools
Watch live smart-money trades on Solana and paper-trade them in real time: open a virtual position when a tracked KOL buys, close when they sell, and track PnL from the market-cap multiple. This tutorial walks the open-source kol-copytrade-bot-starter — ~300 lines, one dependency, free API key — and shows how to filter by KOL win rate, deployer tier, and multi-KOL confirmation before risking anything real.

Education
KOL stands for Key Opinion Leader — a trader or personality whose calls move markets. Here's what KOLs actually are, how they make money, whether their calls are profitable (we measured 757,000 trades), and how wallet tracking separates real traders from paid promoters.

Analytics
Four ready-to-analyze Solana datasets — 1.25M scored wallets, 1.5M priced KOL trades, 27k real deployers, plus genesis-complete Robinhood Chain — as scrubbed, checksummed CSVs with free 5,000-row samples.

Trading
RHC is an ETH-gas Arbitrum L2 with a private FCFS sequencer and no mempool; Solana is a shred-level latency race. Here's how the two chains compare for memecoin traders — and why one key covers both.

Analytics
We ranked 40,781 Robinhood Chain-native wallets by realized net-ETH PnL, filtered out 499 bot fleets, and were left with 509 profitable human wallets in no KOL set — 84 of them up more than 10 ETH. As of mid-July 2026.

Trading
We traced known Solana KOL wallets across the bridges and found more than 220 of them already active on Robinhood Chain across 331 wallets, holding roughly 479 ETH plus memecoin bags and tokenized stocks. Here's what the on-chain data reveals about the migration.
Enjoyed this article?
Real-time KOL trades, Pump.fun deployer intel, and 1,200+ ranked Solana tools — free to explore.
Open the KOL TrackerThe KOL feed returns trades within seconds of on-chain confirmation. Poll it to catch new buys:
// src/monitor.ts
import { client } from "./client";
const SEEN_SIGNATURES = new Set<string>();
async function checkForBuys(watchlist: string[]) {
const { trades } = await client.kol.feed({
limit: 50,
action: "buy",
});
const newBuys = trades.filter(
(t) =>
!SEEN_SIGNATURES.has(t.signature) &&
watchlist.includes(t.wallet)
);
for (const trade of newBuys) {
SEEN_SIGNATURES.add(trade.signature);
console.log(
`[BUY] ${trade.kol_name} bought ${trade.token_symbol} for ${trade.sol_amount} SOL`
);
}
return newBuys;
}
The most powerful signal is when multiple KOLs buy the same token. The coordination endpoint detects this automatically:
// src/coordination.ts
import { client } from "./client";
async function getCoordinationSignals() {
const { tokens } = await client.kol.coordination({
period: "1h", // Look at the last hour
min_kols: 3, // At least 3 KOLs buying
});
// Only accumulating tokens (net positive flow)
const accumulating = tokens.filter((t) => t.signal === "accumulating");
for (const token of accumulating) {
console.log(
`[COORDINATION] ${token.token_symbol}: ${token.kol_count} KOLs buying, ` +
`${token.total_sol_volume.toFixed(1)} SOL volume`
);
}
return accumulating;
}
When a token appears in both the KOL feed and coordination signals, that's a strong entry signal.
The coordination endpoint requires 3+ KOLs. But what if you want to catch tokens before they hit that threshold? The hot tokens endpoint detects accelerating KOL buy interest — tokens where the rate of new KOL buyers is speeding up:
// src/momentum.ts
import { client } from "./client";
async function getMomentumTokens() {
const { hot_tokens } = await client.kol.hotTokens({
period: "6h",
min_kols: 1,
limit: 10,
});
// Acceleration > 1.0 means buying is speeding up
const accelerating = hot_tokens.filter((t) => t.acceleration > 1.5);
for (const token of accelerating) {
console.log(
`[MOMENTUM] ${token.token_symbol}: ${token.kols_total} KOLs, ` +
`acceleration ${token.acceleration}x, net flow ${token.net_flow.toFixed(1)} SOL`
);
}
return accelerating;
}
This catches the ramp-up phase — 1-2 KOLs buying, then 2 more in the last 90 minutes. By the time coordination triggers at 3+, early movers already have an edge.
Not all KOL coordination is equal. Some KOLs trade together frequently — they share alpha channels or run similar strategies. The pairs endpoint reveals these clusters:
// src/affinity.ts
import { client } from "./client";
async function getKolClusters() {
const { pairs } = await client.kol.pairs({
period: "7d",
min_shared: 5, // At least 5 shared tokens
limit: 10,
});
for (const pair of pairs) {
console.log(
`${pair.kol_a.name} + ${pair.kol_b.name}: ` +
`${pair.shared_token_count} shared tokens, ${pair.agreement_rate}% agreement`
);
}
return pairs;
}
Use this to build a more nuanced watchlist — when two KOLs with high affinity both buy the same new token, that's a stronger signal than two random KOLs who rarely overlap.
Before copying any KOL, check their timing profile to understand how they trade:
// src/kol-timing.ts
import { client } from "./client";
async function analyzeKolBehavior(wallet: string) {
const { timing } = await client.kol.timing(wallet, { period: "30d" });
console.log(`Tokens traded: ${timing.tokens_traded}`);
console.log(`Avg hold: ${timing.avg_hold_minutes} minutes`);
console.log(`Closed within 1h: ${timing.pct_closed_1h}%`);
console.log(`Closed within 6h: ${timing.pct_closed_6h}%`);
console.log(`Avg buy size: ${timing.avg_buy_size_sol} SOL`);
console.log(`Most active hours (UTC): ${timing.most_active_hours}`);
// Skip scalpers who hold < 5 minutes — too fast to copy profitably
if (timing.avg_hold_minutes && timing.avg_hold_minutes < 5) {
console.log("⚠️ Scalper — too fast to copy. Skip.");
return false;
}
return true;
}
A KOL who holds for 30+ minutes gives you time to enter. A scalper who exits in under a minute will be gone before your bot even executes.
When your bot detects a buy signal, execute the trade using Jupiter's swap API:
// src/swap.ts
import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js";
const connection = new Connection(process.env.SOLANA_RPC_URL!);
const wallet = Keypair.fromSecretKey(/* decode your private key */);
async function executeBuy(tokenMint: string, solAmount: number) {
// Get Jupiter quote
const quoteUrl = `https://quote-api.jup.ag/v6/quote?inputMint=So11111111111111111111111111111111111111112&outputMint=${tokenMint}&amount=${Math.floor(solAmount * 1e9)}&slippageBps=500`;
const quote = await fetch(quoteUrl).then((r) => r.json());
// Get swap transaction
const swapRes = await fetch("https://quote-api.jup.ag/v6/swap", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
quoteResponse: quote,
userPublicKey: wallet.publicKey.toBase58(),
}),
});
const { swapTransaction } = await swapRes.json();
// Sign and send
const tx = VersionedTransaction.deserialize(
Buffer.from(swapTransaction, "base64")
);
tx.sign([wallet]);
const sig = await connection.sendTransaction(tx);
console.log(`[SWAP] Bought ${tokenMint} — tx: ${sig}`);
}
Instead of polling, you can set up webhooks to receive push notifications the moment a KOL trades:
// src/webhook.ts
import { client } from "./client";
async function setupWebhook() {
const { webhook } = await client.webhooks.create({
url: "https://your-server.com/kol-webhook",
events: ["kol:trade"],
filters: { min_sol: 1, action: "buy" },
});
console.log(`Webhook created: ${webhook.id}`);
console.log(`Secret (save this): ${webhook.secret}`);
}
Your webhook endpoint receives payloads like:
{
"event": "kol:trade",
"data": {
"kol_name": "ansem",
"wallet": "7xKX...",
"action": "buy",
"token_symbol": "BONK",
"sol_amount": 5.2,
"token_mint": "DezX..."
}
}
// src/index.ts
import { client } from "./client";
const MAX_SOL = parseFloat(process.env.MAX_SOL_PER_TRADE || "0.1");
const POLL_INTERVAL = 15_000; // 15 seconds
async function main() {
// Find top performers to copy
const { leaderboard } = await client.kol.leaderboard({ period: "7d" });
const watchlist = leaderboard
.filter((k) => k.win_rate && k.win_rate > 0.55)
.map((k) => k.wallet);
console.log(`Watching ${watchlist.length} KOLs...`);
const seen = new Set<string>();
setInterval(async () => {
try {
const { trades } = await client.kol.feed({ limit: 20, action: "buy" });
for (const trade of trades) {
if (seen.has(trade.signature)) continue;
seen.add(trade.signature);
if (!watchlist.includes(trade.wallet)) continue;
// Check coordination — is anyone else buying this?
const { tokens } = await client.kol.coordination({
period: "1h",
min_kols: 2,
});
const isCoordinated = tokens.some(
(t) => t.mint === trade.mint && t.signal === "accumulating"
);
if (isCoordinated) {
console.log(
`[SIGNAL] ${trade.kol_name} + ${tokens.find((t) => t.mint === trade.mint)?.kol_count} others buying ${trade.token_symbol}`
);
// executeBuy(trade.mint, MAX_SOL);
}
}
} catch (err) {
console.error("Poll error:", err);
}
}, POLL_INTERVAL);
}
main();
| Tier | Price | Daily limit | Best for |
|---|---|---|---|
| Free | $0/mo | 200 requests | Testing and prototyping |
| Pro | €43/mo (≈ $49) | 10,000 requests | Live trading bots |
| Ultra | €131/mo (≈ $149) | 100,000 requests | Multi-strategy, DEX streaming |
See pricing + start free at madeonsol.com/pricing to start building.
MadeOnSol tracks 950+ curated Solana KOL wallets including well-known traders, fund managers, and alpha callers. The list is actively maintained to add new performers and remove inactive wallets.
Trades appear in the API within seconds of on-chain confirmation. For sub-second latency, use webhooks (Pro) or WebSocket streaming (Pro/Ultra) instead of polling.
The free tier (200 requests/day) is good for testing but not for live trading. A polling bot making one request every 15 seconds would need ~5,760 requests/day. The Pro tier (10,000/day) covers this comfortably.
Yes. MadeOnSol provides structured API data with KOL names, PnL statistics, win rates, coordination signals (multiple KOLs buying the same token), KOL affinity analysis, momentum detection, timing profiles, and deployer enrichment — all via API. Wallet explorers show raw transactions without this context.
The /kol/tokens/hot endpoint detects tokens where KOL buy interest is accelerating. It compares the rate of new KOL buyers in the last 25% of the time window to the overall rate. An acceleration score above 1.0 means buying is speeding up. This catches early signals before the coordination endpoint (which requires 3+ KOLs) triggers.
The /kol/pairs endpoint shows which KOLs frequently trade the same tokens. When two KOLs with high affinity (e.g., 85%+ agreement rate) both buy a new token, it's a stronger signal than two random KOLs who rarely overlap. Use this to weight your copy-trading signals.
It isn't — this bot is one concrete implementation of the same signal chain described in building a Solana copy-trading signal feed with the Wallet Tracker API. If you would rather visualize the feed than execute trades automatically, see building a real-time KOL copy-trade dashboard with Next.js.