For copy-trade bot developers
The MadeOnSol copy-trade API watches the Solana wallets you name, KOL or not, and sends one sized signal per matching swap by webhook, WebSocket or REST; your code places the trade.
Try it free
Pro
€43/month
Go further with Ultra
€131/month
Billed in euros by card. Checkout shows today's dollar estimate; your card issuer sets the exact amount. USDC or SOL at the day's rate. Cancel anytime.
Free, Pro and Ultra keys are for your own internal use, such as bots, backends and research. Showing MadeOnSol data to your own users, for example in a public terminal or dashboard, needs Business. Compare plans
Choose any valid wallet, or shortlist one from the KOL leaderboard for its name and track record, write the conditions once, and receive signals your bot can check, size and execute on its own terms.
Step one: choose the wallets
A rule can follow any valid Solana wallet. These optional endpoints help you find wallets worth copying.
GET /kol/walletsFreeOptional: the tracked KOL roster, one place to find wallets to evaluate. A rule can name any valid Solana wallet; one on this list also gets its KOL name on the signal.
wallet_addressis_activestrategy_tag
GET /kol/leaderboardFreeRank KOLs by PnL, win rate, profit factor or early entries over a chosen period.
profit_factor_30dpercentile_pnl_30dwinrate_30d
GET /kol/{wallet}/timingFreeHow long a KOL holds, so you know whether your bot can enter before they exit.
median_hold_minutespct_closed_1h
The problem
Following a wallet sounds like one webhook. A copy-trading product has to decide which traders deserve following, turn each of their transactions into a buy or sell with an amount, drop trades that are too small or entered at the wrong market cap, size the follow, never fire twice on a replayed transaction, and still reach a bot that was offline for a minute.
Show me
The full path for a single trade. Field names are the real request and payload keys; values are illustrative and the frames are abridged.
Name the source wallets (any valid Solana addresses; the two here are examples), a size floor, the side, a market cap band, how to size the follow and where to deliver. The response returns webhook_secret once.
POST /api/v1/copytrade/subscriptions
Authorization: Bearer msk_...
Content-Type: application/json
{
"name": "two wallets, early buys",
"source_wallets": ["CyaE...n5Lb", "4BdK...9sQe"],
"min_trade_sol": 1,
"only_action": "buy",
"min_mc_usd": 20000,
"max_mc_usd": 2000000,
"sizing_mode": "proportional",
"sizing_amount": 0.1,
"delivery_mode": "both",
"webhook_url": "https://bot.example.com/hooks/copytrade"
}MadeOnSol parses the swap with the market cap at the trade and hands it to the rule engine. This example wallet happens to be a tracked KOL, so the same swap also appears on kol:trades with its KOL name. A wallet that is not a KOL is checked the same way and its signal carries kol_name null.
{
"id": "kol:trade:5K7j...",
"seq": 184219,
"channel": "kol:trades",
"event": "kol:trade",
"data": {
"kol_name": "Cented",
"wallet_address": "CyaE...n5Lb",
"token_mint": "8vdc...pump",
"token_symbol": "NEW",
"action": "buy",
"sol_amount": 5.27,
"token_amount": 18400000,
"tx_signature": "5K7j...",
"slot": 449476688,
"traded_at": "2026-09-23T10:00:35.000Z",
"market_cap_usd_at_trade": 28430,
"liquidity_usd_at_trade": 9120,
"primary_dex": "pumpfun"
},
"ts": 1790157635000
}5.27 SOL clears the floor, the entry sits inside the band, and 0.1 of the source trade's size becomes suggested_sol_amount. A webhook receives the same data inside { event, data, timestamp }.
{
"id": "copytrade:signal:77120",
"seq": 184220,
"channel": "copytrade:signals",
"event": "copytrade:signal",
"data": {
"signal_id": 77120,
"subscription_id": 41,
"subscription_name": "two wallets, early buys",
"fired_at": "2026-09-23T10:00:36.120Z",
"source_wallet": "CyaE...n5Lb",
"kol_name": "Cented",
"action": "buy",
"token_mint": "8vdc...pump",
"token_symbol": "NEW",
"source_sol_amount": 5.27,
"suggested_sol_amount": 0.527,
"tx_signature": "5K7j...",
"traded_at": "2026-09-23T10:00:35.000Z",
"market_cap_usd_at_trade": 28430,
"price_usd_at_trade": 0.0000284
},
"ts": 1790157636120
}Handle it on the stream, or verify the webhook signature over timestamp.body with the rule's secret. Everything after this line is your code.
import crypto from "node:crypto";
import { MadeOnSolREST } from "madeonsol-x402";
// Option A: WebSocket (delivery_mode "websocket" or "both")
const rest = new MadeOnSolREST({ apiKey: process.env.MADEONSOL_API_KEY! });
const stream = rest.stream();
stream.on("copytrade:signal", (s) => myBot.consider(s)); // your sizing caps, your signer
stream.subscribe(["copytrade:signals"]);
// Option B: webhook (delivery_mode "webhook" or "both")
// rawBody: the exact request bytes (never re-serialized JSON)
// secret: the webhook secret exactly as the API returned it (do not hex-decode)
function verifyMadeOnSol(rawBody: Buffer | string, timestamp: string | undefined, signature: string | undefined, secret: string): boolean {
// X-MadeOnSol-Timestamp: unix milliseconds, fresh on every retry
if (typeof timestamp !== "string" || !/^\d{1,16}$/.test(timestamp)) return false;
// Replay window: your choice, the server does not enforce one
if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60_000) return false;
// X-MadeOnSol-Signature: 64 hex characters, bare (no algorithm prefix)
if (typeof signature !== "string" || !/^[0-9a-f]{64}$/i.test(signature)) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest();
// Both sides are exactly 32 bytes here, so this compares in constant time and cannot throw
return crypto.timingSafeEqual(expected, Buffer.from(signature, "hex"));
}
// In your raw-body route handler (webhook_secret is returned once at create):
// verifyMadeOnSol(rawBody, headers["x-madeonsol-timestamp"],
// headers["x-madeonsol-signature"], process.env.RULE_WEBHOOK_SECRET!)
// body: { "event": "copytrade:signal", "data": { ...same fields, no signal_id }, "timestamp": "..." }
// dedupe webhook retries on data.subscription_id + data.tx_signaturerest.copyTradeCreate(params)rest.copyTradeSignals({ since })client.copy_trade_create(...)madeonsol_copytrade_createTry it
A free key reads the KOL feed over REST (5 minutes delayed) plus the roster and leaderboard, enough to shortlist wallets and replay what a draft rule would have caught. The KOL Tracker shows the same trades in the browser.
From rule to signal
Each stage reads one part of your rule and one part of the source wallet's trade. The first seven run on MadeOnSol; the last one is yours.
01 · MadeOnSol
source_walletsvswallet_addressEvery qualifying swap by a wallet one of your active rules lists is checked against that rule, whether or not the wallet is a known KOL. KOL status only adds kol_name to the signal. A trade that arrives more than 10 seconds after it landed on chain is not signalled.
02 · MadeOnSol
min_trade_solvssol_amountTrades below your floor are dropped. The default floor is 0. USDC and USDT legs are converted to SOL first; a swap by a non-KOL wallet quoted in another token is skipped rather than sized wrong.
03 · MadeOnSol
only_actionvsactionbuy, sell or both. A rule created without it follows buys only (buy).
04 · MadeOnSol
min_mc_usdmax_mc_usdvsmarket_cap_usd_at_tradeThe source trade's entry must sit inside your band. When a band is set and the trade's market cap is unknown, no signal fires.
05 · MadeOnSol
sizing_modesizing_amountvssol_amountfixed returns sizing_amount as SOL. proportional and percent_source both multiply the source trade's SOL amount by sizing_amount. The result is suggested_sol_amount.
06 · MadeOnSol
subscription_idvstx_signatureOne signal per rule per transaction. A trade replayed after a reconnect or restart does not fire the rule twice.
07 · MadeOnSol
delivery_modewebhook_urlwebhook posts to your URL signed with the rule's own secret, up to 3 attempts. websocket publishes to copytrade:signals for your connections only. both does both.
08 · Your bot
suggested_sol_amountYour code decides: cap the size, check the token, then build, sign and send the swap with your own wallet and RPC.
Filled chips are rule fields, outlined chips are fields of the source trade or the signal. Copy-trade API reference
Compared
A plain address webhook tells you something touched a wallet. A copy-trade signal tells you a wallet your rule follows made a trade your rule accepts. Seeing every transaction a wallet makes, not only qualifying swaps, is what wallet tracking events are for.
| Concern | Generic wallet webhook | MadeOnSol copy-trade signal |
|---|---|---|
| What triggers it | Any transaction that touches the address: transfers, approvals, failed swaps, dust. | A parsed buy or sell by a wallet your rule follows, with SOL and token amounts.actionsol_amounttoken_amount |
| Who the trader is | An address. Track record is yours to build. | Any wallet you choose. When it is a known KOL, the signal names it, and its leaderboard record and hold-time profile are there to query before you follow.kol_nameprofit_factor_30dmedian_hold_minutes |
| Qualification | Your service receives everything and filters it. | Size floor, side and market cap band are checked on the server before anything is sent.min_trade_solonly_actionmin_mc_usdmax_mc_usd |
| What you receive | A raw transaction to decode. | A structured signal naming the rule, the source trade, the market cap at entry and a suggested size.subscription_idsuggested_sol_amountmarket_cap_usd_at_trade |
| Duplicates and history | Deduplication and storage are on your side. | One signal per rule per transaction; signals stay readable over REST for 7 days with a delivery flag.tx_signaturedelivereddelivered_at |
Plans
| Step | Where | Plan |
|---|---|---|
| Find wallets to copy (optional) | GET /kol/wallets, /kol/leaderboard, /kol/{wallet}/timing | Free |
| KOL trades over REST | GET /kol/feed | Free5 minutes delayed on Free |
| KOL trades live | kol:trades channel | From Pro |
| Copy-trade rules | POST /copytrade/subscriptions | From ProRules and wallets per rule depend on the plan |
| Signals, pushed | Per-rule webhook, copytrade:signals channel | From Pro |
| Signal history | GET /copytrade/signals | From Pro |
| Multi-KOL confirmation | kol:coordination channel | From Pro |
Compare plans for the number of rules and wallets per rule.
Architecture
1 · Source
Chain activity
Transactions and program events
2 · MadeOnSol
Data and intelligence layer
3 · Delivery
4 · Yours
Your caps, checks and signer
Keys and business logic stay server-side
5 · Users
Your copy-trading product
Build outcomes
Showing MadeOnSol data to your own users requires a Business plan. See pricing
Keep going
Solution
Smart Money
Find wallets to evaluate: the tracked KOL wallets and how they trade.
Solution
Wallet Tracking
Every transaction a wallet makes, not only the swaps a rule acts on.
Product
KOL Tracker
Live KOL trades and the leaderboard in the browser.
Guide
Choose wallets by profit factor and percentile rank
Selection metrics beyond win rate.
Guide
Backtest a copy-trade strategy on priced KOL trades
Test a rule before it runs live.
Guide
Swap on a KOL buy with Jupiter in TypeScript
The execution layer in your own code.
FAQ
Next step
See how the trades are verified, shortlist wallets on a free key (the KOL leaderboard is one place to start), then turn the shortlist into a rule.