madeonsol
v2.9.0
Published
Official SDK for the MadeOnSol Solana API — KOL wallet tracking, Pump.fun deployer intelligence, and tool directory
Maintainers
Readme
madeonsol
⭐ Star on GitHub if you find this useful · 📂 Examples · 📚 API docs
Official TypeScript/JavaScript SDK for the MadeOnSol Solana API — zero dependencies, fully typed, works in Node.js ≥ 18 and edge runtimes.
Real-time Solana trading intelligence: track 1,000+ KOL wallets with <3s latency, score 6,700+ Pump.fun deployers by reputation, detect multi-KOL coordination signals, and stream every DEX trade across 9+ programs. Free tier: 200 requests/day at madeonsol.com/pricing — no credit card required.
New in 2.6.1 (2026-05-13) — Velocity types fixed. Velocity fields are now correctly typed as
mc_change_pct,volume_usd,mev_volume_pct— each its own object keyed by5m/15m/1h/2h/4h— to match the actual API response. The 2.6.0 shape (velocity[window].mc_change_pct) was wrong; clients reading it would getundefined. Patch is type-only — no runtime breaking changes.New in 2.6.0 (2026-05-12) — Token directory + self-inspection.
client.token.list({ min_liq, min_volume_1h_usd, max_mev_share_pct, mc_change_1h_min_pct, sort, ... })— browse and filter every active mint, with defaultmin_liq=2000to skip phantom-MC dust.client.me()— read your tier, daily/burst quota state, and per-feature usage in one call (no header parsing). Velocity / MEV-share fields added to everyTokenResponseBody:mc_change_pct,volume_usd,mev_volume_pct(each keyed by5m/15m/1h/2h/4h) plushistory_age_secondson the parent./token/{mint}400s now shipcode,reason,received_length,example, anddocsURL — stop guessing why a mint failed. Deprecatedavg_entry_mc_usd/entry_mc_samplesremoved from leaderboard types. All other 2.5.x APIs unchanged.
Build Solana trading bots, analytics dashboards, KOL copy-trading tools, deployer sniper bots, and ecosystem browsers.
Quick start (10 seconds)
npm install madeonsolimport { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({ apiKey: "msk_..." }); // free tier at madeonsol.com/pricing
const { trades } = await client.kol.feed({ limit: 5, action: "buy" });| Feature | Description | |---|---| | KOL Tracker | Real-time trade feed, PnL leaderboard with five time windows (today, 7d, 30d, 90d, 180d), coordination detection, per-wallet profiles, and deep PnL analytics for 1,000+ tracked KOL wallets. 180 days of trade history retained. | | Alpha Wallet Intel | Leaderboard of 47,000+ scored early-buyer wallets, full wallet profiles, linked-wallet clustering, token cap-table enrichment, and 0–100 buyer quality scores. | | Wallet Tracker | Monitor any Solana wallet for swaps and transfers. Track up to 10/50/100 wallets (Free/Pro/Ultra). Full wallets, counterparties, and tx_signatures on every tier. 120-day event retention. WS events on ULTRA. | | Deployer Hunter | Pump.fun deployer scoring, tier leaderboard, deploy alerts, and bonding intelligence | | DEX Trade Stream | Real-time WebSocket stream of ALL Solana DEX trades — filter by token, wallet, program, or trade size (Ultra) | | Webhooks | Push notifications for KOL trades, coordination signals, deployer alerts, and wallet tracker events (Pro/Ultra) | | Tool Directory | Search 950+ Solana tools and dApps indexed on MadeOnSol |
Links: Full docs · Website · API docs
Authentication
Get a free API key at madeonsol.com/pricing. Keys start with msk_.
Install
npm install madeonsol
# or
yarn add madeonsol
# or
pnpm add madeonsolRequires Node.js ≥ 18 (uses native fetch). Works out of the box in Cloudflare Workers, Vercel Edge, and Bun.
Quick start
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({ apiKey: "msk_your_api_key_here" });
// Latest KOL buy trades
const { trades } = await client.kol.feed({ limit: 10, action: "buy" });
console.log(trades[0].kol_name, "bought", trades[0].token_symbol);
// Elite deployer leaderboard
const { deployers } = await client.deployer.leaderboard({ tier: "elite" });
// Recent deploy alerts
const { alerts } = await client.deployer.alerts({ limit: 5 });
// Search Solana tools
const { tools } = await client.tools.search({ q: "trading", limit: 10 });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 or wallet
- Deployer sniper — monitor
client.deployer.alerts()for elite-tier launches - Coordination detector — flag tokens with
client.kol.coordination({ min_kols: 3 }) - Scout signal — track first-KOL-touch events filtered to S/A-tier scouts via
client.kol.firstTouches({ preset: "scout" }) - 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
API Reference
KOL Tracker — client.kol
client.kol.feed(params?)
Live feed of trades made by tracked KOL wallets.
const { trades, count } = await client.kol.feed({
limit: 50, // 1–100, default 50
action: "buy", // "buy" | "sell"
kol: "7xKX...", // filter by specific wallet
});Returns: KolFeedResponse — { trades: KolTrade[], count: number }
Each KolTrade includes market_cap_usd_at_trade and price_usd_at_trade — the token's MC and price at the exact moment the swap fired, sourced from our in-memory price tracker (real-time, faster than Dexscreener spot). Use these to surface "KOL bought $X SOL of token at $Y MC" without a second lookup.
client.kol.leaderboard(params?)
KOL PnL leaderboard ranked by realized profit.
const { leaderboard, period } = await client.kol.leaderboard({
period: "7d", // "today" | "7d" | "30d" | "90d" | "180d", default "7d"
});180-day retention — KOL trade data is retained for 180 days (extended from 31 on 2026-04-07). The 90d and 180d windows fill up over time as the trade table accumulates.
Returns: KolLeaderboardResponse
client.kol.wallet(wallet, params?)
Full profile for a single KOL wallet, including trade history and optional per-token PnL breakdown.
const profile = await client.kol.wallet("7xKX...", {
include: "pnl_by_token",
});Returns: KolWalletProfile
client.kol.coordination(params?)
Detect tokens where multiple KOLs are buying simultaneously — a strong signal of coordinated pumps. v1.1 adds peak-density windows, exit tracking, and a composite 0–100 coordination score.
const { coordination, score_version, window_minutes } = await client.kol.coordination({
period: "24h", // "1h" | "6h" | "24h" | "7d", default "24h"
min_kols: 3, // 2–50, default 3
limit: 20, // 1–50, default 20
window_minutes: 15, // v1.1 — peak-density window in minutes (1–60)
min_score: 60, // v1.1 — filter by composite score (0–100)
include_majors: false, // v1.1 — include WIF/BONK/POPCAT
});
for (const c of coordination) {
console.log(c.token_symbol, "score", c.coordination_score, "peak", c.peak_kols, "exited", c.exited_count);
// c.kols[]: { name, wallet, buy_sol, sell_sol, exited }
}Returns: KolCoordinationResponse — { coordination: CoordinatedToken[], score_version, window_minutes }
client.coordinationAlerts.* (v1.1)
Create real-time push alerts that fire the moment a new coordination cluster forms. Alerts are evaluated per-trade by the signal-evaluator service (sub-second latency), delivered via WebSocket channel kol:coordination and/or HMAC-signed webhook. PRO: 5 rules, ULTRA: 20 rules.
// Create a rule: ≥5 KOLs, 10-min window, score ≥70, webhook delivery
const { rule, webhook_secret } = await client.coordinationAlerts.create({
name: "strong-clusters",
min_kols: 5,
window_minutes: 10,
min_score: 70,
include_majors: false,
cooldown_min: 30, // don't re-fire same token within 30 min
score_jump_break: 15, // UNLESS score jumps by 15+ (catches conviction surges)
delivery_mode: "webhook", // "websocket" | "webhook" | "both"
webhook_url: "https://example.com/coord-hook",
});
// SAVE webhook_secret — used for HMAC-SHA256 signature verification.
await client.coordinationAlerts.list();
await client.coordinationAlerts.get(rule.id);
await client.coordinationAlerts.update(rule.id, { min_score: 80, is_active: false });
await client.coordinationAlerts.delete(rule.id);Webhook signatures: header X-MadeOnSol-Signature = sha256(timestamp + "." + body) with webhook_secret as the HMAC key. Reject deliveries older than ~5 min.
WebSocket delivery: subscribe to channel kol:coordination on wss://madeonsol.com/ws/v1/stream — events are user-scoped (you only receive your own rule fires).
client.priceAlerts.* (new in 2.8)
Sub-second token MC dip/recovery alerts. Set a drop threshold on any token — when MC drops below baseline, a price_alert:dip event fires. Optionally track recovery. PRO: 5 alerts, ULTRA: 25 alerts.
// Create: alert when token drops 20%, then notify when it recovers 15% from the dip low
const { alert, webhook_secret } = await client.priceAlerts.create({
token_mint: "So11111111111111111111111111111111111111112",
drop_pct: 20,
recovery_pct: 15,
name: "SOL dip tracker",
delivery_mode: "webhook",
webhook_url: "https://example.com/dip-hook",
});
await client.priceAlerts.list();
await client.priceAlerts.get(alert.id);
await client.priceAlerts.update(alert.id, { name: "Renamed", is_active: false });
await client.priceAlerts.delete(alert.id);
// Event history (30-day retention)
const { events } = await client.priceAlerts.events({ event_type: "dip", limit: 50 });Alert lifecycle: watching -> dipped -> recovered (terminal). One-shot per alert. Baseline MC captured at creation time. 30-day auto-expiry. Thresholds immutable — delete and recreate to change.
WebSocket: subscribe to channel price_alert:events — user-scoped. Webhook: per-alert HMAC-SHA256 signed (same scheme as coordination alerts).
client.sniper.* — Deshred Sniper Alerts (new in 2.9)
The fastest path to a new pump.fun launch. Deploys are reconstructed from shred-level (deshred) data and surface in the feed ~500ms before the chain confirms them — a measured head start versus any confirmed-stream feed. PRO sees elite + good deployers; ULTRA sees every tier and can keep a custom deployer watchlist.
// Newest-first deshred deploy feed (PRO: elite/good · ULTRA: all tiers)
const { deploys } = await client.sniper.recent({ limit: 50, min_bond_rate: 0.5 });
// Audit one deployer's recent launches (ULTRA)
await client.sniper.byDeployer("7dEx...4pQ8");
// Custom watchlist — get deploys from only the deployers you track, any tier (ULTRA, max 50)
await client.sniper.addToWatchlist({ wallets: ["7dEx...4pQ8", "9aBc...2zZ1"], label: "alpha devs" });
await client.sniper.watchlist();
const { deploys: tracked } = await client.sniper.recent({ watchlist: true });
await client.sniper.removeFromWatchlist("7dEx...4pQ8");Detection is pre-execution, so payloads carry no MC/logs/balances — confirmed_on_chain is "deshred". For live push (not polling), use the sniper:deploy webhook event, the sniper:deploys WebSocket channel, or /alert sniper in the Telegram bot. ~1–3% of detected deploys may abandon before settlement.
client.kol.scoutLeaderboard(params?) (new in 2.8)
Scout leaderboard: top KOLs ranked by scout score, first-touch frequency, and swarm attraction rate. ULTRA only.
const data = await client.kol.scoutLeaderboard({ limit: 20, scout_tier: "S", sort: "scout_score" });client.kol.coordinationHistory(params?) (new in 2.8)
Historical coordination alert fires — past events with token, score, KOL count. ULTRA only.
const data = await client.kol.coordinationHistory({ limit: 50, min_score: 70 });client.token.kolConsensus(mint) (new in 2.8)
KOL consensus on a token: how many bought/sold, exit rate, net flow, median entry MC. ULTRA gets individual wallet arrays.
const consensus = await client.token.kolConsensus("4sVahM4U8js62mQV58ABSkNRhf6Ztc7Xs2LXUznNpump");client.token.peakHistory(mint) (new in 2.8)
Peak MC history: ATH, decline from peak, MC at bond and at 1h/6h/24h/7d after bond.
const peak = await client.token.peakHistory("4sVahM4U8js62mQV58ABSkNRhf6Ztc7Xs2LXUznNpump");client.kol.firstTouches(params?) (new in 2.2)
Recent first-KOL-touch events on tokens — every time a tracked KOL was the first to buy a given mint. Filterable by scout tier (S/A/B/C from the per-KOL mv_kol_scout_score view), KOL winrate, token age, mint suffix, etc.
Backtested signal: top scouts attract ≥3 follow-on KOLs within 4h ~50% of the time vs ~14% baseline (38d / 491k buys / 72,549 events). The full leaderboard is at madeonsol.com/kol/scouts.
// S-tier scouts on tokens younger than 1h
const { events } = await client.kol.firstTouches({
preset: "scout",
min_scout_tier: "S",
limit: 20,
});
for (const e of events) {
console.log(e.first_kol.name, "scouted", e.token_symbol, `(scout_score=${e.first_kol.scout_score}%)`);
}Filter knobs: since, before, limit, kol, min_kol_winrate_7d, min_scout_tier, min_n_touches, strategy, token_age_max_min, min_first_buy_sol, mint_suffix, preset ("scout" or "fresh_launch"), include (e.g. "followers_4h").
Don't poll — push. Median lead time before the second KOL is 12 seconds, so REST polling will lose the swarm. Subscribe to the
kol:first_touchesWebSocket channel (PRO+) or, on Ultra, create an HMAC-signed webhook subscription viaclient.firstTouchSubscriptions.create({...}).
Returns: FirstTouchesResponse
client.firstTouchSubscriptions.* (Ultra)
Create push-delivery rules for first-touch events. Up to 10 active subscriptions per Ultra user.
const { subscription, webhook_secret } = await client.firstTouchSubscriptions.create({
name: "S-tier scouts on pump tokens",
filters: { min_scout_tier: "S", mint_suffix: "pump" },
delivery_mode: "webhook",
webhook_url: "https://my.bot/hooks/scout",
});
// store webhook_secret — shown once
await client.firstTouchSubscriptions.list();
await client.firstTouchSubscriptions.update(subscription.id, { is_active: false });
await client.firstTouchSubscriptions.delete(subscription.id);Same HMAC scheme as coordination alerts. WebSocket channel: kol:first_touches.
client.kol.token(mint)
KOL buy/sell activity for a specific token mint.
const activity = await client.kol.token("EPjFW...");Returns: KolTokenActivity
client.kol.pnl(wallet, params?)
Deep per-wallet PnL breakdown with equity curve, risk metrics, and position history.
const pnl = await client.kol.pnl("7xKX...", {
period: "30d", // "7d" | "30d" | "90d" | "180d", default "30d"
});
// All tiers: summary + equity curve + closed positions
// ULTRA: + open positions (tokens bought but not yet sold)Returns: KolPnlResponse
client.kol.trendingTokens(params?)
Tokens ranked by KOL buy volume across multiple time windows.
const { tokens } = await client.kol.trendingTokens({
period: "1h", // "5m" | "15m" | "30m" | "1h" | "4h" | "8h" | "12h", default "1h"
min_kols: 2, // minimum distinct KOL buyers
limit: 20, // 1–50, default 20
});
// Available on all tiers; ULTRA unlocks full KOL wallet addresses per tokenReturns: KolTrendingTokensResponse
Alpha Wallet Intelligence — client.alpha
client.alpha.leaderboard(params?)
Leaderboard of 47,000+ scored early-buyer wallets ranked by win rate, PnL, or ROI.
const { wallets } = await client.alpha.leaderboard({
period: "30d", // "7d" | "30d" | "90d", default "30d"
sort: "win_rate", // "win_rate" | "pnl" | "roi"
min_tokens: 5,
exclude_bots: true,
});
// Up to 100 results on Free/Pro; ULTRA unlocks 500 + bot signalsReturns: AlphaLeaderboardResponse
client.alpha.wallet(wallet)
Full profile for an alpha wallet including per-token history and bot signals. ULTRA only.
const profile = await client.alpha.wallet("7xKX...");Returns: AlphaWalletResponse
client.alpha.linked(wallet)
Linked-wallet clustering — wallets that co-bought with this address within 2 seconds. ULTRA only.
const { linked } = await client.alpha.linked("7xKX...");Returns: AlphaLinkedResponse
client.alpha.capTable(mint)
First buyers for a token enriched with historical win rates, PnL, and KOL identity. PRO/ULTRA.
const { buyers } = await client.alpha.capTable("EPjFW...");Returns: AlphaCapTableResponse
client.alpha.buyerQuality(mint)
0–100 cohort quality score based on the profile of a token's first buyers. All tiers. 5-minute cache.
const { score } = await client.alpha.buyerQuality("EPjFW...");Returns: AlphaBuyerQualityResponse
Wallet Tracker — client.walletTracker
client.walletTracker.watchlist()
List your tracked wallets and remaining capacity.
const { wallets, capacity } = await client.walletTracker.watchlist();
// capacity: { used, limit } — Free: 10, Pro: 50, Ultra: 100Returns: WatchlistResponse
client.walletTracker.addToWatchlist(wallet, params?)
Add a wallet to your watchlist. Tracking begins immediately.
await client.walletTracker.addToWatchlist("7xKX...", { label: "whale" });client.walletTracker.removeFromWatchlist(wallet)
Remove a wallet from your watchlist.
await client.walletTracker.removeFromWatchlist("7xKX...");client.walletTracker.updateLabel(wallet, label)
Update the label for a tracked wallet.
await client.walletTracker.updateLabel("7xKX...", "smart money");client.walletTracker.trades(params?)
Historical swap and transfer events for your watched wallets. 120-day retention.
const { events } = await client.walletTracker.trades({
wallet: "7xKX...", // filter by specific wallet
action: "buy", // "buy" | "sell"
event_type: "swap", // "swap" | "transfer"
limit: 50,
before: "2026-04-01T00:00:00Z", // ISO 8601 cursor
});Returns: WalletTrackerTradesResponse
client.walletTracker.summary(params?)
Per-wallet stats across your watchlist: swap counts, SOL bought/sold, last event time.
const { wallets } = await client.walletTracker.summary({
period: "7d", // "24h" | "7d" | "30d", default "7d"
wallet: "7xKX...", // optional: single wallet
});Returns: WalletTrackerSummaryResponse
Universal Wallet — client.wallet (new in 2.7)
Per-wallet endpoints that work on any Solana wallet, not just curated KOLs. FIFO cost-basis PnL over the last 90 days. PRO+ on every method. Results are cached server-side in wallet_analyses with dynamic TTL (5min / 1h / 24h based on last activity); cache hits don't count against your daily quota.
Cost-basis honesty: observable only inside the 90-day data window. Wallets that sold tokens bought before that window have the overflow silently discarded rather than fabricated. notes.cost_basis_observable_from makes the cutoff visible per call.
client.wallet.stats(address)
Aggregate stats over 90d plus cross-product flags (KOL / alpha / deployer). Includes enrichments: top traded tokens with realized PnL, trading style, deployer tier mix, recent trades. v2.8 adds derived block: win rate, ROI, best/worst trade, biggest miss (token sold that later mooned), and AI-classified verdict.
const { stats, flags, derived } = await client.wallet.stats("ASVz...ybJk");
console.log(`${flags.kol_name ?? address}: ${stats?.total_trades} trades`);
if (derived?.verdict) {
console.log(`${derived.verdict.label}: ${derived.verdict.description}`);
}
if (derived?.biggest_miss) {
console.log(`Biggest miss: ${derived.biggest_miss.token_symbol} — missed +${derived.biggest_miss.missed_sol.toFixed(1)} SOL`);
}Returns: WalletStatsResponse (404 if the wallet has no trades and no flag-table presence).
client.wallet.pnl(address)
Full FIFO cost-basis PnL: realized + unrealized SOL, profit factor, max drawdown, avg + median hold minutes, daily UTC PnL curve, closed positions sorted by pnl desc (with ROI %, hold time, win/loss), and open positions hydrated with live current prices from the market-cap tracker.
const pnl = await client.wallet.pnl("ASVz...ybJk");
console.log(`Realized: ${pnl.summary.realized_sol} SOL · Unrealized: ${pnl.summary.unrealized_sol} SOL`);
console.log(`Win rate: ${(pnl.summary.win_rate! * 100).toFixed(1)}% · PF: ${pnl.summary.profit_factor}`);
for (const c of pnl.closed_positions.slice(0, 5)) {
const sign = c.pnl_sol > 0 ? "+" : "";
console.log(` ${c.token_mint.slice(0,8)}… ${sign}${c.pnl_sol} SOL (${c.roi_pct}% ROI, ${c.hold_minutes}m hold)`);
}Returns: WalletPnlResponse. Cache hits include cache_hit: true + computed_at; misses include ttl_seconds.
client.wallet.positions(address)
Open positions only — lighter slice of pnl(). Shares the same cache, so calling this right after pnl() is an immediate hit.
const { positions } = await client.wallet.positions("ASVz...ybJk");
for (const p of positions) {
const u = p.unrealized_sol;
console.log(` ${p.token_mint.slice(0,8)}… cost ${p.cost_basis_sol} SOL unrealized ${u ?? "—"} SOL (${p.unrealized_pct ?? "—"}%)`);
}Returns: WalletPositionsResponse. Mints without a current price return unrealized_sol: null rather than fabricated zero.
client.wallet.trades(address, params?)
Cursor-paginated raw trades. Default window is the last 90 days; override via since / until (Unix epoch seconds). Default limit 100, max 500.
let cursor: string | undefined;
while (true) {
const page = await client.wallet.trades("ASVz...ybJk", { limit: 200, cursor, action: "buy" });
for (const t of page.trades) processBuy(t);
if (!page.has_more) break;
cursor = page.next_cursor!;
}Params:
limit— 1-500, default 100cursor— fromnext_cursorof previous responseaction—"buy"or"sell"token_mint— filter to one tokensince/until— Unix epoch seconds (default last 90d)
Returns: WalletTradesResponse with trades[] + next_cursor + has_more + filters echo.
Deployer Hunter — client.deployer
client.deployer.stats()
Global statistics across all tracked deployer wallets.
const stats = await client.deployer.stats();
console.log(stats.overall_bonding_rate); // e.g. 0.043Returns: DeployerStats
client.deployer.leaderboard(params?)
Deployers ranked by bonding rate or recent performance.
const { deployers } = await client.deployer.leaderboard({
tier: "elite", // "elite" | "good" | "moderate" | "rising" | "cold"
sort: "bonding_rate", // "bonding_rate" | "recent_bond_rate" | "total_bonded" | "last_deploy_at"
limit: 20, // 1–50, default 20
offset: 0,
});Returns: DeployerLeaderboardResponse
client.deployer.profile(wallet)
Full profile for a single deployer wallet.
const deployer = await client.deployer.profile("3xAB...");
console.log(deployer.tier, deployer.bonding_rate);Returns: DeployerProfile
client.deployer.tokens(wallet, params?)
All tokens deployed by a specific wallet.
const { tokens } = await client.deployer.tokens("3xAB...", {
limit: 20,
offset: 0,
});Returns: DeployerTokensResponse
client.deployer.alerts(params?)
Real-time deploy alerts — fired when a tracked deployer launches a new token.
const { alerts } = await client.deployer.alerts({
since: "2025-01-01T00:00:00Z", // ISO 8601
limit: 20,
tier: "elite", // "elite" | "good" | "moderate" | "rising" | "cold"
offset: 0,
});Returns: DeployerAlertsResponse
client.deployer.alertStats(params?)
Aggregated alert statistics by tier.
const stats = await client.deployer.alertStats({ period: "7d" });
// "7d" | "30d" | "all", default "all"Returns: DeployerAlertStats
client.deployer.bestTokens(params?)
Top-performing tokens from tracked deployers by peak market cap.
const { tokens } = await client.deployer.bestTokens({
period: "7d", // "7d" | "30d" | "all", default "7d"
limit: 5, // 1–20, default 5
});Returns: BestTokensResponse
client.deployer.recentBonds(params?)
Most recently bonded tokens from tracked deployers.
const { bonds } = await client.deployer.recentBonds({ limit: 20 });Returns: RecentBondsResponse
Token Intelligence — client.token
Per-mint snapshots (price, MC, volume, deployer rep, KOL activity, blacklist flags, v1.7 velocity windows + MEV-share) and a filtered directory.
client.token.get(mint)
Comprehensive per-mint snapshot in one call. ULTRA also returns individual KOL wallet addresses in top_buyers[].
const { token } = await client.token.get("So11111111111111111111111111111111111111112");
console.log(token.price_usd, token.market_cap);
console.log(token.mc_change_pct?.["1h"]); // v1.7
console.log(token.mev_volume_pct?.["1h"]); // v1.7Invalid mints return a 400 with code: "invalid_mint", reason, received_length, example, and docs URL — no trial and error.
Returns: TokenResponse (with mc_change_pct / volume_usd / mev_volume_pct (each keyed by 5m/15m/1h/2h/4h) + history_age_seconds as of 1.7)
client.token.batch(mints)
Batch lookup up to 50 mints in one round-trip. ~10–20× cheaper than N sequential calls.
const { tokens } = await client.token.batch(["mint1", "mint2", "mint3"]);Returns: TokenBatchResponse
client.token.list(params?) (new in 2.6 — PRO+)
Filtered, sortable token directory. Default min_liq=2000 trims the long tail of phantom-MC tokens from low-liquidity pools; pass min_liq=0 to opt out.
Server-side filters (cheap, indexed): min_mc, max_mc, min_liq, active_h, primary_dex (pumpfun/pumpswap/raydium/meteora/orca/raydium_clmm), authority_revoked, exclude_token2022, min_lp_burnt_pct.
Computed post-filters (over-fetches 3×): min_volume_1h_usd, max_mev_share_pct, mc_change_1h_min_pct, mc_change_1h_max_pct. When any of these are set, pagination.post_filtered is true and page size may be smaller than limit.
// Momentum scanner: liquid mints up >20% in 1h, low bot share
const { tokens, pagination } = await client.token.list({
min_liq: 10000,
min_volume_1h_usd: 5000,
max_mev_share_pct: 60,
mc_change_1h_min_pct: 20,
sort: "mc_desc",
limit: 50,
});
// Cleanest filter for a sane "top by MC" feed
const { tokens } = await client.token.list({
min_liq: 25000,
active_h: 1,
authority_revoked: true,
sort: "mc_desc",
});Returns: TokenListResponse (with tokens[], pagination, filters echo)
client.token.batchBuyerQuality(mints)
Batch buyer-quality scoring for up to 50 mints. Shares the same 5-minute LRU cache as client.alpha.buyerQuality(mint).
Returns: AlphaBuyerQualityBatchResponse
Account — client.me() (new in 2.6)
Inspect your tier, quota state, and feature usage in one call. Reads from the same in-memory counters that drive rate-limit enforcement, so quota.daily.remaining is authoritative — no header parsing needed. Works on every tier (BASIC/PRO/ULTRA).
const me = await client.me();
console.log(`${me.tier}: ${me.quota.daily.remaining}/${me.quota.daily.limit} req left today`);
console.log(`Webhooks: ${me.features.webhooks.used}/${me.features.webhooks.limit}`);
console.log(`Copy-trade wallets: ${me.features.copytrade_wallets.used}/${me.features.copytrade_wallets.limit}`);
if (me.quota.daily.remaining < 100) {
// self-throttle
}Returns: MeResponse
Tool Directory — client.tools
client.tools.search(params?)
Search 950+ Solana tools indexed on MadeOnSol.
const { tools, count } = await client.tools.search({
q: "trading bot", // full-text search
category: "trading", // category slug filter
limit: 20, // 1–50, default 20
});Returns: ToolsSearchResponse
WebSocket Streaming — client.stream
client.stream.getToken()
Generate a 24-hour WebSocket streaming token. Pro/Ultra subscribers get ws_url for KOL/deployer event streaming. Ultra subscribers also get dex_ws_url for the all-DEX trade stream.
const token = await client.stream.getToken();
console.log(token.ws_url); // wss://madeonsol.com/ws/v1/stream
console.log(token.dex_ws_url); // wss://madeonsol.com/ws/v1/dex-stream (Ultra only)Returns: StreamToken — { token, expires_at, ws_url, dex_ws_url?, usage }
DEX Firehose (Ultra) — wss://madeonsol.com/ws/v1/dex-stream
Real-time trades across 9+ Solana DEX programs (Pump.fun, PumpAMM, PumpSwap, Raydium AMM/CPMM/CAMM, Jupiter v6, Orca Whirlpool, Meteora DBC/DAMM, LaunchLab/bonk.fun) on a single normalized WebSocket. Server-side filters drop everything you don't care about before it hits your socket.
Limits: ULTRA = 2 connections, 10 named subscriptions per connection, up to 500 trades replay from in-memory ring buffer. Inbound rate limit: 5 messages/sec (excess emits one error per second).
Quick start
import { WebSocket } from "ws"; // or native WebSocket in browsers/Bun
const { token, dex_ws_url } = await client.stream.getToken();
const ws = new WebSocket(`${dex_ws_url}?token=${token}`); // token MUST be appended as query param
ws.on("open", () => {
// Multi-subscription: each sub has its own sub_id and filters
ws.send(JSON.stringify({
type: "subscribe",
sub_id: "fresh-pumpfun",
replay: 50, // backfill up to 500 from ring buffer
filters: {
dex: "pumpfun",
token_age_max_seconds: 300, // first seen in last 5 min
min_sol: 0.5,
action: "buy",
},
}));
});
ws.on("message", (raw) => {
const msg = JSON.parse(raw.toString());
if (msg.channel === "dex:trades") {
// { sub_id, data: { wallet, mint, action, sol_amount, token_amount, dex, ... }, replay, ts }
console.log(msg.sub_id, msg.data.dex, msg.data.action, msg.data.sol_amount);
}
});Protocol — client → server
| type | Required fields | Notes |
|---|---|---|
| subscribe | sub_id, filters | Optional replay: 1–500 |
| update | sub_id, filters | Replaces filters in place — no disconnect needed |
| unsubscribe | sub_id | Or omit sub_id to clear all subs |
| list | — | Server replies with { type: "list", subs: [...] } |
| ping | — | Heartbeat — server replies { type: "pong" } |
Server → client message shapes
{ type: "connected", tier: "ULTRA", capabilities: { max_subs: 10, max_replay: 500, dex_names: [...], deployer_tiers: [...] } } // on connect
{ type: "subscribed", sub_id: "fresh-pumpfun", filters: { ... } }
{ type: "replay_done", sub_id: "fresh-pumpfun", count: 50 } // after backfill
{ type: "updated", sub_id: "fresh-pumpfun", filters: { ... } }
{ type: "unsubscribed", sub_id: "fresh-pumpfun" }
{ type: "list", subs: [{ sub_id, filters }] } // reply to { type: "list" }
{ type: "heartbeat", ts: 1712160000000 } // every 30s
{ type: "error", sub_id?, message: "..." }
{ channel: "dex:trades", sub_id, data: { ... }, replay: false, ts: 1712160000000 }Filter dimensions
At least one targeting filter is required (otherwise the firehose would dump every trade). Filters compose with AND semantics.
| Filter | Type | Notes |
|---|---|---|
| token_mint / token_mints | string / string[] (≤50) | Targeting |
| wallet / wallets | string / string[] (≤50) | Targeting |
| dex | string | string[] | pumpfun, pumpamm, pumpswap, raydium, jupiter, orca, meteora, launchlab |
| program | string | Raw program ID |
| deployer_tier | string | string[] | elite, good, moderate, rising, cold, unranked (uses Deployer Hunter scoring) |
| token_age_max_seconds | number | Only trades on mints first seen within window (uses persisted first-seen table) |
| market_cap_min_sol / market_cap_max_sol | number | Bounded by current market cap (last trade price × cached supply, 1h TTL) |
| min_sol / max_sol | number | Trade size bounds |
| action | "buy" | "sell" | Direction |
Async filters (token_age, deployer_tier, market_cap) evaluate against live state and are skipped on replay. The first trade for an unseen mint may be skipped while the supply fetch is in flight.
Multi-sub example
ws.send(JSON.stringify({ type: "subscribe", sub_id: "snipers", filters: { token_age_max_seconds: 60 } }));
ws.send(JSON.stringify({ type: "subscribe", sub_id: "whales", filters: { min_sol: 50 } }));
ws.send(JSON.stringify({ type: "subscribe", sub_id: "kol-mints", filters: { token_mints: ["EPjF...", "So11..."] } }));
// Tighten the snipers filter without disconnecting
ws.send(JSON.stringify({ type: "update", sub_id: "snipers", filters: { token_age_max_seconds: 30, min_sol: 0.3 } }));
// Drop whales when you're done
ws.send(JSON.stringify({ type: "unsubscribe", sub_id: "whales" }));Each dex:trades message echoes the sub_id that matched, so you can route them locally without reapplying filter logic client-side.
Webhooks — client.webhooks
Manage push notification webhooks for real-time events (Pro: 3, Ultra: 10).
// Create a webhook
const webhook = await client.webhooks.create({
url: "https://example.com/hook",
events: ["kol:trade", "deployer:alert"],
filters: { min_sol: 1 },
});
// List, update, delete
const { webhooks } = await client.webhooks.list();
await client.webhooks.update(webhook.id, { status: "paused" });
await client.webhooks.delete(webhook.id);
await client.webhooks.test(webhook.id);Error handling
All methods throw MadeOnSolError on non-2xx responses.
import { MadeOnSol, MadeOnSolError } from "madeonsol";
try {
const profile = await client.kol.wallet("invalid-wallet");
} catch (err) {
if (err instanceof MadeOnSolError) {
console.error(err.message); // human-readable message
console.error(err.status); // HTTP status code, e.g. 404
console.error(err.body); // raw response body
}
}Exported types
All types are exported from the main entry point:
import type {
// Errors
MadeOnSolError,
// KOL
KolTrade,
KolFeedParams,
KolFeedResponse,
KolLeaderboardParams,
KolLeaderboardResponse,
KolLeaderboardEntry,
KolWalletParams,
KolWalletProfile,
KolCoordinationParams,
KolCoordinationResponse,
CoordinatedToken,
KolTokenActivity,
KolPnlByToken,
// Deployer
DeployerStats,
DeployerLeaderboardParams,
DeployerLeaderboardResponse,
DeployerLeaderboardEntry,
DeployerProfile,
DeployerToken,
DeployerTokensParams,
DeployerTokensResponse,
DeployerAlertsParams,
DeployerAlertsResponse,
DeployerAlert,
DeployerAlertStatsParams,
DeployerAlertStats,
BestTokensParams,
BestTokensResponse,
BestToken,
RecentBondsParams,
RecentBondsResponse,
RecentBond,
// Tools
ToolsSearchParams,
ToolsSearchResponse,
Tool,
// KOL PnL & Trending
KolPnlResponse,
KolTrendingTokensResponse,
TrendingToken,
// Alpha Wallet Intelligence
AlphaWalletEntry,
AlphaLeaderboardResponse,
AlphaWalletResponse,
AlphaLinkedResponse,
AlphaCapTableResponse,
AlphaBuyerQualityResponse,
// Wallet Tracker
WalletEntry,
WatchlistResponse,
WalletTrackerEvent,
WalletTrackerTradesResponse,
WalletTrackerSummaryResponse,
// Streaming
StreamToken,
// Webhooks
Webhook,
WebhookCreateParams,
WebhookUpdateParams,
WebhookListResponse,
// Enums / unions
KolAction,
LeaderboardPeriod,
CoordinationPeriod,
DeployerTier,
DeployerSortField,
AlertPeriod,
BestTokensPeriod,
} from "madeonsol";Related
- MadeOnSol website — Browse 950+ Solana tools
- API documentation — Interactive endpoint reference
- MadeOnSol on GitHub — Main project repository
Also Available
| Platform | Package |
|---|---|
| Rust | madeonsol on crates.io |
| 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 |
License
MIT © MadeOnSol
