When multiple Solana KOLs buy the same token in a tight time window, that's a signal worth paying attention to. But not every cluster is equal — a cluster where 8 wallets bought over four slow hours is worlds apart from one where the same 8 wallets bought in a 15-minute burst. Yet until today, the MadeOnSol /kol/coordination endpoint scored them the same.
Today's v1.1 release addresses that, plus a few other gaps. Three things landed:
- Peak-density windows — surface the densest sub-interval inside the query period, not just the period aggregate.
- Exit detection — flag KOLs that net-sold after buying, so copy-trade bots can de-weight clusters where early entrants are already leaving.
- Composite score + push alerts — a 0–100
coordination_score plus a CRUD API for push alerts that fire within ~1s of a qualifying trade via HMAC-signed webhook and/or user-scoped WebSocket.
This post walks through what each one does, why we built it, and how to use it from any SDK.
The old endpoint, and what was missing
The pre-v1.1 /kol/coordination returned clusters like this:
{
"token": "4k3Dyjzv...",
"symbol": "PEPECAT",
"kols_count": 8,
"net_sol": 42.1,
"first_buy_at": "2026-04-20T14:02:11Z",
"last_buy_at": "2026-04-20T17:48:04Z",
"avg_winrate_7d": 58.2
}
Three problems with that shape:
- The timestamps are global. If a cluster spans four hours, the aggregate obscures the fact that 6 of those 8 KOLs actually bought within a 12-minute window. That 12-minute sub-interval is the real signal.
- It only counts buys. If three of those eight KOLs have already sold out of the position, the
kols_count=8 number is misleading — there are really only 5 wallets still holding.
- No score. With 15+ clusters in a busy hour, downstream consumers had to roll their own ranking logic. Everyone built something; no one built the same thing.
All three issues are fixed in v1.1, without breaking any of the existing fields.
New: peak-density windows
The response now includes a peak_window_start / peak_window_end pair and two new counters:
{
"token": "4k3Dyjzv...",
"symbol": "PEPECAT",
"kols_count": 8,
"peak_kols": 6,
"peak_buys": 9,
"peak_window_start": "2026-04-20T14:02:11Z",
"peak_window_end": "2026-04-20T14:14:33Z",
"first_buy_at": "2026-04-20T14:02:11Z",
"last_buy_at": "2026-04-20T17:48:04Z",
"exited_count": 2,
"coordination_score": 78,
"score_version": 1
}
Read that as: 8 total KOLs bought over 4 hours, but 6 of them piled in within a ~12-minute window starting at 14:02. That 12-minute cluster is what you want to act on. The full period is just context.
You control the window size with a new window_minutes query param (1–60, default 15). Shorter windows surface tighter bursts; longer windows smooth out jitter.
New: exit detection
exited_count flags KOLs in the cluster whose net position went negative after their initial buy — i.e. they've already sold more than they bought. This is intentionally aggressive: we don't care whether they took a small profit off the top; we care whether they're no longer long.
For a copy-trade bot, this is a strong de-weight signal. A cluster of 8 KOLs with exited_count: 5 is three KOLs holding, not eight. The raw kols_count stays honest to what happened; exited_count tells you what's still true.
New: a composite 0–100 coordination score
coordination_score is a single number that folds together what we think matters:
- Peak density — more KOLs per minute = higher score.
- Freshness — clusters that happened 10 minutes ago score higher than clusters from four hours ago.
- Exit penalty — every exited KOL subtracts from the score.
- Average winrate — cluster KOLs with strong 7d winrate score higher than cluster KOLs with weak records.
- Strategy diversity — 4 scalpers buying together is a weaker signal than a scalper + day-trader + swing-trader agreeing on the same token. Diverse agreement is rarer and historically more predictive.
The model-version lives in score_version so we can iterate without breaking existing consumers. The scoring weights may evolve; the response shape will not.
In practice, filter with min_score=70 to cut noise:
curl -H "Authorization: Bearer msk_..." \
"https://madeonsol.com/api/v1/kol/coordination?period=1h&min_score=70&window_minutes=15"
Filtering out majors
The new include_majors=false flag (default false) filters clusters on WIF, BONK, POPCAT, and other major memecoins. These will always look like "KOL coordination" because every KOL on Solana trades them — not actionable alpha, just noise. The blacklist is enforced server-side via the coordination_excluded_mints table; if you want a specific mint included or excluded, contact us.
If you do want the majors (for cohort benchmarking), pass include_majors=true.
New: push alerts API
The REST endpoint is good for "what's happening right now?" polling. It's not good for "notify me within one second when a high-score cluster forms." For that, v1.1 adds a companion API:
POST /api/v1/kol/coordination/alerts
GET /api/v1/kol/coordination/alerts
GET /api/v1/kol/coordination/alerts/{id}
PATCH /api/v1/kol/coordination/alerts/{id}
DELETE /api/v1/kol/coordination/alerts/{id}
You define a rule — min_kols, window_minutes, min_score, include_majors, cooldown, delivery — and on every kol:trade event, a server-side evaluator recomputes the affected mint's peak-density window and fires a signal if your rule matches.
Creating a rule
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({ apiKey: "msk_..." });
const { rule, webhook_secret } = await client.coordinationAlerts.create({
name: "fresh pump cluster",
min_kols: 4,
window_minutes: 15,
min_score: 70,
include_majors: false,
cooldown_min: 60,
score_jump_break: 10,
delivery_mode: "both",
webhook_url: "https://you.com/hooks/coord",
});
// Store webhook_secret now — it's shown once only.
console.log(webhook_secret);
A few fields worth calling out:
cooldown_min (default 60) — after a rule fires on a mint, we won't fire again on the same mint for this many minutes. Stops one hot token from flooding your webhook.
score_jump_break (default 10) — override the cooldown if the coordination score jumps by this many points since the last fire on the same mint. If a cluster goes from 72 → 88, that's meaningful enough to re-fire even inside the cooldown.
delivery_mode — "webhook", "websocket", or "both". Most people want both: WebSocket for instant UI updates, webhook for durable delivery when the WS connection drops.
The webhook payload
{
"event": "kol:coordination",
"rule_id": "a1b2c3d4-...",
"rule_name": "fresh pump cluster",
"user_id": "b9f...",
"fired_at": "2026-04-20T14:15:02Z",
"token_mint": "4k3Dyjzv...",
"token_symbol": "PEPECAT",
"peak_kols": 6,
"peak_buys": 9,
"exited_count": 0,
"coordination_score": 78,
"score_version": 1,
"peak_window_start": "2026-04-20T14:02:11Z",
"peak_window_end": "2026-04-20T14:14:33Z",
"kols": [
{ "wallet": "...", "name": "@alice", "action": "buy", "sol": 3.2, "at": "2026-04-20T14:02:11Z" },
...
]
}
Signed with:
X-MadeOnSol-Timestamp: 1745155302
X-MadeOnSol-Signature: sha256=<hmac-sha256 of "<timestamp>.<raw body>" using webhook_secret>
The same signing scheme as the copy-trade webhooks — verify examples are in our HMAC webhook signature guide. Reject any delivery older than ~5 minutes to defeat replay. Retries fire at 0s / 5s / 30s with an 8s fetch timeout per attempt.