Solutions · Token scanners
MadeOnSol gives Solana token scanners the evidence behind each result: token:surges events for young tokens running against their launch market cap and dormant tokens trading again, each carrying its tape, tracked KOL buyers, early-buyer cohort, deployer tier and risk flags, plus REST lookups for token, deployer and buyer-quality detail, while the ranking stays in your product.
A scanner should tell its users what is becoming active, where, who launched it, which tracked wallets are in it and what looks wrong. The surge event already carries most of that; one REST call adds the rest.
token:surges on the stream and webhooks from Pro. The token, deployer and buyer-quality lookups work on a Free key; the risk score, surge history and screener start on Pro.
One scanner row, assembled
Every cell is a field from one surge event and one token lookup.
symbol · age_secondstier · mc_multipletape.*deployer.tier · bonding_ratekol.buyers · alpha_wallet_countrisk_flagsyour logicThe problem
"New token" is not a scanner result. For every mint a scanner has to decide whether the move was bought or only marked, who launched it and how their launches went, which tracked wallets are already in, and what looks wrong. From raw Solana data that means decoding launches and swaps on every venue, scoring deployers, knowing which wallets matter and re-checking all of it for each candidate.
Show me
Four steps, in the order a scanner runs them. Keys are what the API sends; values are illustrative.
One named subscription on /ws/v1/stream. kinds, tiers, exclude_flags and min_mc_usd are applied on the server, the same filters the token:surge webhooks take. From Pro.
// wss://madeonsol.com/ws/v1/stream (token from POST /api/v1/stream/token)
{
"type": "subscribe",
"sub_id": "scanner-surges",
"channels": [
"token:surges"
],
"filters": {
"kinds": [
"surge",
"revival"
],
"tiers": [
"strong",
"breakout"
],
"exclude_flags": [
"bundled_launch",
"thin_liquidity"
],
"min_mc_usd": 20000
}
}Each frame is the stored fire: market cap and its launch multiple, the tape since birth, tracked KOL buyers, the first-20 cohort, the deployer and risk_flags. Dedupe on id; it is the same on a live frame, a replay and a resume.
{
"channel": "token:surges",
"sub_id": "scanner-surges",
"event": "token:surge",
"id": "token:surge:52817",
"seq": 391204,
"data": {
"id": 52817,
"kind": "surge",
"tier": "strong",
"mint": "9kQu...pump",
"symbol": "RUNNER",
"name": "Runner",
"launchpad": "pumpfun",
"primary_dex": "pumpfun",
"fired_at": "2026-09-24T14:09:12.000Z",
"birth_at": "2026-09-24T14:00:03.000Z",
"birth_source": "deployer",
"age_seconds": 549,
"market_cap_usd": 41200,
"liquidity_usd": 12900,
"liquidity_to_mc_ratio": 0.313,
"price_usd": 0.0000412,
"baseline_mc_usd": 5480,
"baseline_source": "launch",
"mc_multiple": 7.518,
"mc_change_3m_pct": 119.15,
"peak_mc_usd": 41200,
"pct_of_peak": 100,
"bonding_progress_pct": 61.4,
"is_bonded": false,
"tape": {
"since": "2026-09-24T14:00:03.000Z",
"available": true,
"source": "candles",
"buys": 212,
"sells": 74,
"trades": 286,
"buy_volume_usd": 24810.5,
"sell_volume_usd": 7920.3,
"volume_usd": 32730.8,
"unique_buyers": 141,
"unique_wallets": 168,
"trades_per_wallet": 1.7,
"wallet_data_available": true
},
"kol": {
"buyers": 2,
"buys": 3,
"sells": 0,
"names": [
"alpha_scout",
"degen_dan"
]
},
"early_buyers": {
"count": 20,
"bundled": 1,
"cohort_sol": 14.62,
"sold": 6,
"sniper_wallets": 3
},
"deployer": {
"wallet": "7vfC...rXs2",
"tier": "good",
"bonding_rate": 0.31,
"total_bonded": 9,
"total_deployed": 29,
"runner_rate": 0.12,
"labeled_tokens": 7
},
"deployer_tier": "good",
"mint_authority_revoked": true,
"freeze_authority_revoked": true,
"is_token_2022": false,
"risk_flags": [
"sniper_heavy"
],
"detail_url": "https://madeonsol.com/api/v1/token/9kQu...pump"
},
"ts": 1790258952184
}detail_url is GET /api/v1/token/{mint}. include=deployer,buyer_quality runs both lookups in the same request and counts as one call. A Free key gets this response; KOL wallet addresses in top_buyers are added from Ultra.
// GET /api/v1/token/9kQu...pump?include=deployer,buyer_quality Authorization: Bearer msk_...
{
"token": {
"mint": "9kQu...pump",
"name": "Runner",
"symbol": "RUNNER",
"price_usd": 0.0000413,
"market_cap": 41350,
"liquidity_usd": 12950,
"primary_dex": "pumpfun",
"launchpad": "pumpfun",
"mint_authority_revoked": true,
"freeze_authority_revoked": true,
"price_is_stale": false,
"age_seconds": 571,
"early_buyer_exit": {
"cohort": 20,
"still_holding": 14,
"sold": 6,
"still_holding_pct": 70
},
"deployer": {
"wallet": "7vfC...rXs2",
"tier": "good",
"bonding_rate": 0.31,
"total_deployed": 29,
"total_bonded": 9,
"recent_bond_rate": 0.4,
"history_status": "established"
},
"kol_activity": {
"status": "ok",
"buying_kols": 2,
"selling_kols": 0,
"net_flow_sol": 6.4,
"signal": "accumulating",
"top_buyers": [
{
"name": "alpha_scout",
"sol_amount": 4.1
},
{
"name": "degen_dan",
"sol_amount": 2.3
}
],
"window_hours": 168,
"buys": 3,
"sells": 0,
"counts_basis": "complete_window"
}
},
"as_of": "2026-09-24T14:09:34.201Z",
"deployer_profile": {
"is_deployer": true,
"deployer": {
"wallet_address": "7vfC...rXs2",
"tier": "good",
"total_tokens_deployed": 29,
"total_bonded": 9,
"instant_bonds": 1,
"bonding_rate": 0.31,
"recent_bond_rate": 0.4,
"recent_outcomes": "BDDBDBDDBD",
"runner_rate": 0.12,
"avg_time_to_bond_minutes": 41.5
},
"launchpad_tokens": []
},
"buyer_quality": {
"mint": "9kQu...pump",
"score": 64,
"confidence": "medium",
"signal": "positive",
"breakdown": {
"alpha_wallet_count": 7,
"kol_count": 2,
"bundle_buyer_count": 1,
"avg_historical_win_rate": 54.2,
"bot_dominated": false,
"wallets_with_history": 11,
"qualified_win_rate_wallets": 8,
"dump_cluster_count": 0,
"recycled_early_buyer_count": 3
},
"cached_at": "2026-09-24T14:09:34.180Z"
},
"included": [
"deployer",
"buyer_quality"
]
}GET /tokens/surges pages the same rows with filters on mint, launchpad, deployer_tier and flags. About an hour after a fire, outcome adds the market cap, peak and low one hour later, up or down; stats=1 adds hit rates per tier.
// GET /api/v1/tokens/surges?mint=9kQu...pump
{
"events": [
{
"id": 52817,
"kind": "surge",
"tier": "strong",
"mint": "9kQu...pump",
"fired_at": "2026-09-24T14:09:12.000Z",
"market_cap_usd": 41200,
"deployer_tier": "good",
"risk_flags": [
"sniper_heavy"
],
"outcome": {
"computed_at": "2026-09-24T15:20:00.000Z",
"mc_usd_1h_after": 34600,
"peak_mc_usd_1h_after": 66100,
"low_mc_usd_1h_after": 29800,
"mc_1h_multiple": 0.84,
"peak_1h_multiple": 1.604,
"priced_after_1h": true
}
}
],
"pagination": {
"limit": 50,
"count": 1,
"has_more": false,
"next_since": "2026-09-24T14:09:12.000Z",
"next_before": "2026-09-24T14:09:12.000Z"
},
"stream": {
"channel": "token:surges",
"url": "wss://madeonsol.com/ws/v1/stream"
}
}wallet_data_available: false
unique_buyers and trades_per_wallet are null, never zero, when the mint is outside wallet-level trade coverage, which spans the pump.fun pipeline (pump.fun, LaunchLab/bonk, bags).
baseline_source: "late"
The engine first saw the token after launch, so the launch multiple is not applied and the tier rests on the dollar floor and velocity.
kind: "revival"
A revival has no tier. It carries dormant_hours, prev_mc_usd and mc_vs_prev_multiple instead of the launch fields.
Try it
The surge page shows the newest fires and how each tier did over the last seven days. Paste any mint into the token lookup for the same deployer, KOL and risk context, or call GET /api/v1/token/{mint} on a Free key.
From surge to scanner result
Five of them MadeOnSol answers with fields you can filter and sort on; the sixth is yours. Each stage names the channel or endpoint, the fields that matter and the plan it starts on.
1 · Activity
Pro
A surge fires for a token under 30 minutes old running against its launch market cap; a revival for a token with no trade candle for 24 hours that buyers confirm on the tape. The market cap gained has to be paid for by buy volume, so a price mark in an empty pool does not fire. deployer:alerts adds new launches by tracked deployers.
token:surgesdeployer:alerts
kindtierage_secondsmc_multipletape.buy_volume_usdtape.unique_buyers
2 · Token
Free
The token lookup adds price, liquidity, venue, authorities and how much of the first-20 buyer cohort is still holding.
GET /token/{mint}
launchpadprimary_dexliquidity_usdmint_authority_revokedearly_buyer_exit
3 · Deployer
Free
The frame carries the deployer's tier and bonding rate; the deployer include adds the full profile: launches, graduations, runner rate and the last ten outcomes.
data.deployerGET /deployer-hunter/{wallet}
deployer.tierdeployer.bonding_ratedeployer.runner_raterecent_outcomes
4 · KOL and alpha
Freekol-consensus from Pro
Tracked KOL buyers since birth arrive on the frame. The lookup adds the 7-day KOL flow, and buyer quality says how many of the first 20 buyers are scored alpha wallets, KOLs or bundles.
data.kolGET /tokens/{mint}/buyer-qualityGET /tokens/{mint}/kol-consensus
kol.buyerskol_activity.signalbreakdown.alpha_wallet_countbreakdown.kol_count
5 · Risk context
Proflags ride on every event
12 named checks run on every fire and land in risk_flags. For a 0-100 score with its factors, the risk endpoint computes one at read time, from Pro.
data.risk_flagsGET /tokens/{mint}/risk
risk_flagsrisk_scorefactors
6 · Your ranking
Weights, thresholds, which flags hide a row, and when to alert are your scanner's logic. MadeOnSol supplies the evidence, not the order.
The context behind a row
Rendered from the classifier and the surge engine's own thresholds, so this section changes when the code does. Where each field is stored is in the data dictionary.
Every launch carries its deployer's tier, one of:
elite5+ launches, 5+ graduated, 40% lifetime and 50% recent graduation rategood3+ launches, 3+ graduated, 25% lifetime and 30% recent graduation ratemoderate3+ launches, 1+ graduated, 15% lifetime and 15% recent graduation raterising1 to 3 launches and every resolved launch graduated (a lone instant bond does not count)cold3+ launches and none of the last 10 graduatedunrankedEverything else, including new walletsA wallet whose graduations are all instant bonds (within 1 minute of the deploy, a bundle filling its own curve) is never elite, good or moderate. Launch alerts on deployer:alerts fire for elite, good and rising deployers.
earlyUp to 10 min old, market cap $12,000+ and 3× its launch market capstrongUp to 30 min old, $30,000+, 6× launch and 2× its low of the last 3 minbreakoutUp to 2 min old, $45,000+ and 8× launchrevivalNo trade candle for 24 h, then within 15 min at least 5 buys, $500 of buy volume and a $5,000 market cap, never on the price mark aloneA tier must hold on two samples at least 10 s apart, nothing fires before 20 s of age, and known liquidity must clear $1,500 and 2% of market cap. Each tier fires once per mint.
bundled_launchSeveral of the first 20 buyers bought in the same blockfew_buyersFew unique buyers since birth or revivalwash_patternMany trades per wallet across few walletsthin_liquidityLiquidity small in dollars or next to market capcold_deployerThe deployer's tier is coldsniper_heavySeveral early buyers are known sniper walletsearly_buyers_exitingHalf or more of the early-buyer cohort has soldsell_pressureMore sells than buys on the tape at fire timeno_tape_tradesThe price moved, but no covering tape holds a parsed swapno_prior_priceA revival with no earlier market cap to comparemint_authority_activeMint authority not revokedtransfer_feeToken-2022 transfer fee above zeroAbsent data never sets a flag. Exact thresholds are returned in definitions.risk_flags.
Plans
Lookups work on a free key; live discovery needs a stream or webhooks. Read from the tier table and the routes' own gates.
| Input | Where | Plan |
|---|---|---|
| Surges and revivals, live | token:surges on /ws/v1/stream | Pro |
| Surges and revivals, pushed | webhooks: token:surge, token:revival | Pro |
| Surge history, 1-hour outcomes, hit rates | GET /api/v1/tokens/surges | Pro |
| Launches by tracked deployers | deployer:alerts on /ws/v1/stream | Pro |
| Token, deployer profile and buyer quality | GET /api/v1/token/{mint}?include=deployer,buyer_quality | FreeKOL wallet addresses in top_buyers from Ultra |
| Risk score and factors | GET /api/v1/tokens/{mint}/risk | Pro |
| KOL consensus on a token | GET /api/v1/tokens/{mint}/kol-consensus | Pro |
| Screener with momentum sorts | GET /api/v1/tokens | Pro |
| Every parsed DEX trade | /ws/v1/dex-stream (dex:trades) | Ultra and Business |
Compare plans for request, stream and webhook limits. A scanner that shows this data to its users needs Business. Before you pick one, try the token lookup and the live surges.
Architecture
Keep the API key and stream token in your backend. It holds one stream connection, enriches each new mint once and caches the lookup, then applies your ranking before anything reaches a user.
1 · Source
Chain activity
Transactions and program events
2 · MadeOnSol
Data and intelligence layer
3 · Delivery
4 · Yours
Your scanner backend
Keys and business logic stay server-side
5 · Users
Ranked rows and alerts
Build outcomes
Showing MadeOnSol data to your own users requires Business. Rights by plan
Keep going
Solution
DEX Data
Every parsed swap, for scanners that watch the whole tape.
Solution
Trading Bots
Act on a scanner result with launch and KOL signals.
Solution
Token Risk
The risk score, its factors and deployer history for one token.
Solution
Smart Money
KOL and alpha-wallet intelligence behind the tracked-wallet stage.
Guide
A custom memecoin scanner in 80 lines
The screener endpoint in code.
Guide
Surge alerts, day one: 138 fires
What the fires did an hour later.
FAQ
Next step
See how surges played out, look up a token on a free key, then subscribe to token:surges and rank what arrives.