madeonsol
⭐ Star on GitHub · 📂 Examples · 📚 docs.rs · 🌐 API docs
Official Rust SDK for the MadeOnSol Solana API — typed,
async, tokio-based, rustls-only.
Real-time Solana trading intelligence: track 1,000+ KOL wallets with <3s latency on paid keys and x402 pay-per-call (free-tier live feeds are 5-min delayed), score 6,700+ Pump.fun deployers by reputation, detect multi-KOL coordination signals, push every pump.fun graduation the second it bonds, verify any wallet's current on-chain holdings, and stream every DEX trade across 9+ programs.
Free tier: 200 requests/day across 40+ endpoints (live feeds 5-min delayed; paid tiers are real-time) — no signup payment. Get a key at https://madeonsol.com/pricing.
This is the keyed REST SDK — authenticate with an API key (
msk_…). It covers the full endpoint surface (KOL intelligence, deployer intel, token risk/buyer-quality/bundle, Signal Scorecard, wallet PnL, DEX firehose). Want x402 pay-per-call instead — no signup, your agent's wallet pays per request in USDC? Use the TypeScriptmadeonsol-x402or Pythonmadeonsol-x402clients.
New in 0.27.0 — token surges & revivals: momentum fires with the honest half attached. One keyed (PRO+) method + one WebSocket channel.
client.token.surges(&TokenSurgesParams)(GET /tokens/surges, typedTokenSurgesResponse) —SurgeKind::Surge= a token < 30 min old whose MC runs hard vs its launch MC (SurgeTier::Early≤ 10 min / ≥ $12k / ≥ 3×,Strong≤ 30 min / ≥ $30k / ≥ 6× and still climbing,Breakout≤ 2 min / ≥ $45k / ≥ 8× — each fires once per mint, and only when SUSTAINED across ≥ 10 s, never on a one-tick mark);SurgeKind::Revival= a token with no trade candle for ≥ 24 h that started trading again, confirmed by real buys + buy volume on the tape, never by the price move alone. Hard gates on both: liquidity ≥ $1.5k and ≥ 2 % of MC, and the MC gained must be paid for (buy volume ≥ 3 % of the move — a spoof-pool mark moves MC on ~$0). EveryTokenSurgeEventcarriesSurgeTape(buys / sells / volume;unique_buyersonly where wallet data exists —wallet_data_available: falseotherwise, never inferred),SurgeKol,SurgeEarlyBuyers(bundled / sold / sniper wallets),SurgeDeployerandrisk_flags: Vec<SurgeRiskFlag>(BundledLaunch,FewBuyers,WashPattern,ThinLiquidity,ColdDeployer,SniperHeavy,EarlyBuyersExiting,SellPressure,NoTapeTrades,NoPriorPrice,MintAuthorityActive,TransferFee); rows ≥ 65 min old carry the +1 hSurgeOutcome(mc_1h_multiple,peak_1h_multiple,priced_after_1h) andstats: Some(true)returns per-(kind, tier) hit-rates (SurgeStats) — out-of-sample by construction. Filterskind,tier,mint,launchpad,deployer_tier,min_mc_usd/max_mc_usd,min_buys,exclude_flags,only_clean; cursorssince/before. Stream:token:surges(eventstoken:surge/token:revival, payloadTokenSurgeStreamEvent; subscribe filtersSurgeSubscribeFilters:kinds,tiers,launchpads,exclude_flags,min_mc_usd/max_mc_usd,deployer_tier). Nearly every scalar is anOptionwith#[serde(default)]—Nonemeans unknown, never zero, and an older or newer server never breaks deserialization. Keyed (msk_) API only — not on the x402 rail; BASIC gets HTTP 403.
New in 0.26.1 — fix: stream tokens never expire, and
StreamTokendeserializes again. Since 2026-08-27POST /stream/tokenreturns the SAME token on every call and it never expires — it stops working only if your subscription lapses or you replace it with{"rotate": true}(the old value keeps working for 60 s). The API now sendsexpires_at: nullandnext_refresh_at: null, which 0.26.0'sStreamToken { expires_at: String }refused to deserialize, soclient.stream.get_token()errored for every caller.StreamToken.expires_atis nowOption<String>(alwaysNone; kept for wire compatibility — do not schedule refreshes on it),next_refresh_atstaysOption<String>(alwaysNone), and two fields are new:rotated: Option<bool>(Some(true)when the call replaced an existing token) andlifetime: Option<String>(the server's plain-English statement of the above). New methodclient.stream.rotate_token()sends{"rotate": true}for the leaked-token case. A WebSocket close code4001means "callget_token()again and reconnect", never "the token timed out".
New in 0.26.0 — token locks & vesting, upcoming unlocks, and pump.fun creator-fee sharing. Five keyed (PRO+) methods on
client.token+ two WebSocket channels.token.locks(mint, &TokenLocksParams)(GET /tokens/{mint}/locks, typedTokenLocksResponse) — every on-chain Streamflow / Jupiter Lock / Bonfida vesting contract on a mint with the schedule (start / cliff / period / end), the terms (cancelable_by_sender— a cancelable lock is a weaker promise —cancelable_by_recipient,transferable,can_topup) and a live-derived view (locked_rawnow,unlocked,withdrawn,claimable,LockStatus,next_unlock), plus aTokenLocksSummary(exactlock_count,distinct_lockers, locked / deposited raw + ui + usd + % of supply,unlocking_7d_*/unlocking_30d_*, nearestnext_unlock,active_cancelable_by_sender).token.locks_feed(&TokenLocksFeedParams)(GET /tokens/locks) — cross-token feed of NEW contracts, newest first,since/beforecursors frompagination.next_since/next_before.token.unlocks(&TokenUnlocksParams)(GET /tokens/unlocks) — upcoming unlock EVENTS (UnlockEventKind: cliff / period / final / tranche) insideUnlockWindow1h…90dwithamount_*andwindow_amount_*, sorted byUnlocksSort. LP locks are NOT included in any of the three.token.fee_shares(mint)(GET /tokens/{mint}/fee-shares, typedTokenFeeSharesResponse) — the pump.funSharingConfig: who receives what share (bps) of a coin's creator fees,is_admin/is_social_pda(fees earmarked for an X account etc. —FeeShareSocial::platform2 = X,user_id= the platform-native numeric id),redirected_bps,social_bps,is_default: Some(true)= 100% to the creator, plus theFeeDistributionsrollup and confighistory.token.fee_claims(&TokenFeeClaimsParams)(GET /tokens/fee-claims) — the fee-event feed (FeeEventType:Distributionwithpayouts,SocialClaim,SharesCreated/SharesUpdated/SharesReset,CreatorTransferred;CreatorClaimonly when asked viaevent_type). Fee history starts 2026-08-17. Every base-unit amount (*_raw) is aString; ui / usd / pct companions areOptionandNonewhen decimals or price are unknown. Streams:token:locks(eventtoken:lock, payloadTokenLockEvent, one frame per NEW contract) andtoken:fee_claims(eventtoken:fee_claim, payloadTokenFeeClaimEvent). Keyed (msk_) API only — none of these are on the x402 rail; BASIC gets HTTP 403.
New in 0.25.0 — live holder census: exact holder count, labelled holders, and pools that are named, not just excluded.
client.token().holders(mint)(typedTokenHoldersResponse) bindsGET /tokens/{mint}/holders(PRO+): every token account of the mint read from the ledger atconfirmedand merged per owner, soconcentration.holder_countis EXACT (distinct non-zero owners minus pools / bonding curves / burns) — never a trade-derived estimate; it isnullonly when the provider refuses the census for a mega-cap, in which case you get the top-20 view andsource.census_fallback_reasonsays so. Each disclosed owner carries our labels (deployer/kol/early_buyer/bundle/bot/dump_cluster— empty means unknown to us, not clean), andexcluded[]NAMES what was taken out of the circulating denominator:reason=pool(withdex+pool_address),bonding_curve(pump.fun / LaunchLab),burn, orprogram_accountonly when we genuinely cannot attribute the PDA;pool_pct/burned_pct/program_pctsplit the exclusion. Amounts are raw u64 strings. Disclosure: PRO ranks 1–10, ULTRA 1–50, BUSINESS 1–100 — the maths is tier-independent. Big tokens take 5–30 s upstream: you get503 holder_scan_in_progresswithretry_after_seconds: 20while the scan finishes into the cache, and the retry is instant.
New in 0.24.0 — two prices on the trade tape, and the right one is now the default. The trade tape now tells you what a trade actually cost.
price_sol/price_usdon each trade are THIS trade's executed price —sol_amount / token_amount, reconciling exactly with the amounts on the same row and with the PnL endpoints. Becausesol_amountis the wallet's net SOL movement, that is the trader's all-in effective rate: swap fee and any account rent included, not the pool mid. The market-cap tracker's canonical pool price moved to the newmarket_price_sol/market_price_usdfields — sampled once per token per pool update, so every trade in the same slot shares it. Until nowprice_solcarried that canonical value and disagreed with the row's own amounts by a 7.9% median (p90 ~74%): a stale market price reads low in a pump and high in a dump, so anything you averaged out of the tape inherited the bias instead of cancelling it. Useprice_solfor cost basis, fills and PnL;market_price_solfor a per-token series independent of trade size and direction.TokenTradeandWalletTradecarry all four asOption<f64>, so an older server that omits them still deserializes.
New in 0.23.0 — Pool depth / price impact + dev block on risk.
client.token.depth(mint, ¶ms)(GET /tokens/{mint}/depth, PRO+) returns per-pool price-impact / slippage: for each supported pool aDepthPoolwithspot_price_sol,fee_pct,source("stream"reserves or"live_rpc"curve virtual reserves),reserves_age_ms, per-sizeDepthQuotes (size_sol,tokens_out,avg_price_sol,price_impact_pct), andto_move_price(DepthToMovePrice— SOL to move the price 1/5/10%). Exact for constant-product AMMs and correct for pump.fun/bonk curves; concentrated pools (CLMM/Orca/DLMM), Meteora-DBC curves, and unclassified pools come back inunsupported_poolswith a machine-readablereasoninstead of a wrong number. Pick buy sizes withDepthParams::from_sizes(&[0.5, 1.0, 5.0, 10.0])(?sizes=CSV, max 8, each ≤10000; default0.5,1,5,10).client.token.risk(mint)also gains a top-leveldev: Option<RiskDev>block — deployer wallet activity:buy_sol/buy_tokens/buy_supply_pctat create,bought_tokens_after(catches the same-second-separate-tx dev buy),sold_tokens/sold_sol,first_sell_at/last_sell_at, live on-chainholdings_tokens/holdings_supply_pct/wallet_empty, and a coverage-gatedtransferred_outflag (every fieldNonewhen unobservable — never a guess). New types:DepthParams,TokenDepthResponse,DepthPool,DepthUnsupportedPool,DepthQuote,DepthToMovePrice,RiskDev.New in 0.22.0 — Batch wallet classification + token trade tape +
bot_confidencetype fix.client.wallet.batch_classify(wallets)(POST /wallet/batch/classify, PRO/ULTRA) returns reputation flags for 1–100 wallets in one call (counts as 1 request): eachWalletClassificationcarriesis_sniper/is_bundler/is_dumper/is_kol(+kol_name),bot_confidence, and adump_clustercohort block. Flags are pump.fun-pipeline scoped —false= not observed, NOT verified clean;is_bundleris lifetime,is_dumperis a rolling 42-day window.client.token.trades(mint, ¶ms)(GET /tokens/{mint}/trades, PRO/ULTRA) is the mint-scoped trade tape — cursor-paginated raw trades (default FULL history; capture starts 2026-04-12) with a machine-readablecoveragehonesty block.WalletFlagsgains the same reputation flags +dump_cluster, and — breaking type fix —WalletFlags.bot_confidenceis nowOption<String>(text enum"none"/"low"/"medium"/"high"); it was mistypedOption<f64>and a server bug made it alwaysnullbefore, so no working code could have depended on the old type.RiskInputsgainssniper_footprintandSniperDeploygainsfootprint— the slot-window launch-snipe rollup (SniperFootprint:buys,buyers,sol,supply_pct,sniper_wallet_buys,data_available,as_of;None= not observable, not zero). New types:WalletBatchRequest,WalletClassification,WalletBatchClassifyResponse,WalletDumpCluster,TokenTradesParams,TokenTrade,TokenTradesFilters,TokenTradesCoverage,TokenTradesResponse,SniperFootprint.New in 0.21.0 — Verified wallet holdings.
client.wallet.holdings(address, ¶ms)(GET /wallet/{address}/holdings, ULTRA only) reads the wallet's actual SPL + Token-2022 token accounts and SOL balance directly from chain, enriches each with our price / MC / name / symbol data, and computestransfer_delta(on-chain amount minus trade-derived net position) to expose non-swap flows — airdrops, insider funding, wallet-hopping. Distinct fromclient.wallet.positions()(trade-derived FIFO): holdings is "what they actually hold right now".WalletHoldingsResponsecarriesaddress,sol_balance, aVec<Holding>(each withmint,symbol,name,amount,amount_raw,decimals,token_program="spl"/"token2022",price_usd,value_usd,market_cap_usd,is_bonded,trade_derived_amount,transfer_delta), aWalletHoldingsSummary(token_accounts,non_zero,returned,priced,total_value_usd,truncated),verified_at,trade_window_days,cache_hit, andttl_seconds.WalletHoldingsParamsfilters bylimit(1–500, default 200) andmin_value_usd(≥0, default 0). New types:WalletHoldingsParams,WalletHoldingsResponse,WalletHoldingsSummary,Holding.
New in 0.20.1 — Token pools + deployer history.
client.token.pools(mint)(GET /tokens/{mint}/pools) returns every liquidity pool for a token across all tracked DEXes plus an aggregatePoolsSummary(pool_count,active_pool_count,dex_count,dexes,total_liquidity_usd,primary_pool,primary_dex,top_pool_share_pct). EachPoolcarriespool_address,dex,quote_mint,liquidity_usd,last_price_sol,last_swap_at,amm_id, andis_active.client.deployer.history(wallet, limit)(GET /deployer-hunter/{wallet}/history,limit1..=365) returns daily performance snapshots: eachDeployerSnapshothasdate,tier,is_tracked,total_deployed,total_bonded,bonding_rate,recent_bond_rate,avg_peak_mc,best_token_peak_mc;is_deployerisfalsewhen the wallet has never deployed. New types:TokenPoolsResponse,Pool,PoolsSummary,DeployerHistoryParams,DeployerHistoryResponse,DeployerSnapshot.
New in 0.20.0 — Bundle intelligence.
client.token.bundle(mint)(GET /tokens/{mint}/bundle, PRO/ULTRA) detects wallets that bought a token in the same atomic transaction or the same slot — bundlers and coordinated snipers — and how much of supply they still hold. Returns aBundleSummary(wallet_count,bundle_kind=atomic_tx/same_slot/none,held_ratio,held_pct_of_supply,fully_exited,buy_volume,tokens_held) plus a per-walletVec<BundleWallet>breakdown (rank,wallet,held_ratio,has_sold,atomic,tokens_held). ULTRA populates wallet identity fields (is_kol,kol_name,win_rate,bot_confidence); lower tiers may return an emptywalletsarray. New types:TokenBundle,BundleSummary,BundleWallet,BundleKind.
New in 0.18.0 — Almost-bonded tokens + trending sorts.
client.token.almost_bonded(¶ms)(GET /tokens/almost-bonded, PRO/ULTRA) returns pre-bond pump.fun tokens near graduation, ranked by velocity: eachAlmostBondedTokencarriesprogress_pct,velocity_pct_per_min,eta_minutes, astalledflag,real_sol_reserves,market_cap_usd,liquidity_usd,authorities_revoked,deployer_tier, andage_minutes.AlmostBondedParamsfilters bymin_progress/max_progress,min_velocity_pct_per_min,max_age_minutes,deployer_tier,authority_revoked, andmin_liq, and picks theAlmostBondedSortorder (VelocityDescdefault,ProgressDesc,EtaAsc). New types:AlmostBondedParams,AlmostBondedSort,AlmostBondedToken,AlmostBondedResponse.client.token.list(¶ms)also accepts four new momentumsortvalues:"mc_change_5m_desc","mc_change_1h_desc","volume_1h_desc", and"trending".
New in 0.17.0 — Token flow + deployer SOL balance.
client.token.token_flow(mint, ¶ms)(GET /tokens/{mint}/flow, PRO+) returns aggregated buy/sell flow for a token over a rolling window:unique_wallets/unique_buyers/unique_sellers,buy_count/sell_count/total_trades,buy_sol/sell_sol/net_sol(buy − sell), andtrades_per_wallet, plus the windowfromtimestamp.TokenFlowParams { window: Some("24h".into()) }selects the window ("1h"default or"24h"). New types:TokenFlowParams,TokenFlowResponse.DeployerAlertalso gainsdeployer_sol_balance: Option<f64>— the deployer wallet's SOL balance at alert time.
New in 0.16.0 — Signal Scorecard. New
client.signalsnamespace.client.signals.catalog()returns the discovery index — every available signal with itsmethodologyand aperformance_endpoint(SignalsCatalog,SignalCatalogEntry).client.signals.performance(name, ¶ms)returns a named signal's out-of-sample, machine-readable reliability — per-buckethit_ratevsbase_rate,lift, andsample_n, plus the test window andmethodology(SignalPerformance,SignalBucket). PassSignalPerformanceParams { history: Some(true) }to append the per-day drift series (SignalHistoryEntry). Valid signal names:dump_cluster_count,runner_rate,recycled_early_buyer_count,coordination_count. Open to any authenticated tier. New types:SignalPerformanceParams,SignalPerformance,SignalBucket,SignalHistoryEntry,SignalsCatalog,SignalCatalogEntry.
New in 0.15.0 — OHLC candles.
client.token.candles(mint, ¶ms)returns 1-minute OHLC candles aggregated from the trade firehose (PRO/ULTRA): per-baropen/high/low/close,volume_usd,trades, andmarket_cap_usd. ULTRA unlocks buy/sell volume split (buy_volume_usd,sell_volume_usd,net_volume_usd), open/close liquidity, MC high/low, buy/sell counts, and MEV volume per candle.CandlesParamsselectstf,limit, and an optionalfrom/towindow. New types:CandlesParams,Candle,CandlesResponse.
New in 0.14.0 — Token risk score.
client.token.risk(mint)returns a transparent 0–100 rug-risk / safety score (PRO/ULTRA, higher = riskier): an overallrisk_score+RiskBand(safe/caution/danger), a per-factorVec<RiskFactor>breakdown (each withstatus,points, and a human-readabledetail), and the rawRiskInputsevery factor was derived from (mint/freeze authority revocation, liquidity, transfer fee, launch cohort, deployer reputation, blacklist, …). New types:TokenRisk,RiskFactor,RiskInputs,RiskBand,RiskFactorStatus.
New in 0.13.0 — Launch cohort, liquidity/MC ratio, deployer-tier filter, and KOL leaderboard timing.
TokenResponseBodygainsliquidity_to_mc_ratio,launch_cohort_sol, andlaunch_cohort_size.TokensListParamsgainsmin_liq_mc_ratio,max_liq_mc_ratio, anddeployer_tierfilters.TokenSummary(tokens list items) gainsliquidity_to_mc_ratioanddeployer_tier.KolLeaderboardEntrygainsmedian_hold_minutes_30dandpercentile_early_entry_30d.
New in 0.12.1 — Deployer runner-rate fields.
SniperDeploy,DeployerSummary,DeployerProfile, andDeployerLeaderboardEntrynow carryrunner_rate(fraction of the deployer's labeled tokens that ran — peak ≥60min after deploy — vs dumped) andlabeled_tokens(confidence denominator; gate on ≥3).
New in 0.12.0 (2026-06-07) — Graduation events + dump-cluster detection.
GraduationEvent— typed payload for thetoken:graduationsWebSocket channel: every pump.fun bond in real time (tracked deployer or not) with deployer tier, time-to-bond, and MC at bond.AlphaBuyerQualityBreakdownaddsdump_cluster_count(out-of-sample: 3+ such wallets in the first-20 → 94% dump vs 61% base) andrecycled_early_buyer_count. DEX firehose: replay buffer deepened to ~5 min; mint-scoped subs get in-banddex:graduationsframes.
New in 0.10.0 (2026-05-25) — Price alerts, scout leaderboard, KOL consensus, peak history, coordination history, wallet derived stats, trajectory snapshots.
client.price_alerts— full CRUD for MC-drop / recovery alert rules (PRO/ULTRA).client.kol.scout_leaderboard()— ranked scouts by swarm attraction rate.client.token.kol_consensus(mint)— per-token KOL buyer/seller breakdown.client.token.peak_history(mint)— ATH, decline from peak, MC snapshots post-bond.client.kol.coordination_history()— past coordination fires.client.deployer.trajectory(wallet, params)now acceptsinclude: Some("daily_snapshots")for 90d snapshots.WalletStatsResponse.derived— win rate, ROI, best/worst trade, biggest miss, AI verdict.
Get an API key
- Visit https://madeonsol.com/pricing
- Sign in with email or Solana wallet
- Copy your
msk_…key (free tier is unlocked instantly — 200 req/day, 10/min)
Paid tiers unlock higher rate limits, sub-hour windows, WebSocket streaming, webhooks, and the all-DEX firehose:
| Tier | Price | Daily req | KOL trending sub-hour | Stream | Webhooks | DEX firehose |
|---|---|---|---|---|---|---|
| Free | $0 | 200 | — | — | — | — |
| PRO | €43/mo ≈ $49 | 10,000 | ✅ | ✅ | 3 | — |
| ULTRA | €131/mo ≈ $149 | 100,000 | ✅ | ✅ | 10 | ✅ |
| BUSINESS | €400/mo ≈ $449 | 500,000 | ✅ | ✅ | 30 | ✅ |
Annual: PRO €430/yr, ULTRA €1,310/yr, BUSINESS €4,000/yr (2 months free). EUR is the canonical price; USD shown for reference.
Install
[]
= "0.27"
= { = "1", = ["macros", "rt-multi-thread"] }
Requires Rust 1.75+. Uses reqwest with rustls-tls (no OpenSSL dependency).
Quick start
use ;
async
Run the bundled examples:
Namespaces
The MadeOnSol client exposes namespaced sub-clients:
| Namespace | Purpose |
|---|---|
client.kol |
KOL feed, leaderboard, coordination, PnL, trending tokens, alerts, compare, first_touches, scout_leaderboard, coordination_history |
client.deployer |
Pump.fun deployer leaderboard, alerts, trajectory (+ daily snapshots), history, bonded tokens |
client.alpha |
Alpha-wallet leaderboard, profiles, cap tables, buyer quality |
client.token |
Per-mint snapshot, batch lookup, buyer quality, kol_consensus, peak_history, risk (+ dev block, 0.23), batch_risk, bundle, pools, surges (new 0.27 — token surges & revivals: momentum fires with tape / KOL / early-buyer / deployer context, risk_flags, +1 h outcome + hit-rate stats), locks / locks_feed / unlocks (new 0.26 — token locks & vesting, upcoming unlocks; LP locks not included), fee_shares / fee_claims (new 0.26 — pump.fun creator-fee sharing + fee-claim feed), holders (new 0.25 — live holder census + concentration), depth (new 0.23 — per-pool price impact), candles, token_flow, trades (new 0.22 — mint-scoped trade tape), almost_bonded, directory list |
client.wallet_tracker |
Track arbitrary Solana wallets — watchlist CRUD, swap/transfer history |
client.wallet |
Universal wallet endpoints — stats + cross-product flags + derived analytics, FIFO PnL, open positions, paginated trades, batch_classify (new 0.22 — bulk reputation flags, 1–100 wallets) (PRO+), verified on-chain holdings (ULTRA) |
client.coordination_alerts |
Push alerts on coordinated buying (PRO/ULTRA) |
client.first_touch_subscriptions |
Push alerts on first-KOL-touch events (ULTRA) |
client.price_alerts (new 0.10) |
MC-drop / recovery price alert rules CRUD + event history (PRO/ULTRA) |
client.signals (new 0.16) |
Signal Scorecard — out-of-sample, machine-readable signal reliability (performance) + discovery catalog |
client.sniper (new 0.11) |
Deshred pre-confirm pump.fun deploy feed (~500ms head start) + custom deployer watchlist (PRO/ULTRA) |
client.tools |
Solana tool directory search |
client.stream |
Issue WebSocket streaming tokens (non-expiring since 2026-08-27), rotate them (new 0.26.1), list / kill live sessions |
client.webhooks |
Webhook CRUD (PRO/ULTRA) |
Full reference: https://docs.rs/madeonsol · Interactive API docs: https://madeonsol.com/api-docs.
Use cases
- Copy-trading bot — stream KOL buys via
client.kol.feed()and mirror trades - DEX trade sniping — subscribe to the all-DEX stream filtered by token / wallet
- Deployer sniper — monitor
client.deployer.alerts()for elite-tier launches - Coordination detector — flag tokens with
client.kol.coordination()or push alerts - Scout signal — track first-KOL-touch events filtered to S/A-tier scouts via
client.kol.first_touches()(backtested: ~50% swarm rate vs 14% baseline) - Analytics dashboard — combine leaderboard, PnL, and tool data
- Telegram/Discord bot — pipe alerts via webhooks into chat
- Portfolio tracker — use
client.kol.wallet()to follow specific KOL positions
Error handling
All methods return Result<T, madeonsol::Error>. The Error::Api variant exposes
HTTP status, server message, and the raw JSON body:
use ;
# async
Error::MissingApiKey is returned by MadeOnSol::new if the key is empty or
doesn't start with msk_ — the error message and a stderr hint both link to
https://madeonsol.com/pricing.
First-touch signal (new in 0.4)
Every "first KOL buy on a token mint" event — when a tracked KOL is the first of the cohort to touch a token. Filterable by scout tier (S/A/B/C from mv_kol_scout_score), KOL winrate, token age, mint suffix.
Backtest: S-tier scouts attract ≥3 follow-on KOLs within 4h ~50% of the time vs ~14% baseline (38d / 491k buys / 72,549 events). Public leaderboard at https://madeonsol.com/kol/scouts.
use ;
let res = client
.kol
.first_touches
.await?;
for e in res.events
Webhook subscriptions (Ultra, up to 10 active per user) — push delivery, HMAC-SHA256 signed:
use ;
let res = client
.first_touch_subscriptions
.create
.await?;
// store res.webhook_secret — shown ONCE
Don't poll — push. Median lead time before the second KOL is 12 seconds. WebSocket channel:
kol:first_touches(PRO+).
Universal wallet endpoints (new in 0.9)
Per-wallet profile data for any Solana wallet — not just curated KOLs. FIFO cost-basis PnL over the last 90 days, cached server-side with dynamic TTL. Cache hits don't count against your daily quota. PRO+.
# async
Cost-basis honesty. Observable only inside the 90-day window. Overflow sells (no matching buy in window) are silently discarded rather than fabricated. notes.cost_basis_observable_from makes the cutoff visible.
Deshred sniper alerts (new in 0.11)
The fastest path to a new pump.fun launch. Deploys are reconstructed from shred-level (deshred) data and surface ~500ms before the chain confirms them. PRO sees elite + good deployers; ULTRA sees every tier and can keep a custom deployer watchlist. For live push use the sniper:deploy webhook, the sniper:deploys WebSocket channel, or /alert sniper in Telegram — these methods are for catch-up, backtesting, and watchlist management.
# async
Price alerts (new in 0.10)
Get notified when a token's market cap drops below a threshold (and optionally on recovery). PRO: 5 rules, ULTRA: 25 rules. Delivered via WebSocket channel price:alerts and/or HMAC-signed webhook.
# async
New in 0.10: scout leaderboard, KOL consensus, peak history
# async
New in 0.16: Signal Scorecard
Out-of-sample, machine-readable reliability for each enrichment signal, so bots can weight them programmatically instead of asking. Open to any authenticated tier.
# async
Batch risk scoring (new in 0.19)
Score up to 50 mints for rug-risk in a single round-trip (PRO/ULTRA) — same
transparent per-factor breakdown as client.token.risk(mint). Untracked mints
come back as error entries instead of failing the whole batch, so check
is_error() (or match on error) before reading the score.
# async
Token surges & revivals (new in 0.27)
client.token.surges(&TokenSurgesParams) (GET /tokens/surges, PRO+) —
token momentum fires, newest first. Two SurgeKinds:
Surge— a token < 30 min old whose market cap runs hard vs its launch MC.SurgeTier::Early(≤ 10 min, ≥ $12k, ≥ 3× launch MC),Strong(≤ 30 min, ≥ $30k, ≥ 6× launch and ≥ 2× the lowest sample of the last 3 min — it is climbing now),Breakout(≤ 2 min, ≥ $45k, ≥ 8×). Each tier fires at most once per mint; tiers are independent. A tier must be sustained — floor + multiple hold on the current tick and on a sample ≥ 10 s older, and nothing fires before 20 s of age: a one-tick mark (same-slot bundle, routed dust) is a spike, not a surge. When the engine first saw the token late (baseline_source: Late) the launch multiple is not applied — USD floor + velocity only.Revival— a token with no 1-minute trade candle for ≥ 24 h that starts trading again, confirmed only by the tape (≥ 5 buys, ≥ $500 buy volume, MC ≥ 1.5× the pre-dormancy close — or ≥ 20 buys / ≥ $5k regardless), never by the price mark: a single dust buy into an empty pool marks MC up 300 % and is not a revival. One fire per dormancy episode (24 h re-fire guard).
Hard gates on both kinds (not flags): liquidity ≥ $1.5k and ≥ 2 % of MC when known, MC ≤ $100B, and the MC gained must be paid for — buy volume on the tape ≥ 3 % × (MC − launch / pre-dormancy MC); a price mark in a spoof pool moves MC on ~$0 of volume.
Every TokenSurgeEvent carries SurgeTape (buys / sells / volume since
birth or revival; source = Candles or WalletTrades, available: false
with Nones while no tape covers the window yet; unique_buyers /
trades_per_wallet only when the mint is in wallet-trade coverage —
wallet_data_available: false otherwise, never an inferred zero),
SurgeKol, SurgeEarlyBuyers (first-20 cohort: bundled, cohort SOL, sold,
sniper wallets), SurgeDeployer and risk_flags: Vec<SurgeRiskFlag> — the
honest half. Rows ≥ 65 min old carry SurgeOutcome (mc_usd_1h_after,
peak_mc_usd_1h_after, low_mc_usd_1h_after, mc_1h_multiple,
peak_1h_multiple, priced_after_1h — false = no candle in the hour, not
zero); stats: Some(true) adds SurgeStats — per-(kind, tier) hit-rates
over days (up_1h_pct, median_peak_multiple, doubled_1h_pct),
out-of-sample by construction. The live thresholds are echoed in
definitions (untyped serde_json::Value, read from the engine so they
cannot drift). Poll forward with pagination.next_since → since, or
subscribe to WS token:surges (events token:surge / token:revival,
payload TokenSurgeStreamEvent — the same object with outcome: None;
SurgeSubscribeFilters serialises the server-side filter object).
Nearly every scalar is an Option with #[serde(default)] — None means
unknown, never zero. tier is None on revivals; dormant_hours /
prev_mc_usd / mc_vs_prev_multiple are None on surges; baseline_* /
mc_multiple / mc_change_3m_pct are None on revivals. tier together
with kind: Revival is a 400; an unknown name in exclude_flags is a 400
with known_flags[]. Keyed API only — BASIC gets HTTP 403.
# async
Params: kind, tier (surge only), mint, since / before (ISO
cursors), min_mc_usd / max_mc_usd, min_buys, launchpad,
deployer_tier (SurgeDeployerTier), exclude_flags (comma list —
build it with SurgeRiskFlag::as_str), only_clean, stats, days
(1–30, default 7), limit (1–200, default 50).
New types: TokenSurgesParams, TokenSurgesResponse, TokenSurgeEvent,
TokenSurgeStreamEvent, SurgeTape, SurgeKol, SurgeEarlyBuyers,
SurgeDeployer, SurgeOutcome, SurgeStats, SurgeStatsRow,
SurgeFilters, SurgeSubscribeFilters, SurgeKind, SurgeTier,
SurgeBirthSource, SurgeBaselineSource, SurgeTapeSource,
SurgeDeployerTier, SurgeRiskFlag.
Token locks, unlocks & pump.fun fee sharing (new in 0.26)
Five keyed (PRO+) methods on client.token. All base-unit amounts (*_raw)
are Strings — parse them yourself; every ui / usd / pct companion is an
Option that is None when decimals or price are unknown. None of these
are on the x402 rail; BASIC gets HTTP 403.
client.token.locks(mint, &TokenLocksParams)(GET /tokens/{mint}/locks) — every on-chain Streamflow / Jupiter Lock / Bonfida vesting contract on the mint (TokenLock: schedule, terms, live-derivedlocked_raw/unlocked/withdrawn/claimable/LockStatus/next_unlock) + aTokenLocksSummary(exactlock_count,distinct_lockers, locked / deposited totals,unlocking_7d_*/unlocking_30d_*,active_cancelable_by_sender). LP locks are not included.client.token.locks_feed(&TokenLocksFeedParams)(GET /tokens/locks) — NEW contracts across all mints, newest first; poll withpagination.next_since→since, or subscribe to WStoken:locks(TokenLockEvent).client.token.unlocks(&TokenUnlocksParams)(GET /tokens/unlocks) — upcoming unlock events (UnlockEventKind) insideUnlockWindow(1h…90d, default7d), one per active contract withamount_*(next event) andwindow_amount_*(whole window), sorted byUnlocksSort.client.token.fee_shares(mint)(GET /tokens/{mint}/fee-shares) — the pump.funSharingConfig(FeeSharingConfig+FeeShareholder,is_default= 100% to the creator,is_social_pda+FeeShareSocial— platform 2 = X),FeeDistributionsrollup,history,recent_distributions. Event history starts 2026-08-17.client.token.fee_claims(&TokenFeeClaimsParams)(GET /tokens/fee-claims) — the fee-event feed (FeeClaimEvent/FeeEventType);CreatorClaimonly when requested viaevent_type; live on WStoken:fee_claims(TokenFeeClaimEvent).
# async
New types: TokenLocksResponse, TokenLocksSummary, TokenLock,
LockNextUnlock, LockTokenInfo, TokenLocksParams, TokenLocksFeedParams,
TokenLocksFeedResponse, TimeCursorPagination, StreamPointer,
TokenUnlocksParams, TokenUnlocksResponse, TokenUnlock, UnlockLockRef,
UnlockWindowInfo, TokenUnlocksPagination, TokenLockEvent, LockProgram,
LockKind, LockStatus, UnlockEventKind, UnlockWindow, UnlocksSort,
TokenFeeSharesResponse, FeeSharingConfig, FeeShareholder, FeeShareSocial,
FeeDistributions, FeeRecentDistribution, FeeQuote, FeeConfigSource,
TokenFeeClaimsParams, TokenFeeClaimsResponse, FeeClaimEvent,
FeeClaimSocial, FeeClaimShareholder, FeeClaimPayout, FeeEventType,
TokenFeeClaimEvent.
Live holders + concentration (new)
client.token.holders(mint) (GET /tokens/{mint}/holders, PRO+) — a full
holder census read from the ledger at confirmed: every token account of the
mint (owner + balance), merged per owner. This is who holds now;
client.alpha.cap_table is who bought first.
concentration.holder_countis exact (distinct non-zero owners minus excluded pools/curves/burns, atslot) andNoneonly when the provider refused the census for a mega-cap mint — thensource.methodisHoldersMethod::GetTokenLargestAccounts(top-20 view) andsource.census_fallback_reasonis set. It is never estimated from trades.amount_rawon everyTokenHolder/TokenHoldersExcludedis a raw u64String— never a float; parse it (u64/u128) yourself.amountis the UI-scaled conveniencef64.- Pools, bonding curves, burns and unattributed program accounts are
excluded from the circulating denominator and listed in
excluded, each named where possible:HolderExcludedReason::Pool(+dex,pool_address),BondingCurve(pump.fun / LaunchLab),Burn, elseProgramAccount. The #1 raw account of a fresh memecoin is its own bonding curve.concentration.pool_pct/burned_pct/program_pctsplit them (over total supply). - Disclosure is tier-gated: PRO ranks 1–10, ULTRA 1–50, BUSINESS
1–100 (
disclosedis your cap);top1/top10/top20/top50/top100_share, the cohort*_pctvalues andholder_countare computed over the full set and identical on every tier. All shares are 0–100. - Each holder carries
labels: Vec<HolderLabel>from MadeOnSol wallet intelligence (Deployer/Kol/EarlyBuyer/Buyer/Bundle/Bot/DumpCluster) pluskol_name,early_buyer_rank,bot_confidence,historical_win_rate. Empty labels = unknown to us, not verified clean. - Latency: fresh pump.fun mints <1 s; 200k–550k-account tokens 6–11 s. While
the upstream scan is still running the API answers 503
error_kind: "holder_scan_in_progress"withretry_after_seconds: 20— the scan keeps going and is cached, so the retry is instant.holder_rpc_unavailable(503,retry_after_seconds: 15) is a fail-closed RPC outage. Both arrive asError::Api { status: 503, body, .. }— readerror_kind/retry_after_secondsfrombody. Unknown mint: 404error_kind: "not_a_mint".
# async
New types: TokenHoldersResponse, TokenHolder, TokenHoldersExcluded,
TokenHoldersConcentration, TokenHoldersDeployer, TokenHoldersSource,
HolderLabel, HolderExcludedReason, HoldersMethod.
Pool depth / price impact (new in 0.23)
How much SOL moves the price 1/5/10%, and what slippage each buy size eats —
per pool (PRO+). Exact for constant-product AMMs (streamed reserves, zero-RPC),
correct for pump.fun/bonk curves via live virtual reserves. Pools we can't
price honestly (CLMM/Orca/DLMM, Meteora-DBC) come back in unsupported_pools
with a reason instead of a wrong number.
# async
Batch wallet classification + token trade tape (new in 0.22)
Screen up to 100 wallets for sniper / bundler / dumper / KOL reputation in one
request, and replay any token's raw trade history with cursor pagination
(PRO/ULTRA). Reputation flags are pump.fun-pipeline scoped — false means
"not observed", not "verified clean".
# async
Bundle intelligence (new in 0.20)
Detect wallets that bought a token in the same atomic transaction or same slot — bundlers and coordinated snipers — how much of supply they still hold, and whether the cohort has fully exited (PRO/ULTRA). ULTRA additionally labels each wallet with KOL identity and bot-confidence.
# async
WebSocket streams (PRO/ULTRA)
This crate does not ship a WebSocket client — client.stream.get_token()
returns the URL + token, and you connect with any WS library
(tokio-tungstenite recommended):
# async
Stream tokens do not expire (since 2026-08-27). get_token() returns the
same token on every call — call it on every reconnect and never schedule a
refresh: expires_at / next_refresh_at are always None (kept for wire
compatibility only). The token stops working only when your subscription
lapses, or when you replace it yourself with client.stream.rotate_token()
(POST /stream/token with {"rotate": true}) — the old value then keeps
working for 60 s so live sockets can reconnect. A WebSocket close code 4001
means "call get_token() again and reconnect", never "the token timed out".
# async
Channels: kol:trades, kol:coordination, kol:first_touches, deployer:alerts, wallet_tracker:events, copytrade:signals, price_alert:events, sniper:deploys, token:graduations (GraduationEvent), token:prices (mint-scoped price / MC ticks), token:locks (new 0.26 — event token:lock, TokenLockEvent: every NEW lock / vesting contract; LP locks not included), token:fee_claims (new 0.26 — event token:fee_claim, TokenFeeClaimEvent: every pump.fun fee event; history starts 2026-08-17), token:surges (new 0.27 — events token:surge / token:revival, TokenSurgeStreamEvent: momentum fires with tape / KOL / early-buyer / deployer context and risk_flags; server-side filters kinds, tiers, launchpads, exclude_flags, min_mc_usd / max_mc_usd, deployer_tier — SurgeSubscribeFilters; the +1 h outcome is REST-only). All PRO+.
The DEX firehose URL (token.dex_ws_url) is only present for ULTRA subscribers.
See https://madeonsol.com/api-docs for the full subscribe/unsubscribe protocol.
Session management (new in 0.19)
List every live socket on your account and force-disconnect a stale one to free its connection slot (PRO/ULTRA):
# async
Also available
| Platform | Package |
|---|---|
| TypeScript / Node | madeonsol on npm |
| Python (LangChain, CrewAI) | madeonsol-x402 on PyPI |
| MCP Server (Claude, Cursor) | mcp-server-madeonsol · Smithery · Glama |
| ElizaOS | @madeonsol/plugin-madeonsol |
| Solana Agent Kit | solana-agent-kit-plugin-madeonsol |
Links
- API docs: https://madeonsol.com/api-docs (interactive try-it-now)
- OpenAPI 3.1 spec: https://madeonsol.com/api/v1/openapi.json
- Postman collection: https://madeonsol.com/api/v1/postman.json
- Pricing & free key: https://madeonsol.com/pricing
- Issues: https://github.com/madeonsol/madeonsol-rs/issues
License
MIT © MadeOnSol