madeonsol 2.26.0 → 2.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,1606 +1,1608 @@
1
- # madeonsol
2
-
3
- [![npm version](https://img.shields.io/npm/v/madeonsol?style=flat-square)](https://www.npmjs.com/package/madeonsol)
4
- [![npm downloads](https://img.shields.io/npm/dm/madeonsol?style=flat-square)](https://www.npmjs.com/package/madeonsol)
5
- [![GitHub stars](https://img.shields.io/github/stars/madeonsol/madeonsol-sdk?style=flat-square&logo=github)](https://github.com/madeonsol/madeonsol-sdk)
6
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.4+-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
7
- [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen?style=flat-square)](package.json)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](LICENSE)
9
-
10
- > ⭐ **[Star on GitHub](https://github.com/madeonsol/madeonsol-sdk)** if you find this useful · 📂 **[Examples](./examples/)** · 📚 **[API docs](https://madeonsol.com/api-docs)**
11
-
12
- Official TypeScript/JavaScript SDK for the **[MadeOnSol](https://madeonsol.com) Solana API** — zero dependencies, fully typed, works in Node.js ≥ 18 and edge runtimes.
13
- > Real-time Solana trading intelligence: track 1,069 KOL wallets with <3s latency, score 23,000+ Pump.fun deployers, surface deshred deploy signals **~500ms before on-chain confirmation**, detect multi-KOL coordination, score token rug-risk 0–100 with a transparent factor breakdown, expose the bundle cohort that bought a token together and how much of supply it still holds, verify any wallet's current on-chain holdings with airdrop/insider `transfer_delta` detection, push every pump.fun graduation the second it bonds, and stream every DEX trade across 9+ programs. Free tier: 200 requests/day, every endpoint — no signup payment. Get a key at [madeonsol.com/pricing](https://madeonsol.com/pricing).
14
-
15
- > **New in 2.26.0 — token surges & revivals: momentum fires with the honest half attached — one endpoint + one live channel.** `client.token.surges(params?)` (typed `TokenSurgesResponse`) binds `GET /tokens/surges`: **`surge`** = a token < 30 min old whose MC runs hard vs its *launch* MC (tier `early` 10 min / $12k / 3×, `strong` 30 min / $30k / 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); **`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). Every row carries `tape` (buys / sells / volume, `unique_buyers` only where wallet data exists`wallet_data_available: false` otherwise, never inferred), `kol`, `early_buyers` (bundled / sold / sniper wallets), `deployer` reputation and **`risk_flags[]`** (`bundled_launch`, `few_buyers`, `wash_pattern`, `thin_liquidity`, `cold_deployer`, `sniper_heavy`, `early_buyers_exiting`, `sell_pressure`, `no_tape_trades`, `no_prior_price`, `mint_authority_active`, `transfer_fee`); rows 65 min old carry the +1 h `outcome` (`mc_1h_multiple`, `peak_1h_multiple`, `priced_after_1h`) and `stats: true` returns per-(kind, tier) hit-rates out-of-sample by construction. Filters `kind`, `tier`, `mint`, `launchpad`, `deployer_tier`, `min_mc_usd` / `max_mc_usd`, `min_buys`, `exclude_flags`, `only_clean`; cursors `since` / `before`. Pushed live on the new **`token:surges`** WS channel (events `token:surge` / `token:revival`, typed `TokenSurgeStreamEvent`, server-side filters `TokenSurgesSubscribeFilters`: `kinds[]`, `tiers[]`, `launchpads[]`, `exclude_flags[]`, `min_mc_usd` / `max_mc_usd`, `deployer_tier[]`). Nearly every scalar is `| null` — null means unknown, never zero. **PRO+** (BASIC receives HTTP 403) on the keyed `msk_` API.
16
-
17
- > **New in 2.25.1stream tokens never expire.** `client.stream.getToken()` (`POST /stream/token`) now returns the **same token on every call, forever**. It stops working only if your subscription lapses or you call `client.stream.getToken({ rotate: true })` to replace it (the previous value keeps working for 60 s). `StreamToken.expires_at` and `next_refresh_at` are always `null` (kept for wire compatibility do not schedule refreshes on them); the response gains `rotated: boolean` and `lifetime: string`. The server never rotates on its own and never sends `token_refresh` unless you rotated; a `4001` close means "mint again" (lapsed or rotated), never a timer. Prefer `Authorization: Bearer <token>` on the WebSocket handshake `?token=` still works and is masked in access logs. `client.stream.connect()` already does the right thing (it calls `getToken()` on every (re)connect); no code change needed on your side.
18
-
19
- > **New in 2.25.0token locks & vesting, upcoming unlocks, and pump.fun creator-fee sharing / fee claims — five endpoints + two live channels.** `client.token.locks(mint, params?)` (typed `TokenLocksResponse`) binds `GET /tokens/{mint}/locks`: every on-chain Streamflow / Jupiter Lock / Bonfida lock or vesting contract on a mint, decoded from the locker programs' account state, with a LIVE-derived view (`locked_raw` still locked, `unlocked`, `withdrawn`, `claimable`, `status`, `next_unlock`) and a `summary` (locked / deposited totals, the 7d / 30d forward unlock schedule, `active_cancelable_by_sender` — a lock the sender can cancel is a weaker promise). `client.token.locksFeed(params?)` (`GET /tokens/locks`) is the cross-token feed of NEW contracts, cursor-paginated (`pagination.next_since` / `next_before`) and pushed live on the new **`token:locks`** WS channel (event `token:lock`, typed `TokenLockStreamEvent`). `client.token.unlocks(params?)` (`GET /tokens/unlocks`) lists upcoming unlock EVENTS (cliff / period / final / tranche) inside `within=1h…90d` with `window_amount_*` per contract. `client.token.feeShares(mint)` (`GET /tokens/{mint}/fee-shares`) decodes a pump.fun coin's on-chain `SharingConfig` who its creator fees are redirected to (`share_bps`, `is_admin`, `is_social_pda` for fees earmarked for an X account etc., `redirected_bps`, `social_bps`, `is_default` = 100% to the creator) plus the distribution rollup per recipient and the config change log; `client.token.feeClaims(params?)` (`GET /tokens/fee-claims`) is the fee-event feed (`distribution` with per-address `payouts[]`, `social_claim`, `shares_created` / `updated` / `reset`, `creator_transferred`, `creator_claim` on request), pushed live on the new **`token:fee_claims`** channel (event `token:fee_claim`, typed `TokenFeeClaimStreamEvent`). Honest limits: base-unit amounts are **strings** and ui / usd / pct are `null` when decimals or price are unknown; **LP locks are NOT included** (token / vesting locks only); **fee-event history starts 2026-08-17**; all five are **PRO+** (BASIC receives HTTP 403) on the keyed `msk_` API.
20
-
21
- > **New in 2.24.0 — live holder census: exact holder count, labelled holders, and pools that are named, not just excluded.** `client.alpha.holders(mint)` (typed `TokenHoldersResponse`) binds `GET /tokens/{mint}/holders` (PRO+): every token account of the mint read from the ledger at `confirmed` and merged per owner, so `concentration.holder_count` is EXACT (distinct non-zero owners minus pools / bonding curves / burns) never a trade-derived estimate; it is `null` only when the provider refuses the census for a mega-cap, in which case you get the top-20 view and `source.census_fallback_reason` says so. Each disclosed owner carries our labels (`deployer` / `kol` / `early_buyer` / `bundle` / `bot` / `dump_cluster` — empty means unknown to us, not clean), and `excluded[]` NAMES what was taken out of the circulating denominator: `reason` = `pool` (with `dex` + `pool_address`), `bonding_curve` (pump.fun / LaunchLab), `burn`, or `program_account` only when we genuinely cannot attribute the PDA; `pool_pct` / `burned_pct` / `program_pct` split 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 get `503 holder_scan_in_progress` with `retry_after_seconds: 20` while the scan finishes into the cache, and the retry is instant.
22
-
23
- > **New in 2.23.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_usd` on 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. Because `sol_amount` is 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 new **`market_price_sol`/`market_price_usd`** fields sampled once per token per pool update, so every trade in the same slot shares it. Until now `price_sol` carried 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. Use `price_sol` for cost basis, fills and PnL; `market_price_sol` for a per-token series independent of trade size and direction. Both `client.alpha.tokenTrades(mint)` and `client.wallet.trades(address)` carry all four fields on `TokenTrade` / `WalletTrade` the wallet tape returned amounts and no price at all before.
24
-
25
- > **New in 2.22.0****Clean stream shutdown.** `client.stream.connect().close()` now fully tears down the underlying WebSocket so short-lived scripts exit promptly instead of hanging on a lingering socket. In Node the client now prefers the `ws` package (which exposes `terminate()`) and hard-terminates on close; the browser still uses the native WebSocket. No API changes purely a lifecycle fix. (If you don't already depend on `ws` and want the fast exit on Node ≥22, `npm i ws`.)
26
- >
27
- > **New in 2.21.0** — **Pool depth / price-impact + dev self-activity on the risk score.** `client.alpha.tokenDepth(mint, { sizes? })` (`GET /tokens/{mint}/depth`, **PRO+**) answers "how much SOL does it take to move the price N%", per pool. Each depth-computable pool returns `spot_price_sol`, `fee_pct`, `source` (`"stream"` for constant-product AMMs served zero-RPC from stream reserves, `"live_rpc"` for pump.fun/bonk curves priced from a live read of the curve's VIRTUAL reserves), `reserves_age_ms`, per-size `quotes` (`size_sol`/`tokens_out`/`avg_price_sol`/`price_impact_pct`), and `to_move_price` (SOL to move price `1pct`/`5pct`/`10pct`). `sizes` accepts a CSV string or `number[]` (max 8, each >0 and ≤10000; default `0.5,1,5,10`); the response carries `sol_usd`, `sizes_sol`, `primary_pool`, and honesty-first `unsupported_pools`concentrated pools (CLMM/Orca/DLMM), Meteora-DBC curves, and unclassified pools come back with a `reason` instead of a wrong number. Impact is per-pool, not router-optimal. Plus `client.alpha.risk(mint)` responses gain a top-level `dev` block (`TokenRiskDev | null`) deployer self-activity for the mint: `wallet`, `launchpad`, `deployed_at`, create-tx `buy_sol`/`buy_tokens`/`buy_supply_pct`, post-create `bought_tokens_after`/`sold_tokens`/`sold_sol` with `first_sell_at`/`last_sell_at`, live on-chain `holdings_tokens`/`holdings_supply_pct`, `wallet_empty` (`boolean | null`), and `transferred_out` (`boolean | null` — chain balance well below the trade-derived expectation, i.e. tokens moved without a swap). `dev` is `null` when the mint has no tracked deploy row (single-mint `/risk` only; absent on batch items). New types: `TokenDepthParams`, `TokenDepthResponse`, `TokenDepthPool`, `TokenDepthUnsupportedPool`, `TokenDepthPoolBase`, `TokenDepthQuote`, `TokenDepthToMovePrice`, `TokenDepthSource`, `TokenRiskDev`.
28
- >
29
- > **New in 2.20.0** — **Wallet batch classify, token trade tape, sniper footprint.** `client.wallet.batchClassify(wallets)` (`POST /wallet/batch/classify`, 1–100 addresses, **PRO+**) returns bulk reputation flags per wallet: `is_sniper`, `is_bundler` (lifetime), `is_dumper` (rolling 42d), `is_kol` + `kol_name`, `bot_confidence`, and `dump_cluster` cohort stats — flags are pump.fun-pipeline scoped (`false` = not observed, NOT verified clean). `client.token.trades(mint, params?)` (`GET /tokens/{mint}/trades`, **PRO+**) is the mint-scoped trade tape cursor-paginated raw trades with `price_sol`/`price_usd`/`early_buyer_rank`/`slot`, filterable by `action`/`wallet`/`since`/`until`, defaulting to the FULL history (starts 2026-04-12; the `coverage` block carries `history_start` + `scope`). The wallet profile `flags` block (`client.wallet.stats()`) gains the same `is_sniper`/`is_bundler`/`is_dumper` + `dump_cluster` fields, and **`bot_confidence` is a type fix**: previously typed `number | null` but the API always returned `null` due to a bug it now returns the real value as a string enum `"none" | "low" | "medium" | "high" | null`. `TokenRiskInputs` gains `sniper_footprint` (slot-window snipe rollup: `buys`/`buyers`/`sol`/`supply_pct`/`sniper_wallet_buys`/`data_available`/`as_of`, or null) and `client.sniper.recent()` deploys each carry the same `footprint` block. New types: `WalletClassification`, `WalletBatchClassifyResponse`, `TokenTradesParams`, `TokenTrade`, `TokenTradesResponse`, `TokenTradesCoverage`, `SniperFootprint`, `BotConfidence`, `DumpClusterStats`.
30
- >
31
- > **New in 2.19.0** — **Verified wallet holdings.** `client.wallet.holdings(address, { limit?, min_value_usd? })` (`GET /wallet/{address}/holdings`) reads the wallet's actual current SPL + Token-2022 token accounts and SOL balance straight from chain, enriches each with our price/MC/name/symbol, and computes a `transfer_delta` (on-chain amount trade-derived net position)exposing tokens that arrived or left **without a swap** (airdrops, insider funding, wallet-hopping). Distinct from `client.wallet.positions()` (trade-derived FIFO): holdings is "what they actually hold right now". Returns `WalletHoldingsResponse` with a `summary` (`token_accounts`/`non_zero`/`returned`/`priced`/`total_value_usd`/`truncated`), `sol_balance`, and `verified_at`. `limit` 1–500 (default 200), `min_value_usd` ≥0 (default 0). **ULTRA only.** New types: `WalletHoldingsParams`, `WalletHoldingsResponse`, `Holding`.
32
- >
33
- > **New in 2.18.0** — **Bundle cohort intelligence.** `client.alpha.bundle(mint)` (`GET /tokens/{mint}/bundle`) surfaces the wallets that bought a token together in one atomic transaction (`bundle_kind: "atomic_tx"`) or the same slot (`"same_slot"`) and, headline first, how much of supply they still hold. The `bundle` summary block (returned on every tier) carries `held_pct_of_supply` (0–1 of total supply, HEADLINE), `bundle_kind`, `wallet_count`, `held_ratio`, `fully_exited`, `buy_volume`, and `tokens_held`. **Tier-gated:** BASIC/TRADER get the `bundle` block only (`wallets: []`); PRO adds the top-10 cohort wallets with flags (`held_ratio`, `has_sold`, `atomic`, `is_kol`); ULTRA returns the full cohort plus identity (`kol_name`, `win_rate`, `bot_confidence`) and per-wallet `tokens_held`. New types: `TokenBundleResponse`, `BundleSummary`, `BundleWallet`, `BundleKind`.
34
- >
35
- > **New in 2.17.0** — **Batch risk scoring + live stream-session control.** `client.token.batchRisk(mints)` (`POST /tokens/batch/risk`, up to 50 mints, **counts as 1 request**) returns the same transparent 0–100 rug-risk result as `client.alpha.risk(mint)` for each mint (with `as_of`); untracked mints come back as `{ mint, error: "not_tracked" }` without failing the batch. `client.stream.sessions()` lists your live WebSocket sessions (`ws-streaming` + `dex-stream`) and `client.stream.deleteSession(id)` force-closes one to free a slot a ghost socket is holding. **PRO/ULTRA only.** New types: `TokenRiskBatchResponse`, `TokenRiskBatchItem`, `TokenRiskBatchError`, `StreamSession`, `StreamSessionsResponse`, `StreamSessionEvictResponse`.
36
- >
37
- > **New in 2.16.0** — **Almost-bonded discovery + trending sorts.** `client.token.almostBonded({ min_progress, min_velocity_pct_per_min, deployer_tier, sort, limit })` pre-bond pump.fun tokens near graduation, ranked by velocity (Δprogress/min): "95% and accelerating" beats "92% stalled". Each token carries `progress_pct`, `velocity_pct_per_min`, `eta_minutes`, `stalled`, `real_sol_reserves`, `market_cap_usd`, `liquidity_usd`, `authorities_revoked`, `deployer_tier`, and `age_minutes`. **PRO/ULTRA only.** New types: `AlmostBondedParams`, `AlmostBondedToken`, `AlmostBondedResponse`, `AlmostBondedSort`. Plus `client.token.list({ sort })` gains four momentum sorts `mc_change_5m_desc`, `mc_change_1h_desc`, `volume_1h_desc`, and `trending` (composite recent-volume × positive-momentum rank).
38
- >
39
- > **New in 2.15.0** — **Token flow + deployer SOL balance.** `client.alpha.tokenFlow(mint, { window })` (`GET /tokens/{mint}/flow`, `window` `1h` default or `24h`, **PRO+**) returns aggregated buy/sell flow for a token: `unique_wallets`/`unique_buyers`/`unique_sellers`, `buy_count`/`sell_count`/`total_trades`, `buy_sol`/`sell_sol`/`net_sol` (buy sell), and `trades_per_wallet`, plus the window `from` timestamp. New types: `TokenFlowResponse`, `TokenFlowParams`, `TokenFlowWindow`. Deployer-alert objects (`DeployerAlert`) now also carry `deployer_sol_balance` (the deployer wallet's SOL balance at alert time, `number | null`).
40
- >
41
- > **New in 2.14.0** — **OHLCV candles + net flow.** `client.alpha.candles(mint, { tf, limit, from, to })` returns the persisted price/MC trajectory as candlesticks (`1m`/`5m`/`15m`/`1h`/`4h`/`1d`, rolled up on read). **PRO**: OHLCV (`open`/`high`/`low`/`close`/`volume_usd`/`trades`/`market_cap_usd`), last 30 days. **ULTRA**: adds per-bar net flow (`buy_volume_usd`/`sell_volume_usd`/`net_volume_usd`, `buy_count`/`sell_count`, `volume_mev_usd`), liquidity delta (`open_liquidity_usd`/`close_liquidity_usd`) and full history `net_flow_included` flags which set you got. New types: `Candle`, `CandlesResponse`, `CandlesParams`, `CandleTimeframe`.
42
- >
43
- > **New in 2.13.0** — **Token risk score.** `client.alpha.risk(mint)` returns a transparent 0–100 rug-risk/safety score (higher = riskier) for any token: a `band` (`safe`/`caution`/`danger`), an explainable `factors[]` array (each with `key`, `label`, `status`, `points`, `detail`) that sums into the score, and the raw `inputs` it was computed from — mint/freeze authority revocation, liquidity USD + liquidity-to-MC ratio, transfer fee bps, Token-2022 flag, burn detection, launch cohort (SOL + size), deployer bond rate + total deployed, KOL signal, and blacklist status. Plus `score_version` and `as_of`. **PRO/ULTRA only.** New types: `TokenRiskResponse`, `TokenRiskFactor`, `TokenRiskInputs`, `TokenRiskBand`, `TokenRiskStatus`.
44
- >
45
- > **New in 2.12.0** — **Launch cohort, liquidity/MC ratio, deployer tier filter, KOL hold stats, and signal performance.** `TokenResponseBody` (single + batch) gains `liquidity_to_mc_ratio`, `launch_cohort_sol`, and `launch_cohort_size`. `client.token.list()` adds `min_liq_mc_ratio`, `max_liq_mc_ratio`, and `deployer_tier` filter params; list items gain `liquidity_to_mc_ratio` and `deployer_tier`. `KolLeaderboardEntry` gains `median_hold_minutes_30d` and `percentile_early_entry_30d`. New top-level method `client.getSignalPerformance(name)` calls `GET /signals/{name}/performance`.
46
- >
47
- > **New in 2.11.1** — **Deployer runner-rate fields.** Sniper deploys, deployer alerts/profiles, and leaderboard rows now carry `runner_rate` (fraction of the deployer's labeled tokens that ran peak ≥60min after deploy vs dumped) and `labeled_tokens` (confidence denominator; gate on ≥3).
48
- >
49
- > **New in 2.11** — **Graduation events + dump-cluster detection.** Subscribe `token:graduations` for every pump.fun bond in real time — tracked deployer or not — with typed `GraduationEvent` payloads (mint, deployer tier, time-to-bond, MC at bond). Buyer-quality `breakdown` adds `dump_cluster_count` (out-of-sample validated: 3+ such wallets in the first-20 94% dump vs 61% base) and `recycled_early_buyer_count` (high count with zero cluster leans runner). DEX firehose: replay buffer deepened to ~5 minutes; mint-scoped subs now receive in-band `dex:graduations` frames — the bond lands on the same connection as your position's trade flow.
50
-
51
- > **New in 2.9** — **Deshred Sniper Alerts.** `client.sniper.recent()` surfaces new pump.fun deploys reconstructed from shred-level data ~500ms before the chain confirms them a measured head start over any confirmed-stream feed. PRO sees elite/good deployers; ULTRA sees every tier and maintains a custom deployer watchlist (`client.sniper.addToWatchlist()`). Use the `sniper:deploys` WebSocket channel or `sniper:deploy` webhook for live push instead of polling.
52
- >
53
- > **New in 2.8** — **Price alerts, scout leaderboard, wallet derived stats.** `client.priceAlerts.*` CRUD for token MC dip/recovery alerts delivered via webhook or WebSocket (PRO=5, ULTRA=25). `client.kol.scoutLeaderboard()` top scouts ranked by first-touch follow-on rate. `client.kol.coordinationHistory()` and `client.token.peakHistory()` expose the historical record. `client.wallet.stats()` now returns a `derived` block: `win_rate`, `roi`, `verdict`, and `biggest_miss`.
54
- >
55
- > **New in 2.7** — **Universal Wallet API.** `client.wallet.stats()`, `client.wallet.pnl()`, `client.wallet.positions()`, `client.wallet.trades()` — FIFO cost-basis PnL, open positions hydrated with live prices, and cursor-paginated raw trades for **any** Solana wallet (not just curated KOLs). PRO+. Server-side cache (5min/1h/24h based on activity) cache hits don't count against your quota.
56
- >
57
- > **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 by `5m`/`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 get `undefined`. Patch is type-only no runtime breaking changes.
58
- >
59
- > **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 default `min_liq=2000` to 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 every `TokenResponseBody`: `mc_change_pct`, `volume_usd`, `mev_volume_pct` (each keyed by `5m`/`15m`/`1h`/`2h`/`4h`) plus `history_age_seconds` on the parent. `/token/{mint}` 400s now ship `code`, `reason`, `received_length`, `example`, and `docs` URL stop guessing why a mint failed. Deprecated `avg_entry_mc_usd` / `entry_mc_samples` removed from leaderboard types. All other 2.5.x APIs unchanged.
60
-
61
- > **Build Solana trading bots, analytics dashboards, KOL copy-trading tools, deshred sniper bots, and ecosystem browsers.**
62
-
63
- ## Quick start (10 seconds)
64
-
65
- ```bash
66
- npm install madeonsol
67
- ```
68
-
69
- ```ts
70
- import { MadeOnSol } from "madeonsol";
71
- const client = new MadeOnSol({ apiKey: "msk_..." }); // free tier at madeonsol.com/pricing
72
- const { trades } = await client.kol.feed({ limit: 5, action: "buy" });
73
- ```
74
-
75
- | Feature | Description |
76
- |---|---|
77
- | **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,069 tracked KOL wallets. **180 days of trade history** retained. |
78
- | **Deshred Sniper** | Deploy feed reconstructed from shred-level data — surfaces new pump.fun launches **~500ms before on-chain confirmation**. PRO: elite/good deployers. ULTRA: all tiers + custom watchlist. Use WebSocket/webhook for live push. |
79
- | **Alpha Wallet Intel** | Leaderboard of 1M+ scored early-buyer wallets, full wallet profiles, linked-wallet clustering, token cap-table enrichment, and 0–100 buyer quality scores with dump-cluster wallet detection. |
80
- | **Token Risk Score** | Transparent 0–100 rug-risk/safety score per token with a `safe`/`caution`/`danger` band, explainable factor breakdown, and the raw inputs (authorities, liquidity, transfer fee, launch cohort, deployer bond rate, KOL signal, blacklist). PRO/ULTRA. |
81
- | **Bundle Cohort** | The wallets that bought a token together (one atomic tx or the same slot) and — headline first — `held_pct_of_supply` still held, plus `held_ratio`, `fully_exited`, and buy volume. Every tier gets the summary; PRO adds top-10 wallet flags; ULTRA adds KOL identity, win rate, bot confidence, and per-wallet balances. |
82
- | **Universal Wallet** | FIFO cost-basis PnL, open positions (hydrated with live prices), and raw trade history for **any** Solana wallet not just curated KOLs. 90-day window, server-side cache. PRO+. |
83
- | **Verified Holdings** | Current on-chain SPL + Token-2022 balances + SOL, read straight from chain and enriched with price/MC/name, plus a `transfer_delta` that exposes airdrops / insider funding / wallet-hopping (tokens that moved without a swap). ULTRA. |
84
- | **Price Alerts** | Token MC dip/recovery alerts delivered via WebSocket or HMAC-signed webhook. PRO: 5 rules, ULTRA: 25. |
85
- | **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. |
86
- | **Deployer Hunter** | 23,000+ pump.fun deployers scored by bonding rate tier leaderboard, deploy alerts, deployer profiles, and best-tokens feed. |
87
- | **DEX Trade Stream** | Real-time WebSocket stream of ALL Solana DEX trades across 9+ programs filter by token, wallet, DEX, deployer tier, or trade size. ~5 min replay + in-band graduation frames on mint-scoped subs. ULTRA. |
88
- | **Webhooks** | Push notifications for KOL trades, coordination signals, deployer alerts, and wallet tracker events (Pro/Ultra) |
89
- | **Tool Directory** | Search 1,070+ Solana tools and dApps indexed on MadeOnSol |
90
-
91
- **Links:** [Full docs](https://madeonsol.com/solana-api) · [Website](https://madeonsol.com) · [API docs](https://madeonsol.com/api-docs)
92
-
93
- ## Authentication
94
-
95
- Get a free API key at [madeonsol.com/pricing](https://madeonsol.com/pricing). Keys start with `msk_`.
96
-
97
- ---
98
-
99
- ## Install
100
-
101
- ```bash
102
- npm install madeonsol
103
- # or
104
- yarn add madeonsol
105
- # or
106
- pnpm add madeonsol
107
- ```
108
-
109
- Requires **Node.js ≥ 18** (uses native `fetch`). Works out of the box in Cloudflare Workers, Vercel Edge, and Bun.
110
-
111
- ---
112
-
113
- ## Quick start
114
-
115
- ```ts
116
- import { MadeOnSol } from "madeonsol";
117
-
118
- const client = new MadeOnSol({ apiKey: "msk_your_api_key_here" });
119
-
120
- // Latest KOL buy trades
121
- const { trades } = await client.kol.feed({ limit: 10, action: "buy" });
122
- console.log(trades[0].kol_name, "bought", trades[0].token_symbol);
123
-
124
- // Deshred sniper — ~500ms before on-chain confirmation (PRO/ULTRA)
125
- const { deploys } = await client.sniper.recent({ limit: 20, min_bond_rate: 0.5 });
126
- console.log(deploys[0].token_name, "deployed by", deploys[0].deployer_tier, "tier deployer");
127
-
128
- // Multi-KOL coordination signal
129
- const { coordination } = await client.kol.coordination({ min_kols: 3, min_score: 70 });
130
-
131
- // FIFO PnL for any wallet (PRO+)
132
- const pnl = await client.wallet.pnl("ASVz...ybJk");
133
- console.log(`Realized: ${pnl.summary.realized_sol} SOL · Win rate: ${(pnl.summary.win_rate! * 100).toFixed(1)}%`);
134
-
135
- // Search Solana tools
136
- const { tools } = await client.tools.search({ q: "trading", limit: 10 });
137
- ```
138
-
139
- ---
140
-
141
- ## Use cases
142
-
143
- - **Copy-trading bot** — stream KOL buys via `client.kol.feed()` and mirror trades
144
- - **Deshred sniper** — `client.sniper.recent()` or subscribe to `sniper:deploys` WebSocket for ~500ms pre-confirm deploy signals
145
- - **DEX trade sniping** — subscribe to the all-DEX stream filtered by token, wallet, or deployer tier
146
- - **Graduation sniper / position manager** — subscribe `token:graduations` for every pump.fun bond in real time, or hold a mint-scoped firehose sub and get the bond in-band with your position's trade flow
147
- - **Coordination detector** — flag tokens with `client.kol.coordination({ min_kols: 3, min_score: 70 })`
148
- - **Scout signal** — track first-KOL-touch events filtered to S/A-tier scouts via `client.kol.firstTouches({ preset: "scout" })`
149
- - **Rug-risk gate** — score a token with `client.alpha.risk(mint)` and skip anything in the `danger` band before buying
150
- - **Bundle-cohort check** — call `client.alpha.bundle(mint)` and bail when `held_pct_of_supply` is high (a bundle still sitting on supply can dump) or when the cohort hasn't `fully_exited`
151
- - **Wallet analyser** — `client.wallet.pnl()` for FIFO cost-basis PnL on any Solana wallet
152
- - **Holdings verifier / airdrop detector** — `client.wallet.holdings()` for verified current on-chain balances, and flag tokens with a nonzero `transfer_delta` (arrived without a swap airdrops, insider funding, wallet-hopping)
153
- - **Price alert bot** — `client.priceAlerts.create()` for MC dip/recovery alerts delivered via webhook
154
- - **Analytics dashboard** — combine leaderboard, PnL, token velocity, and tool data
155
- - **Telegram/Discord bot** — pipe alerts via webhooks into chat
156
- - **Portfolio tracker** — use `client.kol.wallet()` to follow specific KOL positions
157
-
158
- ---
159
-
160
- ## API Reference
161
-
162
- ### KOL Tracker — `client.kol`
163
-
164
- #### `client.kol.feed(params?)`
165
-
166
- Live feed of trades made by tracked KOL wallets.
167
-
168
- ```ts
169
- const { trades, count } = await client.kol.feed({
170
- limit: 50, // 1–100, default 50
171
- action: "buy", // "buy" | "sell"
172
- kol: "7xKX...", // filter by specific wallet
173
- });
174
- ```
175
-
176
- Returns: `KolFeedResponse` — `{ trades: KolTrade[], count: number }`
177
-
178
- 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.
179
-
180
- ---
181
-
182
- #### `client.kol.leaderboard(params?)`
183
-
184
- KOL PnL leaderboard ranked by realized profit.
185
-
186
- ```ts
187
- const { leaderboard, period } = await client.kol.leaderboard({
188
- period: "7d", // "today" | "7d" | "30d" | "90d" | "180d", default "7d"
189
- });
190
- ```
191
-
192
- > **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.
193
-
194
- Each `KolLeaderboardEntry` includes `median_hold_minutes_30d` (median position hold duration in minutes over the last 30 days) and `percentile_early_entry_30d` (early-entry percentile rank 0–100 over the last 30 days).
195
-
196
- Returns: `KolLeaderboardResponse`
197
-
198
- ---
199
-
200
- #### `client.kol.wallet(wallet, params?)`
201
-
202
- Full profile for a single KOL wallet, including trade history and optional per-token PnL breakdown.
203
-
204
- ```ts
205
- const profile = await client.kol.wallet("7xKX...", {
206
- include: "pnl_by_token",
207
- });
208
- ```
209
-
210
- Returns: `KolWalletProfile`
211
-
212
- ---
213
-
214
- #### `client.kol.coordination(params?)`
215
-
216
- 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.
217
-
218
- ```ts
219
- const { coordination, score_version, window_minutes } = await client.kol.coordination({
220
- period: "24h", // "1h" | "6h" | "24h" | "7d", default "24h"
221
- min_kols: 3, // 2–50, default 3
222
- limit: 20, // 1–50, default 20
223
- window_minutes: 15, // v1.1 peak-density window in minutes (1–60)
224
- min_score: 60, // v1.1 filter by composite score (0–100)
225
- include_majors: false, // v1.1 — include WIF/BONK/POPCAT
226
- });
227
-
228
- for (const c of coordination) {
229
- console.log(c.token_symbol, "score", c.coordination_score, "peak", c.peak_kols, "exited", c.exited_count);
230
- // c.kols[]: { name, wallet, buy_sol, sell_sol, exited }
231
- }
232
- ```
233
-
234
- Returns: `KolCoordinationResponse` — `{ coordination: CoordinatedToken[], score_version, window_minutes }`
235
-
236
- ---
237
-
238
- #### `client.coordinationAlerts.*` (v1.1)
239
-
240
- 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.**
241
-
242
- ```ts
243
- // Create a rule: ≥5 KOLs, 10-min window, score ≥70, webhook delivery
244
- const { rule, webhook_secret } = await client.coordinationAlerts.create({
245
- name: "strong-clusters",
246
- min_kols: 5,
247
- window_minutes: 10,
248
- min_score: 70,
249
- include_majors: false,
250
- cooldown_min: 30, // don't re-fire same token within 30 min
251
- score_jump_break: 15, // UNLESS score jumps by 15+ (catches conviction surges)
252
- delivery_mode: "webhook", // "websocket" | "webhook" | "both"
253
- webhook_url: "https://example.com/coord-hook",
254
- });
255
- // SAVE webhook_secret — used for HMAC-SHA256 signature verification.
256
-
257
- await client.coordinationAlerts.list();
258
- await client.coordinationAlerts.get(rule.id);
259
- await client.coordinationAlerts.update(rule.id, { min_score: 80, is_active: false });
260
- await client.coordinationAlerts.delete(rule.id);
261
- ```
262
-
263
- Webhook signatures: header `X-MadeOnSol-Signature` = `sha256(timestamp + "." + body)` with `webhook_secret` as the HMAC key. Reject deliveries older than ~5 min.
264
-
265
- 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).
266
-
267
- ---
268
-
269
- #### `client.priceAlerts.*` *(new in 2.8)*
270
-
271
- **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.**
272
-
273
- ```ts
274
- // Create: alert when token drops 20%, then notify when it recovers 15% from the dip low
275
- const { alert, webhook_secret } = await client.priceAlerts.create({
276
- token_mint: "So11111111111111111111111111111111111111112",
277
- drop_pct: 20,
278
- recovery_pct: 15,
279
- name: "SOL dip tracker",
280
- delivery_mode: "webhook",
281
- webhook_url: "https://example.com/dip-hook",
282
- });
283
-
284
- await client.priceAlerts.list();
285
- await client.priceAlerts.get(alert.id);
286
- await client.priceAlerts.update(alert.id, { name: "Renamed", is_active: false });
287
- await client.priceAlerts.delete(alert.id);
288
-
289
- // Event history (30-day retention)
290
- const { events } = await client.priceAlerts.events({ event_type: "dip", limit: 50 });
291
- ```
292
-
293
- 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.
294
-
295
- WebSocket: subscribe to channel `price_alert:events` user-scoped. Webhook: per-alert HMAC-SHA256 signed (same scheme as coordination alerts).
296
-
297
- ---
298
-
299
- #### `client.sniper.*` — Deshred Sniper Alerts *(new in 2.9)*
300
-
301
- **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.
302
-
303
- ```ts
304
- // Newest-first deshred deploy feed (PRO: elite/good · ULTRA: all tiers)
305
- const { deploys } = await client.sniper.recent({ limit: 50, min_bond_rate: 0.5 });
306
-
307
- // Audit one deployer's recent launches (ULTRA)
308
- await client.sniper.byDeployer("7dEx...4pQ8");
309
-
310
- // Custom watchlist — get deploys from only the deployers you track, any tier (ULTRA, max 50)
311
- await client.sniper.addToWatchlist({ wallets: ["7dEx...4pQ8", "9aBc...2zZ1"], label: "alpha devs" });
312
- await client.sniper.watchlist();
313
- const { deploys: tracked } = await client.sniper.recent({ watchlist: true });
314
- await client.sniper.removeFromWatchlist("7dEx...4pQ8");
315
- ```
316
-
317
- 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 or the `sniper:deploys` WebSocket channel. ~1–3% of detected deploys may abandon before settlement.
318
-
319
- **v2.20** each deploy also carries a `footprint` block (`SniperFootprint | null`): the slot-window snipe rollup for slots [-1..+3] around the deploy`buys`, `buyers`, `sol`, `supply_pct`, `sniper_wallet_buys`, `data_available`, `as_of`. `null` until the ~10-min settle window has passed (or when the mint is outside the pump.fun-pipeline write-gate) — absent, not zero.
320
-
321
- ---
322
-
323
- #### `client.kol.scoutLeaderboard(params?)` *(new in 2.8)*
324
-
325
- Scout leaderboard: top KOLs ranked by scout score, first-touch frequency, and swarm attraction rate. **ULTRA only.**
326
-
327
- ```ts
328
- const data = await client.kol.scoutLeaderboard({ limit: 20, scout_tier: "S", sort: "scout_score" });
329
- ```
330
-
331
- ---
332
-
333
- #### `client.kol.coordinationHistory(params?)` *(new in 2.8)*
334
-
335
- Historical coordination alert fires — past events with token, score, KOL count. **ULTRA only.**
336
-
337
- ```ts
338
- const data = await client.kol.coordinationHistory({ limit: 50, min_score: 70 });
339
- ```
340
-
341
- ---
342
-
343
- #### `client.token.kolConsensus(mint)` *(new in 2.8)*
344
-
345
- KOL consensus on a token: how many bought/sold, exit rate, net flow, median entry MC. **ULTRA** gets individual wallet arrays.
346
-
347
- ```ts
348
- const consensus = await client.token.kolConsensus("4sVahM4U8js62mQV58ABSkNRhf6Ztc7Xs2LXUznNpump");
349
- ```
350
-
351
- ---
352
-
353
- #### `client.token.peakHistory(mint)` *(new in 2.8)*
354
-
355
- Peak MC history: ATH, decline from peak, MC at bond and at 1h/6h/24h/7d after bond.
356
-
357
- ```ts
358
- const peak = await client.token.peakHistory("4sVahM4U8js62mQV58ABSkNRhf6Ztc7Xs2LXUznNpump");
359
- ```
360
-
361
- ---
362
-
363
- #### `client.kol.firstTouches(params?)` *(new in 2.2)*
364
-
365
- 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.
366
-
367
- **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](https://madeonsol.com/kol/scouts).
368
-
369
- ```ts
370
- // S-tier scouts on tokens younger than 1h
371
- const { events } = await client.kol.firstTouches({
372
- preset: "scout",
373
- min_scout_tier: "S",
374
- limit: 20,
375
- });
376
-
377
- for (const e of events) {
378
- console.log(e.first_kol.name, "scouted", e.token_symbol, `(scout_score=${e.first_kol.scout_score}%)`);
379
- }
380
- ```
381
-
382
- 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"`).
383
-
384
- > **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_touches` WebSocket channel (PRO+) or, on Ultra, create an HMAC-signed webhook subscription via `client.firstTouchSubscriptions.create({...})`.
385
-
386
- Returns: `FirstTouchesResponse`
387
-
388
- ---
389
-
390
- #### `client.firstTouchSubscriptions.*` *(Ultra)*
391
-
392
- Create push-delivery rules for first-touch events. Up to 10 active subscriptions per Ultra user.
393
-
394
- ```ts
395
- const { subscription, webhook_secret } = await client.firstTouchSubscriptions.create({
396
- name: "S-tier scouts on pump tokens",
397
- filters: { min_scout_tier: "S", mint_suffix: "pump" },
398
- delivery_mode: "webhook",
399
- webhook_url: "https://my.bot/hooks/scout",
400
- });
401
- // store webhook_secret — shown once
402
-
403
- await client.firstTouchSubscriptions.list();
404
- await client.firstTouchSubscriptions.update(subscription.id, { is_active: false });
405
- await client.firstTouchSubscriptions.delete(subscription.id);
406
- ```
407
-
408
- Same HMAC scheme as coordination alerts. WebSocket channel: `kol:first_touches`.
409
-
410
- ---
411
-
412
- #### `client.kol.token(mint)`
413
-
414
- KOL buy/sell activity for a specific token mint.
415
-
416
- ```ts
417
- const activity = await client.kol.token("EPjFW...");
418
- ```
419
-
420
- Returns: `KolTokenActivity`
421
-
422
- ---
423
-
424
- #### `client.kol.pnl(wallet, params?)`
425
-
426
- Deep per-wallet PnL breakdown with equity curve, risk metrics, and position history.
427
-
428
- ```ts
429
- const pnl = await client.kol.pnl("7xKX...", {
430
- period: "30d", // "7d" | "30d" | "90d" | "180d", default "30d"
431
- });
432
- // All tiers: summary + equity curve + closed positions
433
- // ULTRA: + open positions (tokens bought but not yet sold)
434
- ```
435
-
436
- Returns: `KolPnlResponse`
437
-
438
- ---
439
-
440
- #### `client.kol.trendingTokens(params?)`
441
-
442
- Tokens ranked by KOL buy volume across multiple time windows.
443
-
444
- ```ts
445
- const { tokens } = await client.kol.trendingTokens({
446
- period: "1h", // "5m" | "15m" | "30m" | "1h" | "4h" | "8h" | "12h", default "1h"
447
- min_kols: 2, // minimum distinct KOL buyers
448
- limit: 20, // 1–50, default 20
449
- });
450
- // Available on all tiers; ULTRA unlocks full KOL wallet addresses per token
451
- ```
452
-
453
- Returns: `KolTrendingTokensResponse`
454
-
455
- ---
456
-
457
- ### Alpha Wallet Intelligence — `client.alpha`
458
-
459
- #### `client.alpha.leaderboard(params?)`
460
-
461
- Leaderboard of 1M+ scored early-buyer wallets ranked by win rate, PnL, or ROI.
462
-
463
- ```ts
464
- const { wallets } = await client.alpha.leaderboard({
465
- period: "30d", // "7d" | "30d" | "90d", default "30d"
466
- sort: "win_rate", // "win_rate" | "pnl" | "roi"
467
- min_tokens: 5,
468
- exclude_bots: true,
469
- });
470
- // Up to 100 results on Free/Pro; ULTRA unlocks 500 + bot signals
471
- ```
472
-
473
- Returns: `AlphaLeaderboardResponse`
474
-
475
- ---
476
-
477
- #### `client.alpha.wallet(wallet)`
478
-
479
- Full profile for an alpha wallet including per-token history and bot signals. ULTRA only.
480
-
481
- ```ts
482
- const profile = await client.alpha.wallet("7xKX...");
483
- ```
484
-
485
- Returns: `AlphaWalletResponse`
486
-
487
- ---
488
-
489
- #### `client.alpha.linked(wallet)`
490
-
491
- Linked-wallet clustering — wallets that co-bought with this address within 2 seconds. ULTRA only.
492
-
493
- ```ts
494
- const { linked } = await client.alpha.linked("7xKX...");
495
- ```
496
-
497
- Returns: `AlphaLinkedResponse`
498
-
499
- ---
500
-
501
- #### `client.alpha.capTable(mint)`
502
-
503
- First buyers for a token enriched with historical win rates, PnL, and KOL identity. PRO/ULTRA.
504
-
505
- ```ts
506
- const { buyers } = await client.alpha.capTable("EPjFW...");
507
- ```
508
-
509
- Returns: `AlphaCapTableResponse`
510
-
511
- ---
512
-
513
- #### `client.alpha.buyerQuality(mint)`
514
-
515
- 0–100 cohort quality score based on the profile of a token's first buyers. All tiers. 5-minute cache.
516
-
517
- ```ts
518
- const { score } = await client.alpha.buyerQuality("EPjFW...");
519
- ```
520
-
521
- Returns: `AlphaBuyerQualityResponse`
522
-
523
- ---
524
-
525
- #### `client.alpha.risk(mint)`
526
-
527
- Transparent 0–100 token rug-risk/safety score (higher = riskier). Returns a `band` (`safe`/`caution`/`danger`), an explainable `factors[]` array that sums into `risk_score`, and the raw `inputs` (mint/freeze authority revocation, liquidity USD + liquidity-to-MC ratio, transfer fee bps, Token-2022 flag, burn detection, launch cohort SOL + size, deployer bond rate + total deployed, KOL signal, blacklist status). **v2.20:** `inputs` also carries `sniper_footprint` (`SniperFootprint | null`) — the slot-window snipe rollup (`buys`/`buyers`/`sol`/`supply_pct`/`sniper_wallet_buys`/`data_available`/`as_of`). Informational: it does not move the score; `null` when not yet computed. PRO/ULTRA — BASIC receives HTTP 403.
528
-
529
- ```ts
530
- const { risk_score, band, factors } = await client.alpha.risk("EPjFW...");
531
- if (band === "danger") return; // skip risky tokens
532
- ```
533
-
534
- Returns: `TokenRiskResponse`
535
-
536
- ---
537
-
538
- #### `client.alpha.bundle(mint)`
539
-
540
- Bundle-cohort holdings — the wallets that bought a token together (one atomic transaction, `bundle_kind: "atomic_tx"`, or the same slot, `"same_slot"`) and, headline first, how much of supply they still hold. The `bundle` summary block (`held_pct_of_supply`, `bundle_kind`, `wallet_count`, `held_ratio`, `fully_exited`, `buy_volume`, `tokens_held`) is returned on **every** tier. BASIC/TRADER get `wallets: []`; PRO adds the top-10 cohort wallets with flags (`held_ratio`, `has_sold`, `atomic`, `is_kol`); ULTRA returns the full cohort plus identity (`kol_name`, `win_rate`, `bot_confidence`) and per-wallet `tokens_held`.
541
-
542
- ```ts
543
- const { bundle, wallets } = await client.alpha.bundle("EPjFW...");
544
- if ((bundle.held_pct_of_supply ?? 0) > 0.2 && !bundle.fully_exited) return; // bundle still holds supply
545
- ```
546
-
547
- Returns: `TokenBundleResponse`
548
-
549
- ---
550
-
551
- #### `client.alpha.candles(mint, params?)`
552
-
553
- OHLCV candlestick time-series — the persisted price/MC trajectory, rolled up to any timeframe on read. **PRO**: OHLCV (last 30 days). **ULTRA**: + per-bar net flow (buy/sell volume, `net_volume_usd`, counts, MEV volume), liquidity delta, and full retained history. Params: `tf` (`1m`|`5m`|`15m`|`1h`|`4h`|`1d`, default `1h`), `limit` (1–1000, default 200), `from`/`to` (ISO8601). `net_flow_included` flags whether the ULTRA fields are populated.
554
-
555
- ```ts
556
- const { candles, net_flow_included } = await client.alpha.candles("EPjFW...", { tf: "5m", limit: 100 });
557
- const last = candles.at(-1);
558
- console.log(last.close, net_flow_included ? `net flow $${last.net_volume_usd}` : "(ULTRA for net flow)");
559
- ```
560
-
561
- Returns: `CandlesResponse`
562
-
563
- ---
564
-
565
- #### `client.alpha.tokenFlow(mint, params?)`
566
-
567
- Aggregated buy/sell flow for a token over a rolling window. **PRO+** (keyed). Params: `window` (`1h` default, or `24h`). Returns unique wallet/buyer/seller counts, buy/sell counts and SOL volumes, `net_sol` (`buy_sol − sell_sol`), and `trades_per_wallet`, plus the window `from` timestamp.
568
-
569
- ```ts
570
- const flow = await client.alpha.tokenFlow("EPjFW...", { window: "24h" });
571
- console.log(`${flow.unique_wallets} wallets · net ${flow.net_sol} SOL`);
572
- ```
573
-
574
- Returns: `TokenFlowResponse`
575
-
576
- ---
577
-
578
- #### `client.alpha.tokenPools(mint)`
579
-
580
- Per-venue liquidity map — every DEX pool a token trades in, each flagged live (`is_active`) or parked, with `liquidity_usd`, `last_price_sol`, `last_swap_at`, `dex`, `quote_mint`, and `amm_id`. The `summary` block rolls up `pool_count`/`active_pool_count`/`dex_count`, `dexes[]`, `total_liquidity_usd`, the `primary_pool`/`primary_dex`, and `top_pool_share_pct` (largest-pool concentration) — a fragmentation read on a token's liquidity. **PRO/ULTRA only** — BASIC receives HTTP 403.
581
-
582
- ```ts
583
- const { pools, summary } = await client.alpha.tokenPools("EPjFW...");
584
- console.log(`${summary.active_pool_count}/${summary.pool_count} live across ${summary.dex_count} DEXs · top pool ${summary.top_pool_share_pct}%`);
585
- ```
586
-
587
- Returns: `TokenPoolsResponse`
588
-
589
- ---
590
-
591
- #### `client.alpha.holders(mint)`
592
-
593
- Live holders, holder count + concentration (`GET /tokens/{mint}/holders`) — 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**; `capTable` is who bought first. **PRO+** — BASIC receives HTTP 403.
594
-
595
- - `concentration.holder_count` is **exact** (distinct non-zero owners minus excluded pools/curves/burns, at `slot`) and `null` only when the provider refused the census for a mega-cap mint then `source.method` is `"getTokenLargestAccounts"` (top-20 view) and `source.census_fallback_reason` is set. It is never estimated from trades.
596
- - `amount_raw` on every holder and excluded row is a raw u64 **string** — never a float; use `BigInt()`. `amount` is the UI-scaled convenience number.
597
- - Pools, bonding curves, burns and unattributed program accounts are **excluded** from the circulating denominator and listed in `excluded[]`, each named where possible: `reason` `pool` (+ `dex`, `pool_address`), `bonding_curve` (pump.fun / LaunchLab), `burn`, else `program_account`. The #1 raw account of a fresh memecoin is its own bonding curve. `concentration.pool_pct` / `burned_pct` / `program_pct` split them (over total supply).
598
- - Disclosure is tier-gated: **PRO** ranks 1–10, **ULTRA** 1–50, **BUSINESS** 1–100 (`disclosed` tells you your cap); `top1/top10/top20/top50/top100_share`, the cohort `*_pct` values and `holder_count` are computed over the full set and are identical on every tier. All shares are 0–100.
599
- - Each holder carries `labels[]` from MadeOnSol wallet intelligence (`deployer` / `kol` / `early_buyer` / `buyer` / `bundle` / `bot` / `dump_cluster`) plus `kol_name`, `early_buyer_rank`, `bot_confidence`, `historical_win_rate`. Empty labels = unknown to us, not verified clean.
600
- - Latency: fresh pump.fun mints <1 s; 200k550k-account tokens 611 s. While the upstream scan is still running the API answers **503** `error_kind: "holder_scan_in_progress"` with `retry_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 throw `MadeOnSolError` with `status === 503`; inspect `error.body`. Unknown mint: 404 `error_kind: "not_a_mint"`.
601
-
602
- ```ts
603
- import { MadeOnSolError } from "madeonsol";
604
-
605
- async function holders(mint: string) {
606
- for (;;) {
607
- try {
608
- return await client.alpha.holders(mint);
609
- } catch (e) {
610
- const body = e instanceof MadeOnSolError ? (e.body as { error_kind?: string; retry_after_seconds?: number }) : null;
611
- if (e instanceof MadeOnSolError && e.status === 503 && body?.error_kind === "holder_scan_in_progress") {
612
- await new Promise((r) => setTimeout(r, (body.retry_after_seconds ?? 20) * 1000)); // scan is cached — retry is instant
613
- continue;
614
- }
615
- throw e;
616
- }
617
- }
618
- }
619
-
620
- const { holders: top, concentration, excluded } = await holders("EPjFW...");
621
- console.log(`${concentration.holder_count} holders · top10 ${concentration.top10_share}% of circulating`);
622
- console.log(`bonding curve / pools hold ${concentration.pool_pct}% of supply (${excluded.length} excluded owners)`);
623
- console.log(top[0].owner, BigInt(top[0].amount_raw), top[0].labels);
624
- ```
625
-
626
- Returns: `TokenHoldersResponse` (`TokenHolder`, `TokenHoldersExcluded`, `TokenHoldersConcentration`, `TokenHoldersDeployer`, `TokenHoldersSource`, `TokenHolderLabel`, `TokenHolderExcludedReason`, `TokenHoldersMethod`)
627
-
628
- ---
629
-
630
- ### Wallet Tracker — `client.walletTracker`
631
-
632
- #### `client.walletTracker.watchlist()`
633
-
634
- List your tracked wallets and remaining capacity.
635
-
636
- ```ts
637
- const { wallets, capacity } = await client.walletTracker.watchlist();
638
- // capacity: { used, limit } — Free: 10, Pro: 50, Ultra: 100
639
- ```
640
-
641
- Returns: `WatchlistResponse`
642
-
643
- ---
644
-
645
- #### `client.walletTracker.addToWatchlist(wallet, params?)`
646
-
647
- Add a wallet to your watchlist. Tracking begins immediately.
648
-
649
- ```ts
650
- await client.walletTracker.addToWatchlist("7xKX...", { label: "whale" });
651
- ```
652
-
653
- ---
654
-
655
- #### `client.walletTracker.removeFromWatchlist(wallet)`
656
-
657
- Remove a wallet from your watchlist.
658
-
659
- ```ts
660
- await client.walletTracker.removeFromWatchlist("7xKX...");
661
- ```
662
-
663
- ---
664
-
665
- #### `client.walletTracker.updateLabel(wallet, label)`
666
-
667
- Update the label for a tracked wallet.
668
-
669
- ```ts
670
- await client.walletTracker.updateLabel("7xKX...", "smart money");
671
- ```
672
-
673
- ---
674
-
675
- #### `client.walletTracker.trades(params?)`
676
-
677
- Historical swap and transfer events for your watched wallets. 120-day retention.
678
-
679
- ```ts
680
- const { events } = await client.walletTracker.trades({
681
- wallet: "7xKX...", // filter by specific wallet
682
- action: "buy", // "buy" | "sell"
683
- event_type: "swap", // "swap" | "transfer"
684
- limit: 50,
685
- before: "2026-04-01T00:00:00Z", // ISO 8601 cursor
686
- });
687
- ```
688
-
689
- Returns: `WalletTrackerTradesResponse`
690
-
691
- ---
692
-
693
- #### `client.walletTracker.summary(params?)`
694
-
695
- Per-wallet stats across your watchlist: swap counts, SOL bought/sold, last event time.
696
-
697
- ```ts
698
- const { wallets } = await client.walletTracker.summary({
699
- period: "7d", // "24h" | "7d" | "30d", default "7d"
700
- wallet: "7xKX...", // optional: single wallet
701
- });
702
- ```
703
-
704
- Returns: `WalletTrackerSummaryResponse`
705
-
706
- ---
707
-
708
- ### Universal Wallet — `client.wallet` *(new in 2.7)*
709
-
710
- 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.
711
-
712
- **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.
713
-
714
- #### `client.wallet.stats(address)`
715
-
716
- 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. **v2.20** adds reputation flags to `flags`: `is_sniper`, `is_bundler` (lifetime), `is_dumper` (rolling 42d), and `dump_cluster` cohort stats — pump.fun-pipeline scoped, so `false` means "not observed", NOT verified clean. **v2.20 type fix:** `flags.bot_confidence` is a string enum (`"none" | "low" | "medium" | "high" | null`), not a number — the old `number | null` typing never matched a real value (the API returned `null` unconditionally due to a bug, now fixed).
717
-
718
- ```ts
719
- const { stats, flags, derived } = await client.wallet.stats("ASVz...ybJk");
720
- console.log(`${flags.kol_name ?? address}: ${stats?.total_trades} trades`);
721
- if (derived?.verdict) {
722
- console.log(`${derived.verdict.label}: ${derived.verdict.description}`);
723
- }
724
- if (derived?.biggest_miss) {
725
- console.log(`Biggest miss: ${derived.biggest_miss.token_symbol} — missed +${derived.biggest_miss.missed_sol.toFixed(1)} SOL`);
726
- }
727
- ```
728
-
729
- Returns: `WalletStatsResponse` (404 if the wallet has no trades and no flag-table presence).
730
-
731
- ---
732
-
733
- #### `client.wallet.pnl(address)`
734
-
735
- 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.
736
-
737
- ```ts
738
- const pnl = await client.wallet.pnl("ASVz...ybJk");
739
- console.log(`Realized: ${pnl.summary.realized_sol} SOL · Unrealized: ${pnl.summary.unrealized_sol} SOL`);
740
- console.log(`Win rate: ${(pnl.summary.win_rate! * 100).toFixed(1)}% · PF: ${pnl.summary.profit_factor}`);
741
- for (const c of pnl.closed_positions.slice(0, 5)) {
742
- const sign = c.pnl_sol > 0 ? "+" : "";
743
- console.log(` ${c.token_mint.slice(0,8)}… ${sign}${c.pnl_sol} SOL (${c.roi_pct}% ROI, ${c.hold_minutes}m hold)`);
744
- }
745
- ```
746
-
747
- Returns: `WalletPnlResponse`. Cache hits include `cache_hit: true` + `computed_at`; misses include `ttl_seconds`.
748
-
749
- ---
750
-
751
- #### `client.wallet.positions(address)`
752
-
753
- Open positions only — lighter slice of `pnl()`. Shares the same cache, so calling this right after `pnl()` is an immediate hit.
754
-
755
- ```ts
756
- const { positions } = await client.wallet.positions("ASVz...ybJk");
757
- for (const p of positions) {
758
- const u = p.unrealized_sol;
759
- console.log(` ${p.token_mint.slice(0,8)}… cost ${p.cost_basis_sol} SOL unrealized ${u ?? "—"} SOL (${p.unrealized_pct ?? "—"}%)`);
760
- }
761
- ```
762
-
763
- Returns: `WalletPositionsResponse`. Mints without a current price return `unrealized_sol: null` rather than fabricated zero.
764
-
765
- ---
766
-
767
- #### `client.wallet.holdings(address, params?)`
768
-
769
- Verified **current** on-chain holdings — reads the wallet's actual SPL + Token-2022 token accounts and SOL balance straight from chain, enriches each with our price/MC/name/symbol, and computes a `transfer_delta` (on-chain `amount` − trade-derived net position). A nonzero `transfer_delta` exposes tokens that arrived or left **without a swap** — airdrops, insider funding, wallet-hopping. Distinct from `positions()` (trade-derived FIFO): holdings is "what they actually hold right now". **ULTRA only.**
770
-
771
- ```ts
772
- const h = await client.wallet.holdings("ASVz...ybJk", { min_value_usd: 10 });
773
- console.log(`${h.summary.non_zero} tokens · $${h.summary.total_value_usd} · ${h.sol_balance} SOL`);
774
- for (const t of h.holdings) {
775
- const d = t.transfer_delta;
776
- const flag = d && d > 0 ? " ⬅ arrived without a swap" : "";
777
- console.log(` ${t.symbol ?? t.mint.slice(0,8)}… ${t.amount} ($${t.value_usd ?? "—"})${flag}`);
778
- }
779
- ```
780
-
781
- Params:
782
- - `limit` — 1-500, default 200
783
- - `min_value_usd` — number ≥0, default 0 (minimum USD value per holding to include)
784
-
785
- Returns: `WalletHoldingsResponse` — `holdings[]` (typed `Holding`), `sol_balance`, `summary` (`token_accounts` / `non_zero` / `returned` / `priced` / `total_value_usd` / `truncated`), `verified_at`, `trade_window_days`, `cache_hit`, `ttl_seconds`.
786
-
787
- ---
788
-
789
- #### `client.wallet.trades(address, params?)`
790
-
791
- Cursor-paginated raw trades. Default window is the last 90 days; override via `since` / `until` (Unix epoch seconds). Default limit 100, max 500.
792
-
793
- ```ts
794
- let cursor: string | undefined;
795
- while (true) {
796
- const page = await client.wallet.trades("ASVz...ybJk", { limit: 200, cursor, action: "buy" });
797
- for (const t of page.trades) processBuy(t);
798
- if (!page.has_more) break;
799
- cursor = page.next_cursor!;
800
- }
801
- ```
802
-
803
- Params:
804
- - `limit` — 1-500, default 100
805
- - `cursor` — from `next_cursor` of previous response
806
- - `action` — `"buy"` or `"sell"`
807
- - `token_mint` — filter to one token
808
- - `since` / `until` Unix epoch seconds (default last 90d)
809
-
810
- Returns: `WalletTradesResponse` with `trades[]` + `next_cursor` + `has_more` + `filters` echo.
811
-
812
- ---
813
-
814
- #### `client.wallet.batchClassify(wallets)` *(new in 2.20 — PRO+)*
815
-
816
- Bulk wallet reputation flags — 1–100 addresses in one request (`POST /wallet/batch/classify`). Each entry carries the same flag values as the `flags` block of `stats()`: `is_sniper`, `is_bundler`, `is_dumper`, `is_kol` + `kol_name`, `bot_confidence` (`"none"`/`"low"`/`"medium"`/`"high"` or null), and `dump_cluster` (`{ dump_cohorts, runner_cohorts, total_cohorts, as_of }` or null).
817
-
818
- **Semantics** flags are pump.fun-pipeline scoped: `false` means the behavior was **not observed** by our pipeline, NOT that the wallet is verified clean. `is_bundler` is a lifetime flag; `is_dumper` is a rolling 42-day window.
819
-
820
- ```ts
821
- const { wallets, as_of } = await client.wallet.batchClassify([buyerA, buyerB, buyerC]);
822
- for (const w of wallets) {
823
- const tags = [w.is_sniper && "sniper", w.is_bundler && "bundler", w.is_dumper && "dumper", w.is_kol && `KOL ${w.kol_name}`].filter(Boolean);
824
- console.log(`${w.address.slice(0, 8)}… ${tags.join(" · ") || "clean-ish (not observed)"} bot=${w.bot_confidence ?? "?"}`);
825
- }
826
- ```
827
-
828
- Returns: `WalletBatchClassifyResponse` — `{ wallets: WalletClassification[], count, as_of }`.
829
-
830
- ---
831
-
832
- ### Deployer Hunter — `client.deployer`
833
-
834
- #### `client.deployer.stats()`
835
-
836
- Global statistics across all tracked deployer wallets.
837
-
838
- ```ts
839
- const stats = await client.deployer.stats();
840
- console.log(stats.overall_bonding_rate); // e.g. 0.043
841
- ```
842
-
843
- Returns: `DeployerStats`
844
-
845
- ---
846
-
847
- #### `client.deployer.leaderboard(params?)`
848
-
849
- Deployers ranked by bonding rate or recent performance.
850
-
851
- ```ts
852
- const { deployers } = await client.deployer.leaderboard({
853
- tier: "elite", // "elite" | "good" | "moderate" | "rising" | "cold"
854
- sort: "bonding_rate", // "bonding_rate" | "recent_bond_rate" | "total_bonded" | "last_deploy_at"
855
- limit: 20, // 1–50, default 20
856
- offset: 0,
857
- });
858
- ```
859
-
860
- Returns: `DeployerLeaderboardResponse`
861
-
862
- ---
863
-
864
- #### `client.deployer.profile(wallet)`
865
-
866
- Full profile for a single deployer wallet.
867
-
868
- ```ts
869
- const deployer = await client.deployer.profile("3xAB...");
870
- console.log(deployer.tier, deployer.bonding_rate);
871
- ```
872
-
873
- Returns: `DeployerProfile`
874
-
875
- ---
876
-
877
- #### `client.deployer.tokens(wallet, params?)`
878
-
879
- All tokens deployed by a specific wallet.
880
-
881
- ```ts
882
- const { tokens } = await client.deployer.tokens("3xAB...", {
883
- limit: 20,
884
- offset: 0,
885
- });
886
- ```
887
-
888
- Returns: `DeployerTokensResponse`
889
-
890
- ---
891
-
892
- #### `client.deployer.alerts(params?)`
893
-
894
- Real-time deploy alerts — fired when a tracked deployer launches a new token.
895
-
896
- ```ts
897
- const { alerts } = await client.deployer.alerts({
898
- since: "2025-01-01T00:00:00Z", // ISO 8601
899
- limit: 20,
900
- tier: "elite", // "elite" | "good" | "moderate" | "rising" | "cold"
901
- offset: 0,
902
- });
903
- ```
904
-
905
- Each `DeployerAlert` carries the deploy details plus `deployer_sol_balance` — the deployer wallet's SOL balance at alert time (`number | null` when unknown).
906
-
907
- Returns: `DeployerAlertsResponse`
908
-
909
- ---
910
-
911
- #### `client.deployer.alertStats(params?)`
912
-
913
- Aggregated alert statistics by tier.
914
-
915
- ```ts
916
- const stats = await client.deployer.alertStats({ period: "7d" });
917
- // "7d" | "30d" | "all", default "all"
918
- ```
919
-
920
- Returns: `DeployerAlertStats`
921
-
922
- ---
923
-
924
- #### `client.deployer.bestTokens(params?)`
925
-
926
- Top-performing tokens from tracked deployers by peak market cap.
927
-
928
- ```ts
929
- const { tokens } = await client.deployer.bestTokens({
930
- period: "7d", // "7d" | "30d" | "all", default "7d"
931
- limit: 5, // 1–20, default 5
932
- });
933
- ```
934
-
935
- Returns: `BestTokensResponse`
936
-
937
- ---
938
-
939
- #### `client.deployer.recentBonds(params?)`
940
-
941
- Most recently bonded tokens from tracked deployers.
942
-
943
- ```ts
944
- const { bonds } = await client.deployer.recentBonds({ limit: 20 });
945
- ```
946
-
947
- Returns: `RecentBondsResponse`
948
-
949
- ---
950
-
951
- #### `client.deployer.deployerHistory(wallet, opts?)`
952
-
953
- Daily reputation time-series for a deployer — one snapshot per `date` capturing the `tier`, `is_tracked` flag, `total_deployed`/`total_bonded`, `bonding_rate`, `recent_bond_rate`, `avg_peak_mc`, and `best_token_peak_mc` that were true on that day. Backtest "was this deployer elite when it launched token X?" without look-ahead bias. `opts.limit` is 1–365 daily snapshots (default 90).
954
-
955
- ```ts
956
- const { is_deployer, snapshots } = await client.deployer.deployerHistory("3xAB...", { limit: 180 });
957
- const onLaunch = snapshots.find((s) => s.date === "2025-01-01");
958
- console.log(onLaunch?.tier, onLaunch?.bonding_rate);
959
- ```
960
-
961
- Returns: `DeployerHistoryResponse`
962
-
963
- ---
964
-
965
- ### Token Intelligence — `client.token`
966
-
967
- Per-mint snapshots (price, MC, volume, deployer rep, KOL activity, blacklist flags, **v1.7 velocity windows + MEV-share**) and a filtered directory.
968
-
969
- #### `client.token.get(mint)`
970
-
971
- Comprehensive per-mint snapshot in one call. **ULTRA** also returns individual KOL wallet addresses in `top_buyers[]`.
972
-
973
- ```ts
974
- const { token } = await client.token.get("So11111111111111111111111111111111111111112");
975
- console.log(token.price_usd, token.market_cap);
976
- console.log(token.mc_change_pct?.["1h"]); // v1.7
977
- console.log(token.mev_volume_pct?.["1h"]); // v1.7
978
- ```
979
-
980
- Invalid mints return a 400 with `code: "invalid_mint"`, `reason`, `received_length`, `example`, and `docs` URL — no trial and error.
981
-
982
- 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). **New in 2.12:** also returns `liquidity_to_mc_ratio` (liquidity_usd / market_cap), `launch_cohort_sol` (total SOL spent by the first-20 buyers), and `launch_cohort_size` (count of first-20 buyers, 0–20).
983
-
984
- #### `client.token.batch(mints)`
985
-
986
- Batch lookup up to 50 mints in one round-trip. ~10–20× cheaper than N sequential calls. Each item returns the same shape as `get()` — including `liquidity_to_mc_ratio`, `launch_cohort_sol`, and `launch_cohort_size` *(new in 2.12)*.
987
-
988
- ```ts
989
- const { tokens } = await client.token.batch(["mint1", "mint2", "mint3"]);
990
- ```
991
-
992
- Returns: `TokenBatchResponse`
993
-
994
- #### `client.token.list(params?)` *(new in 2.6 — PRO+)*
995
-
996
- 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.
997
-
998
- **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`, `deployer_tier` (`elite`/`good`/`moderate`/`rising`/`cold`/`unranked`), `min_liq_mc_ratio`, `max_liq_mc_ratio`.
999
-
1000
- **Computed post-filters** (over-fetches ): `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`.
1001
-
1002
- **Sort** (`sort`): `mc_desc` (default), `mc_asc`, `last_trade_desc`, `liquidity_desc`, `cumulative_volume_desc`, plus *(new in 2.15)* the momentum sorts `mc_change_5m_desc`, `mc_change_1h_desc`, `volume_1h_desc`, and `trending` (composite recent-volume × positive-momentum rank, DB-native paginates correctly with no over-fetch).
1003
-
1004
- Each item in `tokens[]` includes `liquidity_to_mc_ratio` and `deployer_tier` *(new in 2.12)*.
1005
-
1006
- ```ts
1007
- // Momentum scanner: liquid mints up >20% in 1h, low bot share
1008
- const { tokens, pagination } = await client.token.list({
1009
- min_liq: 10000,
1010
- min_volume_1h_usd: 5000,
1011
- max_mev_share_pct: 60,
1012
- mc_change_1h_min_pct: 20,
1013
- sort: "mc_desc",
1014
- limit: 50,
1015
- });
1016
-
1017
- // Cleanest filter for a sane "top by MC" feed
1018
- const { tokens } = await client.token.list({
1019
- min_liq: 25000,
1020
- active_h: 1,
1021
- authority_revoked: true,
1022
- sort: "mc_desc",
1023
- });
1024
- ```
1025
-
1026
- Returns: `TokenListResponse` (with `tokens[]`, `pagination`, `filters` echo)
1027
-
1028
- #### `client.token.almostBonded(params?)` *(new in 2.15 PRO+)*
1029
-
1030
- Pre-bond pump.fun tokens approaching graduation, ranked by **velocity** (Δprogress/min) "95% and accelerating" beats "92% stalled". Each token is enriched with its deployer's reputation tier.
1031
-
1032
- **Params** (all optional): `min_progress` (default 80), `max_progress` (default 99.99), `min_velocity_pct_per_min`, `max_age_minutes`, `deployer_tier` (`elite`/`good`/`moderate`/`rising`/`cold`/`unranked`), `authority_revoked`, `min_liq`, `sort` (`velocity_desc` default / `progress_desc` / `eta_asc`), `limit` (1–100, default 50).
1033
-
1034
- Each item in `tokens[]`: `mint`, `symbol`, `name`, `progress_pct`, `velocity_pct_per_min` (null until a 5m snapshot exists), `eta_minutes` (linear projection), `stalled`, `real_sol_reserves`, `market_cap_usd`, `liquidity_usd`, `authorities_revoked`, `deployer_tier`, `age_minutes`.
1035
-
1036
- ```ts
1037
- // Tokens >90% bonded, accelerating, from a credible deployer — soonest first
1038
- const { tokens } = await client.token.almostBonded({
1039
- min_progress: 90,
1040
- min_velocity_pct_per_min: 0.5,
1041
- deployer_tier: "elite",
1042
- sort: "eta_asc",
1043
- limit: 25,
1044
- });
1045
- ```
1046
-
1047
- Returns: `AlmostBondedResponse` (with `tokens[]`, `filters`, `returned`, `note`)
1048
-
1049
- #### `client.token.batchBuyerQuality(mints)`
1050
-
1051
- Batch buyer-quality scoring for up to 50 mints. Shares the same 5-minute LRU cache as `client.alpha.buyerQuality(mint)`.
1052
-
1053
- Returns: `AlphaBuyerQualityBatchResponse`
1054
-
1055
- #### `client.token.batchRisk(mints)` *(new in 2.17 — PRO+)*
1056
-
1057
- Batch token risk scoring for up to 50 mints in a single request that **counts as 1** against your quota — each item is the same transparent 0–100 rug-risk result as `client.alpha.risk(mint)` (with `band`, `factors[]`, `inputs`, `score_version`, `as_of`). Untracked mints come back as `{ mint, error: "not_tracked" }` without failing the batch. `tokens` preserves de-duplicated input order; `count` is the number of unique mints. **PRO/ULTRA only.**
1058
-
1059
- ```ts
1060
- const { tokens, count } = await client.token.batchRisk(["mint1", "mint2", "mint3"]);
1061
- for (const t of tokens) {
1062
- if ("error" in t) continue; // untracked / per-mint failure
1063
- if (t.band === "danger") console.log(`skip ${t.mint} (${t.risk_score})`);
1064
- }
1065
- ```
1066
-
1067
- Returns: `TokenRiskBatchResponse` — `{ tokens: (TokenRiskResponse | TokenRiskBatchError)[], count }`
1068
-
1069
- ---
1070
-
1071
- #### `client.token.trades(mint, params?)` *(new in 2.20 — PRO+)*
1072
-
1073
- Mint-scoped trade tape — every captured trade for a token, cursor-paginated newest first (`GET /tokens/{mint}/trades`). Each trade carries `tx_signature`, `wallet_address`, `action`, `sol_amount`, `token_amount`, `price_sol`/`price_usd`, `early_buyer_rank`, `slot`, `block_time`, `traded_at`. Unlike `client.wallet.trades()` (90-day default), the default window here is the **full history**.
1074
-
1075
- **Coverage honesty**the tape starts 2026-04-12 and is pump.fun-pipeline scoped; the `coverage` block (`history_start`, `scope`) makes both visible on every response. Trades outside that pipeline are not on the tape.
1076
-
1077
- ```ts
1078
- let cursor: string | undefined;
1079
- while (true) {
1080
- const page = await client.token.trades(mint, { limit: 500, cursor, action: "buy" });
1081
- for (const t of page.trades) processBuy(t);
1082
- if (!page.has_more) break;
1083
- cursor = page.next_cursor!;
1084
- }
1085
- ```
1086
-
1087
- Params:
1088
- - `limit` — 1-500, default 100
1089
- - `cursor` — from `next_cursor` of previous response
1090
- - `action` — `"buy"` or `"sell"`
1091
- - `wallet` — filter to one wallet address
1092
- - `since` / `until` Unix epoch seconds (default: full history)
1093
-
1094
- Returns: `TokenTradesResponse` with `trades[]` + `next_cursor` + `has_more` + `filters` echo + `coverage`.
1095
-
1096
- ---
1097
-
1098
- #### `client.token.locks(mint, params?)` *(new in 2.25 — PRO+)*
1099
-
1100
- Token locks & vesting on a mint (`GET /tokens/{mint}/locks`) — every on-chain **Streamflow** stream, **Jupiter Lock** vesting escrow and **Bonfida** token-vesting contract, decoded from the locker programs' account state. Each row carries the schedule (`start_at` / `cliff_at` / `period_seconds` / `end_at`, `cliff_amount`, `amount_per_period`), the terms (`cancelable_by_sender` — funds are locked against the *recipient*, not the locker; `cancelable_by_recipient`, `transferable`, `can_topup`) and a **live-derived** view computed at request time: `locked_raw` (still locked now), `unlocked`, `withdrawn`, `claimable`, `status` (active / completed / cancelled / closed) and `next_unlock` (cliff | period | final | tranche). `summary` rolls up `lock_count`, `active_count`, `by_program` / `by_kind`, `distinct_lockers`, locked / deposited totals (raw + ui + usd + % of supply), `unlocking_7d_*` / `unlocking_30d_*`, the nearest `next_unlock` and `active_cancelable_by_sender`. **PRO+** — BASIC receives HTTP 403.
1101
-
1102
- - `*_raw` amounts are base-unit **strings** — never floats; use `BigInt()`. `amount` / `locked` / `*_usd` / `*_pct_of_supply` are `null` when decimals or price are unknown (`token.facts_resolved`).
1103
- - **LP locks are NOT included** — this is token / vesting locks only.
1104
- - `status` / `program` narrow `locks[]` only; `summary` always covers every contract on the mint (`summary.complete` is false past 5000 contracts totals then cover the newest 5000).
1105
- - `created_at_estimated: true` marks a backfilled Jupiter Lock row with no on-chain creation time.
1106
-
1107
- ```ts
1108
- const { summary, locks } = await client.token.locks(mint, { status: "active" });
1109
- console.log(`${summary.locked_pct_of_supply}% of supply locked · ${summary.unlocking_7d_usd} USD unlocks in 7d`);
1110
- console.log(`${summary.active_cancelable_by_sender} active locks the locker can still cancel`);
1111
- for (const l of locks) console.log(l.program, l.kind, BigInt(l.locked_raw), "until", l.end_at, l.cancelable_by_sender ? "(cancelable)" : "");
1112
- ```
1113
-
1114
- Params: `status` (active | completed | cancelled | closed), `program` (streamflow | jupiter_lock | bonfida_vesting), `limit` (1–500, default 200).
1115
-
1116
- Returns: `TokenLocksResponse` (`TokenLock`, `TokenLocksSummary`, `TokenLockNextUnlock`, `TokenLockToken`, `TokenLockProgram`, `TokenLockKind`, `TokenLockStatus`, `TokenUnlockEventKind`)
1117
-
1118
- ---
1119
-
1120
- #### `client.token.locksFeed(params?)` *(new in 2.25 — PRO+)*
1121
-
1122
- Cross-token feed of **new** lock / vesting contracts, newest first (`GET /tokens/locks`) — who just locked tokens, of what mint, how much, until when. Rows are the same live-derived contract as `locks()` plus `token` (`symbol`, `decimals`, `price_usd`, `market_cap_usd`). Poll with `since = pagination.next_since`, page back with `before = pagination.next_before`, or subscribe to the **`token:locks`** WS channel (event `token:lock`) for a push the moment the contract lands on-chain. `min_usd` / `min_pct_of_supply` / `status` post-filter (×4 over-fetch, so a page may be shorter than `limit`). Backfilled Jupiter Lock rows are excluded unless `include_estimated: true`. **LP locks are NOT included.** **PRO+**.
1123
-
1124
- ```ts
1125
- let since: string | undefined;
1126
- for (;;) {
1127
- const page = await client.token.locksFeed({ since, min_usd: 10_000 });
1128
- for (const l of page.locks) console.log(l.token.symbol, l.amount_usd, "USD locked until", l.end_at, "by", l.sender);
1129
- since = page.pagination.next_since ?? since;
1130
- await new Promise((r) => setTimeout(r, 30_000));
1131
- }
1132
- ```
1133
-
1134
- Params: `since` / `before` (ISO date-time cursors), `mint`, `sender`, `recipient`, `program`, `kind` (lock | vesting), `status`, `min_usd`, `min_pct_of_supply` (0–100), `include_estimated` (boolean), `limit` (1–100, default 50).
1135
-
1136
- Returns: `TokenLocksFeedResponse` (`TokenLockFeedEntry`, `TokenFeedPagination`, `TokenFeedStreamPointer`)
1137
-
1138
- ---
1139
-
1140
- #### `client.token.unlocks(params?)` *(new in 2.25 — PRO+)*
1141
-
1142
- Upcoming **unlock events** across all active lock / vesting contracts inside a window (`GET /tokens/unlocks`) — which tokens have locked supply hitting the market, how much, from whose lock. One entry per active contract = its **next** unlock event in the window (`event`: cliff | period | final | tranche) with `unlock_at` / `in_seconds` / `amount_*`, plus `window_amount_*` = that contract's total release over the whole window, the mint's `token` facts and the parent `lock` (subset of the `locks()` row). Continuous per-second streams (Streamflow payroll) contribute only their cliff / final events. **LP locks are NOT included.** **PRO+**.
1143
-
1144
- ```ts
1145
- const { window, unlocks } = await client.token.unlocks({ within: "24h", sort: "largest_usd", min_usd: 50_000 });
1146
- console.log(window.from, "→", window.to);
1147
- for (const u of unlocks) console.log(u.token.symbol, u.event, u.amount_usd, "USD in", u.in_seconds, "s —", u.lock.program, u.lock.sender);
1148
- ```
1149
-
1150
- Params: `within` (1h | 6h | 24h | 3d | 7d (default) | 14d | 30d | 90d), `mint`, `program`, `kind`, `min_usd`, `min_pct_of_supply`, `sort` (soonest (default) | largest_usd | largest_pct), `limit` (1–200, default 50).
1151
-
1152
- Returns: `TokenUnlocksResponse` (`TokenUnlockEvent`, `TokenUnlocksWithin`)
1153
-
1154
- ---
1155
-
1156
- #### `client.token.feeShares(mint)` *(new in 2.25 — PRO+)*
1157
-
1158
- pump.fun **creator-fee sharing** on a coin (`GET /tokens/{mint}/fee-shares`) — who its creator fees are redirected to. Decodes the on-chain `SharingConfig` (pump_fees PDA `["sharing-config", mint]`): `admin`, `status`, each shareholder's `share_bps` / `share_pct` with `is_admin` and `is_social_pda` (a SocialFeePda holds fees earmarked for a platform identity — `social.platform` 2 = X, `social.user_id` is the platform-native numeric id, **not** the handle with `lifetime_claimed`), `redirected_bps` (share going to non-admin addresses), `social_bps` and `is_default` (100% to the creator — a real answer: pump creates one config per coin). Plus `distributions` (every `distribute_creator_fees` payout, pro-rata per shareholder; per-recipient `received_*` totals; `past_recipients` no longer in the split), `history` (config created / updated / reset, creator transferred) and `recent_distributions`. `config.source` is `"stream"` (our table — only non-default configs are stored) or `"chain"` (live PDA read; `config_error` set and `config` null if every RPC endpoint failed). Amounts are quote base units (SOL lamports unless a stable-quoted coin) as **strings**. **Event / distribution history starts 2026-08-17.** **PRO+**.
1159
-
1160
- ```ts
1161
- const fs = await client.token.feeShares(mint);
1162
- if (fs.config?.is_default) console.log("100% of creator fees go to the creator");
1163
- else for (const s of fs.config?.shareholders ?? []) console.log(s.address, s.share_pct, "%", s.social?.platform_label ?? "", "received", s.received_usd, "USD");
1164
- console.log(fs.distributions.count, "distributions,", fs.distributions.total_usd, "USD since 2026-08-17");
1165
- ```
1166
-
1167
- Returns: `TokenFeeSharesResponse` (`TokenFeeSharingConfig`, `TokenFeeShareholder`, `TokenFeeSocialIdentity`, `TokenFeeShareHistoryEntry`, `TokenFeeDistribution`, `TokenFeeShareEntry`)
1168
-
1169
- ---
1170
-
1171
- #### `client.token.feeClaims(params?)` *(new in 2.25 — PRO+)*
1172
-
1173
- pump.fun **fee-event feed**, newest first (`GET /tokens/fee-claims`). `type`s: `distribution` (creator fees paid out pro-rata to the SharingConfig shareholders — fees redirected to others — with `payouts[]` per address), `social_claim` (fees earmarked for a platform identity — 2 = X claimed to a `recipient` wallet), `shares_created` / `shares_updated` / `shares_reset` (config changes), `creator_transferred`, and `creator_claim` (the plain creator vault claim — per creator, carries **no mint**; excluded unless you ask for it via `type`). Default 100%-to-creator configs and zero-amount distributions are not stored. Poll with `since = pagination.next_since` or subscribe to the **`token:fee_claims`** WS channel (event `token:fee_claim`). Amounts are quote base units as **strings** + `amount` / `amount_usd`. **History starts 2026-08-17.** **PRO+**.
1174
-
1175
- ```ts
1176
- const { events } = await client.token.feeClaims({ type: "distribution,social_claim", min_sol: 1 });
1177
- for (const e of events) console.log(e.type, e.mint, e.amount, e.quote, e.recipient ?? `${e.payouts?.length} payouts`);
1178
- ```
1179
-
1180
- Params: `type` (comma list), `mint`, `recipient`, `actor`, `social_platform` (2 = X), `social_user_id`, `min_sol`, `since` / `before` (ISO date-time cursors), `limit` (1–100, default 50).
1181
-
1182
- Returns: `TokenFeeClaimsResponse` (`TokenFeeClaimEvent`, `TokenFeePayout`, `TokenFeeEventType`, `TokenFeedPagination`, `TokenFeedStreamPointer`)
1183
-
1184
- ---
1185
-
1186
- #### `client.token.surges(params?)` *(new in 2.26 — PRO+)*
1187
-
1188
- Token **momentum fires**, newest first (`GET /tokens/surges`). Two kinds. **`surge`** — a token < 30 min old whose market cap runs hard vs its *launch* MC: tier `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 token can go straight to breakout). 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. **PRO+** — BASIC receives HTTP 403.
1189
-
1190
- Every row carries `tape` (buys / sells / volume since birth or revival; `tape.source` = `candles` or `wallet_trades`, `tape.available: false` with nulls while no tape covers the window yet; `unique_buyers` / `trades_per_wallet` only when the mint is in `token_trades` coverage `wallet_data_available: false` otherwise, **never inferred zero**), `kol` (tracked-KOL buyers + names), `early_buyers` (first-20 cohort: bundled, cohort SOL, sold, sniper wallets), `deployer` (tier, bonding / runner rate, labeled tokens) and **`risk_flags[]`** — the honest half (thresholds echoed in `definitions.risk_flags`). Rows 65 min old carry `outcome` (`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: true` adds per-(kind, tier) hit-rates over `days` (`up_1h_pct`, `median_peak_multiple`, `doubled_1h_pct`) out-of-sample by construction, the fire is recorded before the outcome exists. The live thresholds are echoed in `definitions` (read from the engine, so they cannot drift from what fires). Poll with `since = pagination.next_since`, or subscribe to the **`token:surges`** WS channel (events `token:surge` / `token:revival` the same object minus `outcome`).
1191
-
1192
- - Nearly every scalar is `| null` — null means unknown, never zero. `tier` is null on revivals; `dormant_hours` / `prev_mc_usd` / `mc_vs_prev_multiple` are null on surges; `baseline_*` / `mc_multiple` / `mc_change_3m_pct` are null on revivals.
1193
- - `launchpad` is the venue at *birth*, `primary_dex` where it trades at fire time — a pump token that graduated inside its first 10 min is `pumpfun` / `pumpswap`.
1194
- - `tier` with `kind: "revival"` is a 400; an unknown flag in `exclude_flags` is a 400 with `known_flags[]`.
1195
-
1196
- ```ts
1197
- const { events, stats } = await client.token.surges({ kind: "surge", tier: "strong", exclude_flags: ["bundled_launch", "sniper_heavy"], stats: true });
1198
- for (const e of events) {
1199
- console.log(e.symbol, `$${e.market_cap_usd}`, `${e.mc_multiple}× launch`, e.tape.buys, "buys /", e.tape.unique_buyers ?? "n/a", "buyers", e.risk_flags, e.outcome ? `${e.outcome.peak_1h_multiple}× peak in 1h` : "outcome pending");
1200
- }
1201
- for (const r of stats?.rows ?? []) console.log(r.kind, r.tier, `${r.up_1h_pct}% up after 1h`, "median peak", r.median_peak_multiple, `(${r.with_outcome} fires)`);
1202
-
1203
- // live
1204
- const stream = await client.stream.connect();
1205
- stream.subscribe(["token:surges"], { kinds: ["surge"], tiers: ["strong", "breakout"], exclude_flags: ["bundled_launch"], min_mc_usd: 30_000 } satisfies TokenSurgesSubscribeFilters);
1206
- stream.on("token:surge", (d) => { const e = d as TokenSurgeStreamEvent; console.log(e.tier, e.symbol, e.market_cap_usd, e.risk_flags); });
1207
- stream.on("token:revival", (d) => { const e = d as TokenSurgeStreamEvent; console.log("revived after", e.dormant_hours, "h", e.symbol, e.mc_vs_prev_multiple); });
1208
- ```
1209
-
1210
- Params: `kind` (surge | revival), `tier` (early | strong | breakout — surge only), `mint`, `since` / `before` (ISO date-time cursors), `min_mc_usd` / `max_mc_usd`, `min_buys`, `launchpad`, `deployer_tier` (elite | good | moderate | rising | cold | unranked), `exclude_flags` (array or comma list), `only_clean` (boolean), `stats` (boolean), `days` (1–30, default 7), `limit` (1–200, default 50).
1211
-
1212
- Returns: `TokenSurgesResponse` (`TokenSurgeEvent`, `TokenSurgeStreamEvent`, `TokenSurgeTape`, `TokenSurgeKol`, `TokenSurgeEarlyBuyers`, `TokenSurgeDeployer`, `TokenSurgeOutcome`, `TokenSurgeStats`, `TokenSurgeStatsRow`, `TokenSurgesFilters`, `TokenSurgeDefinitions`, `TokenSurgeKind`, `TokenSurgeTier`, `TokenSurgeRiskFlag`, `TokenSurgeDeployerTier`, `TokenSurgesSubscribeFilters`, `TokenFeedPagination`, `TokenFeedStreamPointer`)
1213
-
1214
- ---
1215
-
1216
- ### Account — `client.me()` *(new in 2.6)*
1217
-
1218
- 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).
1219
-
1220
- ```ts
1221
- const me = await client.me();
1222
- console.log(`${me.tier}: ${me.quota.daily.remaining}/${me.quota.daily.limit} req left today`);
1223
- console.log(`Webhooks: ${me.features.webhooks.used}/${me.features.webhooks.limit}`);
1224
- console.log(`Copy-trade wallets: ${me.features.copytrade_wallets.used}/${me.features.copytrade_wallets.limit}`);
1225
-
1226
- if (me.quota.daily.remaining < 100) {
1227
- // self-throttle
1228
- }
1229
- ```
1230
-
1231
- Returns: `MeResponse`
1232
-
1233
- ---
1234
-
1235
- ### Signal Performance — `client.getSignalPerformance(name)` *(new in 2.12)*
1236
-
1237
- Performance stats for a named signal: hit rate, precision, sample count, and lookback window.
1238
-
1239
- ```ts
1240
- const perf = await client.getSignalPerformance("kol_coordination");
1241
- console.log(perf.precision, perf.hit_rate);
1242
- ```
1243
-
1244
- Params: `name` — signal name (e.g. `"kol_coordination"`, `"first_touch_scout"`, `"deployer_elite"`).
1245
-
1246
- Returns: `Promise<unknown>`shape varies by signal name; see `/api-docs` for the full schema.
1247
-
1248
- ---
1249
-
1250
- ### Tool Directory — `client.tools`
1251
-
1252
- #### `client.tools.search(params?)`
1253
-
1254
- Search 950+ Solana tools indexed on MadeOnSol.
1255
-
1256
- ```ts
1257
- const { tools, count } = await client.tools.search({
1258
- q: "trading bot", // full-text search
1259
- category: "trading", // category slug filter
1260
- limit: 20, // 1–50, default 20
1261
- });
1262
- ```
1263
-
1264
- Returns: `ToolsSearchResponse`
1265
-
1266
- ---
1267
-
1268
- ### WebSocket Streaming — `client.stream`
1269
-
1270
- #### `client.stream.getToken()`
1271
-
1272
- Issue your 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.
1273
-
1274
- Stream tokens **never expire** (since 2.25.1): the same token comes back on every call until your subscription lapses or you pass `{ rotate: true }` to replace it (the previous value keeps working for 60 s). Send it as `Authorization: Bearer <token>` on the WebSocket handshake (`?token=` still works). A `4001` close means "mint again", never a timer.
1275
-
1276
- ```ts
1277
- const token = await client.stream.getToken();
1278
- console.log(token.ws_url); // wss://madeonsol.com/ws/v1/stream
1279
- console.log(token.dex_ws_url); // wss://madeonsol.com/ws/v1/dex-stream (Ultra only)
1280
- console.log(token.expires_at); // null — never expires
1281
- const fresh = await client.stream.getToken({ rotate: true }); // replace it
1282
- ```
1283
-
1284
- Returns: `StreamToken` — `{ token, expires_at: null, next_refresh_at: null, rotated, lifetime, ws_url, dex_ws_url?, usage }`
1285
-
1286
- #### `client.stream.connect()` *(new in 2.10)*
1287
-
1288
- Open a **managed** stream — token fetch (the token never expires; `getToken()` is called on every (re)connect), auto-reconnect (backoff + jitter), heartbeat liveness, and typed events are handled for you. No need to touch `getToken()` or `ws` directly.
1289
-
1290
- ```ts
1291
- const stream = client.stream.connect();
1292
- stream.on("kol:trade", (t) => console.log(t.token_symbol, t.action));
1293
- stream.on("deployer:alert", (a) => console.log("new deploy", a.token_mint));
1294
- stream.subscribe(["kol:trades", "deployer:alerts"]);
1295
- // stream.unsubscribe([...]) / stream.close() when done
1296
- ```
1297
-
1298
- Channels: `kol:trades`, `kol:coordination`, `kol:first_touches`, `deployer:alerts`, `wallet_tracker:events`, `copytrade:signals`, `price_alert:events`, `sniper:deploys`, `token:graduations` (every pump.fun graduation in real time, tracked deployer or not — typed `GraduationEvent`), `token:locks` (**new 2.25** — event `token:lock` for every NEW Streamflow / Jupiter Lock / Bonfida lock or vesting contract, typed `TokenLockStreamEvent`; PRO+; updates are not pushed — poll `client.token.locks()`), `token:fee_claims` (**new 2.25** — event `token:fee_claim` for every pump.fun fee event: distributions, social-handle claims, config changes, typed `TokenFeeClaimStreamEvent`; PRO+), `token:surges` (**new 2.26** — events `token:surge` / `token:revival` the moment a momentum fire is confirmed, typed `TokenSurgeStreamEvent` with `tape` / `kol` / `early_buyers` / `deployer` / `risk_flags[]`; server-side filters `kinds[]`, `tiers[]`, `launchpads[]`, `exclude_flags[]`, `min_mc_usd` / `max_mc_usd`, `deployer_tier[]` — typed `TokenSurgesSubscribeFilters`; PRO+; the +1 h `outcome` is REST-only — poll `client.token.surges()`). Lifecycle: `open`, `close`, `reconnect`, `heartbeat`, `error`. Node 22+ uses the global `WebSocket`; on Node < 22 also `npm i ws`.
1299
-
1300
- #### `client.stream.sessions()` / `client.stream.deleteSession(id)` *(new in 2.17 — PRO+)*
1301
-
1302
- Audit and evict your **live** WebSocket sessions across the KOL/deployer (`ws-streaming`) and all-DEX (`dex-stream`) services. `sessions()` lists each open connection; `deleteSession(id)` force-closes onehandy for freeing a connection slot held by a ghost/stale socket after a network drop.
1303
-
1304
- ```ts
1305
- const { sessions, count } = await client.stream.sessions();
1306
- for (const s of sessions) {
1307
- console.log(s.id, s.service, s.channels, s.messages_sent);
1308
- }
1309
-
1310
- // Kill a stale session to free a slot
1311
- const { evicted } = await client.stream.deleteSession(sessions[0].id);
1312
- ```
1313
-
1314
- `deleteSession()` returns `{ evicted: true, id }`; it throws a 404 if no live session has that id, or a 400 if `id` is not a positive integer. Types: `StreamSession`, `StreamSessionsResponse`, `StreamSessionEvictResponse`.
1315
-
1316
- ---
1317
-
1318
- ### DEX Firehose (Ultra) — `wss://madeonsol.com/ws/v1/dex-stream`
1319
-
1320
- 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.
1321
-
1322
- **Limits:** ULTRA = 2 connections, **10 named subscriptions per connection**, up to **500 trades replay** from a server-side buffer holding ~5 minutes of firehose history (not connection-scoped covers trades from before you connected; newest-first, sort by `block_time`). Inbound rate limit: 5 messages/sec (excess emits one error per second).
1323
-
1324
- #### Quick start
1325
-
1326
- ```ts
1327
- import { WebSocket } from "ws"; // or native WebSocket in browsers/Bun
1328
-
1329
- const { token, dex_ws_url } = await client.stream.getToken();
1330
- const ws = new WebSocket(`${dex_ws_url}?token=${token}`); // token MUST be appended as query param
1331
-
1332
- ws.on("open", () => {
1333
- // Multi-subscription: each sub has its own sub_id and filters
1334
- ws.send(JSON.stringify({
1335
- type: "subscribe",
1336
- sub_id: "fresh-pumpfun",
1337
- replay: 50, // backfill up to 500 from ring buffer
1338
- filters: {
1339
- dex: "pumpfun",
1340
- token_age_max_seconds: 300, // first seen in last 5 min
1341
- min_sol: 0.5,
1342
- action: "buy",
1343
- },
1344
- }));
1345
- });
1346
-
1347
- ws.on("message", (raw) => {
1348
- const msg = JSON.parse(raw.toString());
1349
- if (msg.channel === "dex:trades") {
1350
- // { sub_id, data: { wallet, mint, action, sol_amount, token_amount, dex, ... }, replay, ts }
1351
- console.log(msg.sub_id, msg.data.dex, msg.data.action, msg.data.sol_amount);
1352
- }
1353
- });
1354
- ```
1355
-
1356
- #### Protocol — client → server
1357
-
1358
- | `type` | Required fields | Notes |
1359
- |---|---|---|
1360
- | `subscribe` | `sub_id`, `filters` | Optional `replay: 1–500` |
1361
- | `update` | `sub_id`, `filters` | Replaces filters in place — no disconnect needed |
1362
- | `unsubscribe` | `sub_id` | Or omit `sub_id` to clear all subs |
1363
- | `list` | | Server replies with `{ type: "list", subs: [...] }` |
1364
- | `ping` | | Heartbeat server replies `{ type: "pong" }` |
1365
-
1366
- #### Server client message shapes
1367
-
1368
- ```ts
1369
- { type: "connected", tier: "ULTRA", capabilities: { max_subs: 10, max_replay: 500, dex_names: [...], deployer_tiers: [...] } } // on connect
1370
- { type: "subscribed", sub_id: "fresh-pumpfun", filters: { ... } }
1371
- { type: "replay_done", sub_id: "fresh-pumpfun", count: 50 } // after backfill
1372
- { type: "updated", sub_id: "fresh-pumpfun", filters: { ... } }
1373
- { type: "unsubscribed", sub_id: "fresh-pumpfun" }
1374
- { type: "list", subs: [{ sub_id, filters }] } // reply to { type: "list" }
1375
- { type: "heartbeat", ts: 1712160000000 } // every 30s
1376
- { type: "error", sub_id?, message: "..." }
1377
- { channel: "dex:trades", sub_id, data: { ... }, replay: false, ts: 1712160000000 }
1378
- ```
1379
-
1380
- #### Filter dimensions
1381
-
1382
- At least **one targeting filter** is required (otherwise the firehose would dump every trade). Filters compose with AND semantics.
1383
-
1384
- | Filter | Type | Notes |
1385
- |---|---|---|
1386
- | `token_mint` / `token_mints` | string / string[] (≤50) | Targeting |
1387
- | `wallet` / `wallets` | string / string[] (≤50) | Targeting |
1388
- | `dex` | string \| string[] | `pumpfun`, `pumpamm`, `pumpswap`, `raydium`, `jupiter`, `orca`, `meteora`, `launchlab` |
1389
- | `program` | string | Raw program ID |
1390
- | `deployer_tier` | string \| string[] | `elite`, `good`, `moderate`, `rising`, `cold`, `unranked` (uses Deployer Hunter scoring) |
1391
- | `token_age_max_seconds` | number | Only trades on mints first seen within window (uses persisted first-seen table) |
1392
- | `market_cap_min_sol` / `market_cap_max_sol` | number | Bounded by current market cap (last trade price × cached supply, 1h TTL) |
1393
- | `min_sol` / `max_sol` | number | Trade size bounds |
1394
- | `action` | `"buy"` \| `"sell"` | Direction |
1395
-
1396
- **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.
1397
-
1398
- #### Multi-sub example
1399
-
1400
- ```ts
1401
- ws.send(JSON.stringify({ type: "subscribe", sub_id: "snipers", filters: { token_age_max_seconds: 60 } }));
1402
- ws.send(JSON.stringify({ type: "subscribe", sub_id: "whales", filters: { min_sol: 50 } }));
1403
- ws.send(JSON.stringify({ type: "subscribe", sub_id: "kol-mints", filters: { token_mints: ["EPjF...", "So11..."] } }));
1404
-
1405
- // Tighten the snipers filter without disconnecting
1406
- ws.send(JSON.stringify({ type: "update", sub_id: "snipers", filters: { token_age_max_seconds: 30, min_sol: 0.3 } }));
1407
-
1408
- // Drop whales when you're done
1409
- ws.send(JSON.stringify({ type: "unsubscribe", sub_id: "whales" }));
1410
- ```
1411
-
1412
- Each `dex:trades` message echoes the `sub_id` that matched, so you can route them locally without reapplying filter logic client-side.
1413
-
1414
- ---
1415
-
1416
- ### Copy-Trade — `client.copytrade` *(new in 2.10)*
1417
-
1418
- Mirror N source wallets into actionable signals (delivered via webhook/WebSocket). PRO/ULTRA — PRO: 3 rules × 5 wallets, ULTRA: 20 × 50.
1419
-
1420
- ```ts
1421
- const { subscription, webhook_secret } = await client.copytrade.create({
1422
- name: "whale mirror",
1423
- source_wallets: ["WalletA…", "WalletB…"],
1424
- sizing_mode: "fixed",
1425
- sizing_amount: 0.5, // SOL per mirrored buy
1426
- only_action: "buy",
1427
- delivery_mode: "webhook",
1428
- webhook_url: "https://you.example/hook",
1429
- });
1430
-
1431
- await client.copytrade.subscriptions(); // list rules
1432
- await client.copytrade.update(subscription.id, { is_active: false });
1433
- await client.copytrade.signals({ limit: 50 }); // 7-day fired-signal history
1434
- await client.copytrade.delete(subscription.id);
1435
- ```
1436
-
1437
- ### Webhooks — `client.webhooks`
1438
-
1439
- Manage push notification webhooks for real-time events (Pro: 3, Ultra: 10).
1440
-
1441
- ```ts
1442
- // Create a webhook
1443
- const webhook = await client.webhooks.create({
1444
- url: "https://example.com/hook",
1445
- events: ["kol:trade", "deployer:alert"],
1446
- filters: { min_sol: 1 },
1447
- });
1448
-
1449
- // List, update, delete
1450
- const { webhooks } = await client.webhooks.list();
1451
- await client.webhooks.update(webhook.id, { status: "paused" });
1452
- await client.webhooks.delete(webhook.id);
1453
- await client.webhooks.test(webhook.id);
1454
- ```
1455
-
1456
- ---
1457
-
1458
- ## Error handling
1459
-
1460
- All methods throw `MadeOnSolError` on non-2xx responses.
1461
-
1462
- ```ts
1463
- import { MadeOnSol, MadeOnSolError } from "madeonsol";
1464
-
1465
- try {
1466
- const profile = await client.kol.wallet("invalid-wallet");
1467
- } catch (err) {
1468
- if (err instanceof MadeOnSolError) {
1469
- console.error(err.message); // human-readable message
1470
- console.error(err.status); // HTTP status code, e.g. 404
1471
- console.error(err.body); // raw response body
1472
- }
1473
- }
1474
- ```
1475
-
1476
- ---
1477
-
1478
- ## Exported types
1479
-
1480
- All types are exported from the main entry point:
1481
-
1482
- ```ts
1483
- import type {
1484
- // Errors
1485
- MadeOnSolError,
1486
-
1487
- // KOL
1488
- KolTrade,
1489
- KolFeedParams,
1490
- KolFeedResponse,
1491
- KolLeaderboardParams,
1492
- KolLeaderboardResponse,
1493
- KolLeaderboardEntry,
1494
- KolWalletParams,
1495
- KolWalletProfile,
1496
- KolCoordinationParams,
1497
- KolCoordinationResponse,
1498
- CoordinatedToken,
1499
- KolTokenActivity,
1500
- KolPnlByToken,
1501
-
1502
- // Deployer
1503
- DeployerStats,
1504
- DeployerLeaderboardParams,
1505
- DeployerLeaderboardResponse,
1506
- DeployerLeaderboardEntry,
1507
- DeployerProfile,
1508
- DeployerToken,
1509
- DeployerTokensParams,
1510
- DeployerTokensResponse,
1511
- DeployerAlertsParams,
1512
- DeployerAlertsResponse,
1513
- DeployerAlert,
1514
- DeployerAlertStatsParams,
1515
- DeployerAlertStats,
1516
- BestTokensParams,
1517
- BestTokensResponse,
1518
- BestToken,
1519
- RecentBondsParams,
1520
- RecentBondsResponse,
1521
- RecentBond,
1522
-
1523
- // Tools
1524
- ToolsSearchParams,
1525
- ToolsSearchResponse,
1526
- Tool,
1527
-
1528
- // KOL PnL & Trending
1529
- KolPnlResponse,
1530
- KolTrendingTokensResponse,
1531
- TrendingToken,
1532
-
1533
- // Alpha Wallet Intelligence
1534
- AlphaWalletEntry,
1535
- AlphaLeaderboardResponse,
1536
- AlphaWalletResponse,
1537
- AlphaLinkedResponse,
1538
- AlphaCapTableResponse,
1539
- AlphaBuyerQualityResponse,
1540
-
1541
- // Wallet Tracker
1542
- WalletEntry,
1543
- WatchlistResponse,
1544
- WalletTrackerEvent,
1545
- WalletTrackerTradesResponse,
1546
- WalletTrackerSummaryResponse,
1547
-
1548
- // Wallet classification (v2.20)
1549
- WalletClassification,
1550
- WalletBatchClassifyResponse,
1551
- BotConfidence,
1552
- DumpClusterStats,
1553
-
1554
- // Token trade tape (v2.20)
1555
- TokenTradesParams,
1556
- TokenTrade,
1557
- TokenTradesResponse,
1558
- TokenTradesCoverage,
1559
-
1560
- // Sniper footprint (v2.20)
1561
- SniperFootprint,
1562
-
1563
- // Streaming
1564
- StreamToken,
1565
-
1566
- // Webhooks
1567
- Webhook,
1568
- WebhookCreateParams,
1569
- WebhookUpdateParams,
1570
- WebhookListResponse,
1571
-
1572
- // Enums / unions
1573
- KolAction,
1574
- LeaderboardPeriod,
1575
- CoordinationPeriod,
1576
- DeployerTier,
1577
- DeployerSortField,
1578
- AlertPeriod,
1579
- BestTokensPeriod,
1580
- } from "madeonsol";
1581
- ```
1582
-
1583
- ---
1584
-
1585
- ## Related
1586
-
1587
- - [MadeOnSol website](https://madeonsol.com) — Browse 950+ Solana tools
1588
- - [API documentation](https://madeonsol.com/api-docs) — Interactive endpoint reference
1589
- - [Pricing & API keys](https://madeonsol.com/pricing) — Free tier: 200 requests/day, no signup payment
1590
- - [MadeOnSol on GitHub](https://github.com/madeonsol/madeonsol) — Main project repository
1591
-
1592
- ## Also Available
1593
-
1594
- | Platform | Package |
1595
- |---|---|
1596
- | Rust | [`madeonsol`](https://crates.io/crates/madeonsol) on crates.io |
1597
- | Python (LangChain, CrewAI) | [`madeonsol-x402`](https://pypi.org/project/madeonsol-x402/) on PyPI |
1598
- | MCP Server (Claude, Cursor) | [`mcp-server-madeonsol`](https://www.npmjs.com/package/mcp-server-madeonsol) · [Smithery](https://smithery.ai/servers/madeonsol/solana-kol-intelligence) · [Glama](https://glama.ai/mcp/servers/LamboPoewert/mcp-server-madeonsol) |
1599
- | ElizaOS | [`@madeonsol/plugin-madeonsol`](https://www.npmjs.com/package/@madeonsol/plugin-madeonsol) |
1600
- | Solana Agent Kit | [`solana-agent-kit-plugin-madeonsol`](https://www.npmjs.com/package/solana-agent-kit-plugin-madeonsol) |
1601
-
1602
- ---
1603
-
1604
- ## License
1605
-
1606
- MIT © [MadeOnSol](https://madeonsol.com)
1
+ # madeonsol
2
+
3
+ [![npm version](https://img.shields.io/npm/v/madeonsol?style=flat-square)](https://www.npmjs.com/package/madeonsol)
4
+ [![npm downloads](https://img.shields.io/npm/dm/madeonsol?style=flat-square)](https://www.npmjs.com/package/madeonsol)
5
+ [![GitHub stars](https://img.shields.io/github/stars/madeonsol/madeonsol-sdk?style=flat-square&logo=github)](https://github.com/madeonsol/madeonsol-sdk)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.4+-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
7
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen?style=flat-square)](package.json)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](LICENSE)
9
+
10
+ > ⭐ **[Star on GitHub](https://github.com/madeonsol/madeonsol-sdk)** if you find this useful · 📂 **[Examples](./examples/)** · 📚 **[API docs](https://madeonsol.com/api-docs)**
11
+
12
+ Official TypeScript/JavaScript SDK for the **[MadeOnSol](https://madeonsol.com) Solana API** — zero dependencies, fully typed, works in Node.js ≥ 18 and edge runtimes.
13
+ > Real-time Solana trading intelligence: track 1,069 KOL wallets with <3s latency on paid keys and x402 pay-per-call (free-tier live feeds are 5-min delayed), score 23,000+ Pump.fun deployers, surface deshred deploy signals **~500ms before on-chain confirmation**, detect multi-KOL coordination, score token rug-risk 0–100 with a transparent factor breakdown, expose the bundle cohort that bought a token together and how much of supply it still holds, verify any wallet's current on-chain holdings with airdrop/insider `transfer_delta` detection, push every pump.fun graduation the second it bonds, and stream every DEX trade across 9+ programs. Free tier: 200 requests/day across 40+ endpoints (live feeds 5-min delayed) — no signup payment. Get a key at [madeonsol.com/pricing](https://madeonsol.com/pricing).
14
+
15
+ > **New in 2.27.0 — deployer reputation as-of a date, and creator-fee rewards.** `client.deployerHunter.deployerAsOf(wallet, { date? })` (typed `DeployerAsOfResponse`) binds `GET /deployer-hunter/{wallet}/as-of`: the deployer's reputation exactly as it stood on `date` (default today, UTC) the latest write-on-change snapshot at or before it, so a backtest sees only what was knowable then. `snapshot.snapshot_date` can predate `requested_date` (snapshots are write-on-change); `snapshot.carried: true` marks that. No snapshot at or before the date `as_of: false, snapshot: null` nothing is ever synthesized. `date` must be 2026-04-07 and not in the future. `client.deployerHunter.deployerRewards(wallet)` (typed `DeployerRewardsResponse`) binds `GET /deployer-hunter/{wallet}/rewards`: pump.fun creator-fee rewards, answered two ways that are never merged `collected` (what actually reached the wallet: direct vault claims, kept 90 days; social-handle claims; shareholder payouts on **any** token) and `attributed` (every payout on the tokens it **deployed**, split `to_self`/`to_others` + `redirected_pct`a deployer redirecting most of its fees elsewhere is visible in the gap between the two). Every money field is `{ sol, usdc, usd }`; `usd` is `null` (never a silent 0) when a SOL amount exists and no SOL price was available. `top_tokens`/`top_recipients` (≤10, USD-sorted) show where attributed fees went, recipients flagged `is_self`/`is_social_pda`. Works for non-deployers too (`is_deployer: false`, `attributed` empty). **PRO+** (BASIC receives HTTP 403) on the keyed `msk_` API. New types: `DeployerAsOfParams`, `DeployerAsOfResponse`, `DeployerAsOfSnapshot`, `DeployerRewardsResponse`, `DeployerRewardsMoney`, `DeployerRewardsRail`, `DeployerRewardsTopToken`, `DeployerRewardsTopRecipient`, `DeployerRewardsSocial`.
16
+
17
+ > **New in 2.26.0token surges & revivals: momentum fires with the honest half attached — one endpoint + one live channel.** `client.token.surges(params?)` (typed `TokenSurgesResponse`) binds `GET /tokens/surges`: **`surge`** = a token < 30 min old whose MC runs hard vs its *launch* MC (tier `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); **`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). Every row carries `tape` (buys / sells / volume, `unique_buyers` only where wallet data exists — `wallet_data_available: false` otherwise, never inferred), `kol`, `early_buyers` (bundled / sold / sniper wallets), `deployer` reputation and **`risk_flags[]`** (`bundled_launch`, `few_buyers`, `wash_pattern`, `thin_liquidity`, `cold_deployer`, `sniper_heavy`, `early_buyers_exiting`, `sell_pressure`, `no_tape_trades`, `no_prior_price`, `mint_authority_active`, `transfer_fee`); rows ≥ 65 min old carry the +1 h `outcome` (`mc_1h_multiple`, `peak_1h_multiple`, `priced_after_1h`) and `stats: true` returns per-(kind, tier) hit-rates out-of-sample by construction. Filters `kind`, `tier`, `mint`, `launchpad`, `deployer_tier`, `min_mc_usd` / `max_mc_usd`, `min_buys`, `exclude_flags`, `only_clean`; cursors `since` / `before`. Pushed live on the new **`token:surges`** WS channel (events `token:surge` / `token:revival`, typed `TokenSurgeStreamEvent`, server-side filters `TokenSurgesSubscribeFilters`: `kinds[]`, `tiers[]`, `launchpads[]`, `exclude_flags[]`, `min_mc_usd` / `max_mc_usd`, `deployer_tier[]`). Nearly every scalar is `| null` null means unknown, never zero. **PRO+** (BASIC receives HTTP 403) on the keyed `msk_` API.
18
+
19
+ > **New in 2.25.1stream tokens never expire.** `client.stream.getToken()` (`POST /stream/token`) now returns the **same token on every call, forever**. It stops working only if your subscription lapses or you call `client.stream.getToken({ rotate: true })` to replace it (the previous value keeps working for 60 s). `StreamToken.expires_at` and `next_refresh_at` are always `null` (kept for wire compatibility do not schedule refreshes on them); the response gains `rotated: boolean` and `lifetime: string`. The server never rotates on its own and never sends `token_refresh` unless you rotated; a `4001` close means "mint again" (lapsed or rotated), never a timer. Prefer `Authorization: Bearer <token>` on the WebSocket handshake `?token=` still works and is masked in access logs. `client.stream.connect()` already does the right thing (it calls `getToken()` on every (re)connect); no code change needed on your side.
20
+
21
+ > **New in 2.25.0 — token locks & vesting, upcoming unlocks, and pump.fun creator-fee sharing / fee claims — five endpoints + two live channels.** `client.token.locks(mint, params?)` (typed `TokenLocksResponse`) binds `GET /tokens/{mint}/locks`: every on-chain Streamflow / Jupiter Lock / Bonfida lock or vesting contract on a mint, decoded from the locker programs' account state, with a LIVE-derived view (`locked_raw` still locked, `unlocked`, `withdrawn`, `claimable`, `status`, `next_unlock`) and a `summary` (locked / deposited totals, the 7d / 30d forward unlock schedule, `active_cancelable_by_sender` a lock the sender can cancel is a weaker promise). `client.token.locksFeed(params?)` (`GET /tokens/locks`) is the cross-token feed of NEW contracts, cursor-paginated (`pagination.next_since` / `next_before`) and pushed live on the new **`token:locks`** WS channel (event `token:lock`, typed `TokenLockStreamEvent`). `client.token.unlocks(params?)` (`GET /tokens/unlocks`) lists upcoming unlock EVENTS (cliff / period / final / tranche) inside `within=1h…90d` with `window_amount_*` per contract. `client.token.feeShares(mint)` (`GET /tokens/{mint}/fee-shares`) decodes a pump.fun coin's on-chain `SharingConfig` — who its creator fees are redirected to (`share_bps`, `is_admin`, `is_social_pda` for fees earmarked for an X account etc., `redirected_bps`, `social_bps`, `is_default` = 100% to the creator) plus the distribution rollup per recipient and the config change log; `client.token.feeClaims(params?)` (`GET /tokens/fee-claims`) is the fee-event feed (`distribution` with per-address `payouts[]`, `social_claim`, `shares_created` / `updated` / `reset`, `creator_transferred`, `creator_claim` on request), pushed live on the new **`token:fee_claims`** channel (event `token:fee_claim`, typed `TokenFeeClaimStreamEvent`). Honest limits: base-unit amounts are **strings** and ui / usd / pct are `null` when decimals or price are unknown; **LP locks are NOT included** (token / vesting locks only); **fee-event history starts 2026-08-17**; all five are **PRO+** (BASIC receives HTTP 403) on the keyed `msk_` API.
22
+
23
+ > **New in 2.24.0 — live holder census: exact holder count, labelled holders, and pools that are named, not just excluded.** `client.alpha.holders(mint)` (typed `TokenHoldersResponse`) binds `GET /tokens/{mint}/holders` (PRO+): every token account of the mint read from the ledger at `confirmed` and merged per owner, so `concentration.holder_count` is EXACT (distinct non-zero owners minus pools / bonding curves / burns) never a trade-derived estimate; it is `null` only when the provider refuses the census for a mega-cap, in which case you get the top-20 view and `source.census_fallback_reason` says so. Each disclosed owner carries our labels (`deployer` / `kol` / `early_buyer` / `bundle` / `bot` / `dump_cluster` empty means unknown to us, not clean), and `excluded[]` NAMES what was taken out of the circulating denominator: `reason` = `pool` (with `dex` + `pool_address`), `bonding_curve` (pump.fun / LaunchLab), `burn`, or `program_account` only when we genuinely cannot attribute the PDA; `pool_pct` / `burned_pct` / `program_pct` split 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 get `503 holder_scan_in_progress` with `retry_after_seconds: 20` while the scan finishes into the cache, and the retry is instant.
24
+
25
+ > **New in 2.23.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_usd` on 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. Because `sol_amount` is 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 new **`market_price_sol`/`market_price_usd`** fields — sampled once per token per pool update, so every trade in the same slot shares it. Until now `price_sol` carried 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. Use `price_sol` for cost basis, fills and PnL; `market_price_sol` for a per-token series independent of trade size and direction. Both `client.alpha.tokenTrades(mint)` and `client.wallet.trades(address)` carry all four fields on `TokenTrade` / `WalletTrade` the wallet tape returned amounts and no price at all before.
26
+
27
+ > **New in 2.22.0** — **Clean stream shutdown.** `client.stream.connect().close()` now fully tears down the underlying WebSocket so short-lived scripts exit promptly instead of hanging on a lingering socket. In Node the client now prefers the `ws` package (which exposes `terminate()`) and hard-terminates on close; the browser still uses the native WebSocket. No API changes purely a lifecycle fix. (If you don't already depend on `ws` and want the fast exit on Node ≥22, `npm i ws`.)
28
+ >
29
+ > **New in 2.21.0** — **Pool depth / price-impact + dev self-activity on the risk score.** `client.alpha.tokenDepth(mint, { sizes? })` (`GET /tokens/{mint}/depth`, **PRO+**) answers "how much SOL does it take to move the price N%", per pool. Each depth-computable pool returns `spot_price_sol`, `fee_pct`, `source` (`"stream"` for constant-product AMMs served zero-RPC from stream reserves, `"live_rpc"` for pump.fun/bonk curves priced from a live read of the curve's VIRTUAL reserves), `reserves_age_ms`, per-size `quotes` (`size_sol`/`tokens_out`/`avg_price_sol`/`price_impact_pct`), and `to_move_price` (SOL to move price `1pct`/`5pct`/`10pct`). `sizes` accepts a CSV string or `number[]` (max 8, each >0 and ≤10000; default `0.5,1,5,10`); the response carries `sol_usd`, `sizes_sol`, `primary_pool`, and honesty-first `unsupported_pools` concentrated pools (CLMM/Orca/DLMM), Meteora-DBC curves, and unclassified pools come back with a `reason` instead of a wrong number. Impact is per-pool, not router-optimal. Plus `client.alpha.risk(mint)` responses gain a top-level `dev` block (`TokenRiskDev | null`) deployer self-activity for the mint: `wallet`, `launchpad`, `deployed_at`, create-tx `buy_sol`/`buy_tokens`/`buy_supply_pct`, post-create `bought_tokens_after`/`sold_tokens`/`sold_sol` with `first_sell_at`/`last_sell_at`, live on-chain `holdings_tokens`/`holdings_supply_pct`, `wallet_empty` (`boolean | null`), and `transferred_out` (`boolean | null` — chain balance well below the trade-derived expectation, i.e. tokens moved without a swap). `dev` is `null` when the mint has no tracked deploy row (single-mint `/risk` only; absent on batch items). New types: `TokenDepthParams`, `TokenDepthResponse`, `TokenDepthPool`, `TokenDepthUnsupportedPool`, `TokenDepthPoolBase`, `TokenDepthQuote`, `TokenDepthToMovePrice`, `TokenDepthSource`, `TokenRiskDev`.
30
+ >
31
+ > **New in 2.20.0** — **Wallet batch classify, token trade tape, sniper footprint.** `client.wallet.batchClassify(wallets)` (`POST /wallet/batch/classify`, 1–100 addresses, **PRO+**) returns bulk reputation flags per wallet: `is_sniper`, `is_bundler` (lifetime), `is_dumper` (rolling 42d), `is_kol` + `kol_name`, `bot_confidence`, and `dump_cluster` cohort stats flags are pump.fun-pipeline scoped (`false` = not observed, NOT verified clean). `client.token.trades(mint, params?)` (`GET /tokens/{mint}/trades`, **PRO+**) is the mint-scoped trade tapecursor-paginated raw trades with `price_sol`/`price_usd`/`early_buyer_rank`/`slot`, filterable by `action`/`wallet`/`since`/`until`, defaulting to the FULL history (starts 2026-04-12; the `coverage` block carries `history_start` + `scope`). The wallet profile `flags` block (`client.wallet.stats()`) gains the same `is_sniper`/`is_bundler`/`is_dumper` + `dump_cluster` fields, and **`bot_confidence` is a type fix**: previously typed `number | null` but the API always returned `null` due to a bug — it now returns the real value as a string enum `"none" | "low" | "medium" | "high" | null`. `TokenRiskInputs` gains `sniper_footprint` (slot-window snipe rollup: `buys`/`buyers`/`sol`/`supply_pct`/`sniper_wallet_buys`/`data_available`/`as_of`, or null) and `client.sniper.recent()` deploys each carry the same `footprint` block. New types: `WalletClassification`, `WalletBatchClassifyResponse`, `TokenTradesParams`, `TokenTrade`, `TokenTradesResponse`, `TokenTradesCoverage`, `SniperFootprint`, `BotConfidence`, `DumpClusterStats`.
32
+ >
33
+ > **New in 2.19.0** — **Verified wallet holdings.** `client.wallet.holdings(address, { limit?, min_value_usd? })` (`GET /wallet/{address}/holdings`) reads the wallet's actual current SPL + Token-2022 token accounts and SOL balance straight from chain, enriches each with our price/MC/name/symbol, and computes a `transfer_delta` (on-chain amount trade-derived net position) exposing tokens that arrived or left **without a swap** (airdrops, insider funding, wallet-hopping). Distinct from `client.wallet.positions()` (trade-derived FIFO): holdings is "what they actually hold right now". Returns `WalletHoldingsResponse` with a `summary` (`token_accounts`/`non_zero`/`returned`/`priced`/`total_value_usd`/`truncated`), `sol_balance`, and `verified_at`. `limit` 1–500 (default 200), `min_value_usd` ≥0 (default 0). **ULTRA only.** New types: `WalletHoldingsParams`, `WalletHoldingsResponse`, `Holding`.
34
+ >
35
+ > **New in 2.18.0** — **Bundle cohort intelligence.** `client.alpha.bundle(mint)` (`GET /tokens/{mint}/bundle`) surfaces the wallets that bought a token together in one atomic transaction (`bundle_kind: "atomic_tx"`) or the same slot (`"same_slot"`) and, headline first, how much of supply they still hold. The `bundle` summary block (returned on every tier) carries `held_pct_of_supply` (0–1 of total supply, HEADLINE), `bundle_kind`, `wallet_count`, `held_ratio`, `fully_exited`, `buy_volume`, and `tokens_held`. **Tier-gated:** BASIC/TRADER get the `bundle` block only (`wallets: []`); PRO adds the top-10 cohort wallets with flags (`held_ratio`, `has_sold`, `atomic`, `is_kol`); ULTRA returns the full cohort plus identity (`kol_name`, `win_rate`, `bot_confidence`) and per-wallet `tokens_held`. New types: `TokenBundleResponse`, `BundleSummary`, `BundleWallet`, `BundleKind`.
36
+ >
37
+ > **New in 2.17.0** — **Batch risk scoring + live stream-session control.** `client.token.batchRisk(mints)` (`POST /tokens/batch/risk`, up to 50 mints, **counts as 1 request**) returns the same transparent 0–100 rug-risk result as `client.alpha.risk(mint)` for each mint (with `as_of`); untracked mints come back as `{ mint, error: "not_tracked" }` without failing the batch. `client.stream.sessions()` lists your live WebSocket sessions (`ws-streaming` + `dex-stream`) and `client.stream.deleteSession(id)` force-closes one to free a slot a ghost socket is holding. **PRO/ULTRA only.** New types: `TokenRiskBatchResponse`, `TokenRiskBatchItem`, `TokenRiskBatchError`, `StreamSession`, `StreamSessionsResponse`, `StreamSessionEvictResponse`.
38
+ >
39
+ > **New in 2.16.0** — **Almost-bonded discovery + trending sorts.** `client.token.almostBonded({ min_progress, min_velocity_pct_per_min, deployer_tier, sort, limit })` pre-bond pump.fun tokens near graduation, ranked by velocity (Δprogress/min): "95% and accelerating" beats "92% stalled". Each token carries `progress_pct`, `velocity_pct_per_min`, `eta_minutes`, `stalled`, `real_sol_reserves`, `market_cap_usd`, `liquidity_usd`, `authorities_revoked`, `deployer_tier`, and `age_minutes`. **PRO/ULTRA only.** New types: `AlmostBondedParams`, `AlmostBondedToken`, `AlmostBondedResponse`, `AlmostBondedSort`. Plus `client.token.list({ sort })` gains four momentum sorts `mc_change_5m_desc`, `mc_change_1h_desc`, `volume_1h_desc`, and `trending` (composite recent-volume × positive-momentum rank).
40
+ >
41
+ > **New in 2.15.0** — **Token flow + deployer SOL balance.** `client.alpha.tokenFlow(mint, { window })` (`GET /tokens/{mint}/flow`, `window` `1h` default or `24h`, **PRO+**) returns aggregated buy/sell flow for a token: `unique_wallets`/`unique_buyers`/`unique_sellers`, `buy_count`/`sell_count`/`total_trades`, `buy_sol`/`sell_sol`/`net_sol` (buy − sell), and `trades_per_wallet`, plus the window `from` timestamp. New types: `TokenFlowResponse`, `TokenFlowParams`, `TokenFlowWindow`. Deployer-alert objects (`DeployerAlert`) now also carry `deployer_sol_balance` (the deployer wallet's SOL balance at alert time, `number | null`).
42
+ >
43
+ > **New in 2.14.0** — **OHLCV candles + net flow.** `client.alpha.candles(mint, { tf, limit, from, to })` returns the persisted price/MC trajectory as candlesticks (`1m`/`5m`/`15m`/`1h`/`4h`/`1d`, rolled up on read). **PRO**: OHLCV (`open`/`high`/`low`/`close`/`volume_usd`/`trades`/`market_cap_usd`), last 30 days. **ULTRA**: adds per-bar net flow (`buy_volume_usd`/`sell_volume_usd`/`net_volume_usd`, `buy_count`/`sell_count`, `volume_mev_usd`), liquidity delta (`open_liquidity_usd`/`close_liquidity_usd`) and full history `net_flow_included` flags which set you got. New types: `Candle`, `CandlesResponse`, `CandlesParams`, `CandleTimeframe`.
44
+ >
45
+ > **New in 2.13.0** — **Token risk score.** `client.alpha.risk(mint)` returns a transparent 0–100 rug-risk/safety score (higher = riskier) for any token: a `band` (`safe`/`caution`/`danger`), an explainable `factors[]` array (each with `key`, `label`, `status`, `points`, `detail`) that sums into the score, and the raw `inputs` it was computed from mint/freeze authority revocation, liquidity USD + liquidity-to-MC ratio, transfer fee bps, Token-2022 flag, burn detection, launch cohort (SOL + size), deployer bond rate + total deployed, KOL signal, and blacklist status. Plus `score_version` and `as_of`. **PRO/ULTRA only.** New types: `TokenRiskResponse`, `TokenRiskFactor`, `TokenRiskInputs`, `TokenRiskBand`, `TokenRiskStatus`.
46
+ >
47
+ > **New in 2.12.0** — **Launch cohort, liquidity/MC ratio, deployer tier filter, KOL hold stats, and signal performance.** `TokenResponseBody` (single + batch) gains `liquidity_to_mc_ratio`, `launch_cohort_sol`, and `launch_cohort_size`. `client.token.list()` adds `min_liq_mc_ratio`, `max_liq_mc_ratio`, and `deployer_tier` filter params; list items gain `liquidity_to_mc_ratio` and `deployer_tier`. `KolLeaderboardEntry` gains `median_hold_minutes_30d` and `percentile_early_entry_30d`. New top-level method `client.getSignalPerformance(name)` calls `GET /signals/{name}/performance`.
48
+ >
49
+ > **New in 2.11.1** — **Deployer runner-rate fields.** Sniper deploys, deployer alerts/profiles, and leaderboard rows now carry `runner_rate` (fraction of the deployer's labeled tokens that ran peak ≥60min after deploy — vs dumped) and `labeled_tokens` (confidence denominator; gate on ≥3).
50
+ >
51
+ > **New in 2.11** — **Graduation events + dump-cluster detection.** Subscribe `token:graduations` for every pump.fun bond in real time tracked deployer or not with typed `GraduationEvent` payloads (mint, deployer tier, time-to-bond, MC at bond). Buyer-quality `breakdown` adds `dump_cluster_count` (out-of-sample validated: 3+ such wallets in the first-20 94% dump vs 61% base) and `recycled_early_buyer_count` (high count with zero cluster leans runner). DEX firehose: replay buffer deepened to ~5 minutes; mint-scoped subs now receive in-band `dex:graduations` frames the bond lands on the same connection as your position's trade flow.
52
+
53
+ > **New in 2.9** — **Deshred Sniper Alerts.** `client.sniper.recent()` surfaces new pump.fun deploys reconstructed from shred-level data ~500ms before the chain confirms thema measured head start over any confirmed-stream feed. PRO sees elite/good deployers; ULTRA sees every tier and maintains a custom deployer watchlist (`client.sniper.addToWatchlist()`). Use the `sniper:deploys` WebSocket channel or `sniper:deploy` webhook for live push instead of polling.
54
+ >
55
+ > **New in 2.8** — **Price alerts, scout leaderboard, wallet derived stats.** `client.priceAlerts.*` — CRUD for token MC dip/recovery alerts delivered via webhook or WebSocket (PRO=5, ULTRA=25). `client.kol.scoutLeaderboard()` — top scouts ranked by first-touch follow-on rate. `client.kol.coordinationHistory()` and `client.token.peakHistory()` expose the historical record. `client.wallet.stats()` now returns a `derived` block: `win_rate`, `roi`, `verdict`, and `biggest_miss`.
56
+ >
57
+ > **New in 2.7** — **Universal Wallet API.** `client.wallet.stats()`, `client.wallet.pnl()`, `client.wallet.positions()`, `client.wallet.trades()` — FIFO cost-basis PnL, open positions hydrated with live prices, and cursor-paginated raw trades for **any** Solana wallet (not just curated KOLs). PRO+. Server-side cache (5min/1h/24h based on activity) cache hits don't count against your quota.
58
+ >
59
+ > **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 by `5m`/`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 get `undefined`. Patch is type-only no runtime breaking changes.
60
+ >
61
+ > **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 default `min_liq=2000` to 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 every `TokenResponseBody`: `mc_change_pct`, `volume_usd`, `mev_volume_pct` (each keyed by `5m`/`15m`/`1h`/`2h`/`4h`) plus `history_age_seconds` on the parent. `/token/{mint}` 400s now ship `code`, `reason`, `received_length`, `example`, and `docs` URL — stop guessing why a mint failed. Deprecated `avg_entry_mc_usd` / `entry_mc_samples` removed from leaderboard types. All other 2.5.x APIs unchanged.
62
+
63
+ > **Build Solana trading bots, analytics dashboards, KOL copy-trading tools, deshred sniper bots, and ecosystem browsers.**
64
+
65
+ ## Quick start (10 seconds)
66
+
67
+ ```bash
68
+ npm install madeonsol
69
+ ```
70
+
71
+ ```ts
72
+ import { MadeOnSol } from "madeonsol";
73
+ const client = new MadeOnSol({ apiKey: "msk_..." }); // free tier at madeonsol.com/pricing
74
+ const { trades } = await client.kol.feed({ limit: 5, action: "buy" });
75
+ ```
76
+
77
+ | Feature | Description |
78
+ |---|---|
79
+ | **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,069 tracked KOL wallets. **180 days of trade history** retained. |
80
+ | **Deshred Sniper** | Deploy feed reconstructed from shred-level data surfaces new pump.fun launches **~500ms before on-chain confirmation**. PRO: elite/good deployers. ULTRA: all tiers + custom watchlist. Use WebSocket/webhook for live push. |
81
+ | **Alpha Wallet Intel** | Leaderboard of 1M+ scored early-buyer wallets, full wallet profiles, linked-wallet clustering, token cap-table enrichment, and 0–100 buyer quality scores with dump-cluster wallet detection. |
82
+ | **Token Risk Score** | Transparent 0–100 rug-risk/safety score per token with a `safe`/`caution`/`danger` band, explainable factor breakdown, and the raw inputs (authorities, liquidity, transfer fee, launch cohort, deployer bond rate, KOL signal, blacklist). PRO/ULTRA. |
83
+ | **Bundle Cohort** | The wallets that bought a token together (one atomic tx or the same slot) and headline first — `held_pct_of_supply` still held, plus `held_ratio`, `fully_exited`, and buy volume. Every tier gets the summary; PRO adds top-10 wallet flags; ULTRA adds KOL identity, win rate, bot confidence, and per-wallet balances. |
84
+ | **Universal Wallet** | FIFO cost-basis PnL, open positions (hydrated with live prices), and raw trade history for **any** Solana wallet — not just curated KOLs. 90-day window, server-side cache. PRO+. |
85
+ | **Verified Holdings** | Current on-chain SPL + Token-2022 balances + SOL, read straight from chain and enriched with price/MC/name, plus a `transfer_delta` that exposes airdrops / insider funding / wallet-hopping (tokens that moved without a swap). ULTRA. |
86
+ | **Price Alerts** | Token MC dip/recovery alerts delivered via WebSocket or HMAC-signed webhook. PRO: 5 rules, ULTRA: 25. |
87
+ | **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. |
88
+ | **Deployer Hunter** | 23,000+ pump.fun deployers scored by bonding rate — tier leaderboard, deploy alerts, deployer profiles, and best-tokens feed. |
89
+ | **DEX Trade Stream** | Real-time WebSocket stream of ALL Solana DEX trades across 9+ programs — filter by token, wallet, DEX, deployer tier, or trade size. ~5 min replay + in-band graduation frames on mint-scoped subs. ULTRA. |
90
+ | **Webhooks** | Push notifications for KOL trades, coordination signals, deployer alerts, and wallet tracker events (Pro/Ultra) |
91
+ | **Tool Directory** | Search 1,070+ Solana tools and dApps indexed on MadeOnSol |
92
+
93
+ **Links:** [Full docs](https://madeonsol.com/solana-api) · [Website](https://madeonsol.com) · [API docs](https://madeonsol.com/api-docs)
94
+
95
+ ## Authentication
96
+
97
+ Get a free API key at [madeonsol.com/pricing](https://madeonsol.com/pricing). Keys start with `msk_`.
98
+
99
+ ---
100
+
101
+ ## Install
102
+
103
+ ```bash
104
+ npm install madeonsol
105
+ # or
106
+ yarn add madeonsol
107
+ # or
108
+ pnpm add madeonsol
109
+ ```
110
+
111
+ Requires **Node.js ≥ 18** (uses native `fetch`). Works out of the box in Cloudflare Workers, Vercel Edge, and Bun.
112
+
113
+ ---
114
+
115
+ ## Quick start
116
+
117
+ ```ts
118
+ import { MadeOnSol } from "madeonsol";
119
+
120
+ const client = new MadeOnSol({ apiKey: "msk_your_api_key_here" });
121
+
122
+ // Latest KOL buy trades
123
+ const { trades } = await client.kol.feed({ limit: 10, action: "buy" });
124
+ console.log(trades[0].kol_name, "bought", trades[0].token_symbol);
125
+
126
+ // Deshred sniper ~500ms before on-chain confirmation (PRO/ULTRA)
127
+ const { deploys } = await client.sniper.recent({ limit: 20, min_bond_rate: 0.5 });
128
+ console.log(deploys[0].token_name, "deployed by", deploys[0].deployer_tier, "tier deployer");
129
+
130
+ // Multi-KOL coordination signal
131
+ const { coordination } = await client.kol.coordination({ min_kols: 3, min_score: 70 });
132
+
133
+ // FIFO PnL for any wallet (PRO+)
134
+ const pnl = await client.wallet.pnl("ASVz...ybJk");
135
+ console.log(`Realized: ${pnl.summary.realized_sol} SOL · Win rate: ${(pnl.summary.win_rate! * 100).toFixed(1)}%`);
136
+
137
+ // Search Solana tools
138
+ const { tools } = await client.tools.search({ q: "trading", limit: 10 });
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Use cases
144
+
145
+ - **Copy-trading bot** — stream KOL buys via `client.kol.feed()` and mirror trades
146
+ - **Deshred sniper** — `client.sniper.recent()` or subscribe to `sniper:deploys` WebSocket for ~500ms pre-confirm deploy signals
147
+ - **DEX trade sniping** — subscribe to the all-DEX stream filtered by token, wallet, or deployer tier
148
+ - **Graduation sniper / position manager** — subscribe `token:graduations` for every pump.fun bond in real time, or hold a mint-scoped firehose sub and get the bond in-band with your position's trade flow
149
+ - **Coordination detector** — flag tokens with `client.kol.coordination({ min_kols: 3, min_score: 70 })`
150
+ - **Scout signal** — track first-KOL-touch events filtered to S/A-tier scouts via `client.kol.firstTouches({ preset: "scout" })`
151
+ - **Rug-risk gate** — score a token with `client.alpha.risk(mint)` and skip anything in the `danger` band before buying
152
+ - **Bundle-cohort check** — call `client.alpha.bundle(mint)` and bail when `held_pct_of_supply` is high (a bundle still sitting on supply can dump) or when the cohort hasn't `fully_exited`
153
+ - **Wallet analyser** — `client.wallet.pnl()` for FIFO cost-basis PnL on any Solana wallet
154
+ - **Holdings verifier / airdrop detector** — `client.wallet.holdings()` for verified current on-chain balances, and flag tokens with a nonzero `transfer_delta` (arrived without a swap — airdrops, insider funding, wallet-hopping)
155
+ - **Price alert bot** — `client.priceAlerts.create()` for MC dip/recovery alerts delivered via webhook
156
+ - **Analytics dashboard** — combine leaderboard, PnL, token velocity, and tool data
157
+ - **Telegram/Discord bot** — pipe alerts via webhooks into chat
158
+ - **Portfolio tracker** — use `client.kol.wallet()` to follow specific KOL positions
159
+
160
+ ---
161
+
162
+ ## API Reference
163
+
164
+ ### KOL Tracker — `client.kol`
165
+
166
+ #### `client.kol.feed(params?)`
167
+
168
+ Live feed of trades made by tracked KOL wallets.
169
+
170
+ ```ts
171
+ const { trades, count } = await client.kol.feed({
172
+ limit: 50, // 1–100, default 50
173
+ action: "buy", // "buy" | "sell"
174
+ kol: "7xKX...", // filter by specific wallet
175
+ });
176
+ ```
177
+
178
+ Returns: `KolFeedResponse` `{ trades: KolTrade[], count: number }`
179
+
180
+ 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.
181
+
182
+ ---
183
+
184
+ #### `client.kol.leaderboard(params?)`
185
+
186
+ KOL PnL leaderboard ranked by realized profit.
187
+
188
+ ```ts
189
+ const { leaderboard, period } = await client.kol.leaderboard({
190
+ period: "7d", // "today" | "7d" | "30d" | "90d" | "180d", default "7d"
191
+ });
192
+ ```
193
+
194
+ > **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.
195
+
196
+ Each `KolLeaderboardEntry` includes `median_hold_minutes_30d` (median position hold duration in minutes over the last 30 days) and `percentile_early_entry_30d` (early-entry percentile rank 0–100 over the last 30 days).
197
+
198
+ Returns: `KolLeaderboardResponse`
199
+
200
+ ---
201
+
202
+ #### `client.kol.wallet(wallet, params?)`
203
+
204
+ Full profile for a single KOL wallet, including trade history and optional per-token PnL breakdown.
205
+
206
+ ```ts
207
+ const profile = await client.kol.wallet("7xKX...", {
208
+ include: "pnl_by_token",
209
+ });
210
+ ```
211
+
212
+ Returns: `KolWalletProfile`
213
+
214
+ ---
215
+
216
+ #### `client.kol.coordination(params?)`
217
+
218
+ 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.
219
+
220
+ ```ts
221
+ const { coordination, score_version, window_minutes } = await client.kol.coordination({
222
+ period: "24h", // "1h" | "6h" | "24h" | "7d", default "24h"
223
+ min_kols: 3, // 2–50, default 3
224
+ limit: 20, // 1–50, default 20
225
+ window_minutes: 15, // v1.1 — peak-density window in minutes (1–60)
226
+ min_score: 60, // v1.1 — filter by composite score (0–100)
227
+ include_majors: false, // v1.1 — include WIF/BONK/POPCAT
228
+ });
229
+
230
+ for (const c of coordination) {
231
+ console.log(c.token_symbol, "score", c.coordination_score, "peak", c.peak_kols, "exited", c.exited_count);
232
+ // c.kols[]: { name, wallet, buy_sol, sell_sol, exited }
233
+ }
234
+ ```
235
+
236
+ Returns: `KolCoordinationResponse` — `{ coordination: CoordinatedToken[], score_version, window_minutes }`
237
+
238
+ ---
239
+
240
+ #### `client.coordinationAlerts.*` (v1.1)
241
+
242
+ 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.**
243
+
244
+ ```ts
245
+ // Create a rule: ≥5 KOLs, 10-min window, score ≥70, webhook delivery
246
+ const { rule, webhook_secret } = await client.coordinationAlerts.create({
247
+ name: "strong-clusters",
248
+ min_kols: 5,
249
+ window_minutes: 10,
250
+ min_score: 70,
251
+ include_majors: false,
252
+ cooldown_min: 30, // don't re-fire same token within 30 min
253
+ score_jump_break: 15, // UNLESS score jumps by 15+ (catches conviction surges)
254
+ delivery_mode: "webhook", // "websocket" | "webhook" | "both"
255
+ webhook_url: "https://example.com/coord-hook",
256
+ });
257
+ // SAVE webhook_secret — used for HMAC-SHA256 signature verification.
258
+
259
+ await client.coordinationAlerts.list();
260
+ await client.coordinationAlerts.get(rule.id);
261
+ await client.coordinationAlerts.update(rule.id, { min_score: 80, is_active: false });
262
+ await client.coordinationAlerts.delete(rule.id);
263
+ ```
264
+
265
+ Webhook signatures: header `X-MadeOnSol-Signature` = `sha256(timestamp + "." + body)` with `webhook_secret` as the HMAC key. Reject deliveries older than ~5 min.
266
+
267
+ 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).
268
+
269
+ ---
270
+
271
+ #### `client.priceAlerts.*` *(new in 2.8)*
272
+
273
+ **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.**
274
+
275
+ ```ts
276
+ // Create: alert when token drops 20%, then notify when it recovers 15% from the dip low
277
+ const { alert, webhook_secret } = await client.priceAlerts.create({
278
+ token_mint: "So11111111111111111111111111111111111111112",
279
+ drop_pct: 20,
280
+ recovery_pct: 15,
281
+ name: "SOL dip tracker",
282
+ delivery_mode: "webhook",
283
+ webhook_url: "https://example.com/dip-hook",
284
+ });
285
+
286
+ await client.priceAlerts.list();
287
+ await client.priceAlerts.get(alert.id);
288
+ await client.priceAlerts.update(alert.id, { name: "Renamed", is_active: false });
289
+ await client.priceAlerts.delete(alert.id);
290
+
291
+ // Event history (30-day retention)
292
+ const { events } = await client.priceAlerts.events({ event_type: "dip", limit: 50 });
293
+ ```
294
+
295
+ 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.
296
+
297
+ WebSocket: subscribe to channel `price_alert:events` — user-scoped. Webhook: per-alert HMAC-SHA256 signed (same scheme as coordination alerts).
298
+
299
+ ---
300
+
301
+ #### `client.sniper.*` Deshred Sniper Alerts *(new in 2.9)*
302
+
303
+ **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.
304
+
305
+ ```ts
306
+ // Newest-first deshred deploy feed (PRO: elite/good · ULTRA: all tiers)
307
+ const { deploys } = await client.sniper.recent({ limit: 50, min_bond_rate: 0.5 });
308
+
309
+ // Audit one deployer's recent launches (ULTRA)
310
+ await client.sniper.byDeployer("7dEx...4pQ8");
311
+
312
+ // Custom watchlist — get deploys from only the deployers you track, any tier (ULTRA, max 50)
313
+ await client.sniper.addToWatchlist({ wallets: ["7dEx...4pQ8", "9aBc...2zZ1"], label: "alpha devs" });
314
+ await client.sniper.watchlist();
315
+ const { deploys: tracked } = await client.sniper.recent({ watchlist: true });
316
+ await client.sniper.removeFromWatchlist("7dEx...4pQ8");
317
+ ```
318
+
319
+ 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 or the `sniper:deploys` WebSocket channel. ~1–3% of detected deploys may abandon before settlement.
320
+
321
+ **v2.20** — each deploy also carries a `footprint` block (`SniperFootprint | null`): the slot-window snipe rollup for slots [-1..+3] around the deploy — `buys`, `buyers`, `sol`, `supply_pct`, `sniper_wallet_buys`, `data_available`, `as_of`. `null` until the ~10-min settle window has passed (or when the mint is outside the pump.fun-pipeline write-gate) — absent, not zero.
322
+
323
+ ---
324
+
325
+ #### `client.kol.scoutLeaderboard(params?)` *(new in 2.8)*
326
+
327
+ Scout leaderboard: top KOLs ranked by scout score, first-touch frequency, and swarm attraction rate. **ULTRA only.**
328
+
329
+ ```ts
330
+ const data = await client.kol.scoutLeaderboard({ limit: 20, scout_tier: "S", sort: "scout_score" });
331
+ ```
332
+
333
+ ---
334
+
335
+ #### `client.kol.coordinationHistory(params?)` *(new in 2.8)*
336
+
337
+ Historical coordination alert fires — past events with token, score, KOL count. **ULTRA only.**
338
+
339
+ ```ts
340
+ const data = await client.kol.coordinationHistory({ limit: 50, min_score: 70 });
341
+ ```
342
+
343
+ ---
344
+
345
+ #### `client.token.kolConsensus(mint)` *(new in 2.8)*
346
+
347
+ KOL consensus on a token: how many bought/sold, exit rate, net flow, median entry MC. **ULTRA** gets individual wallet arrays.
348
+
349
+ ```ts
350
+ const consensus = await client.token.kolConsensus("4sVahM4U8js62mQV58ABSkNRhf6Ztc7Xs2LXUznNpump");
351
+ ```
352
+
353
+ ---
354
+
355
+ #### `client.token.peakHistory(mint)` *(new in 2.8)*
356
+
357
+ Peak MC history: ATH, decline from peak, MC at bond and at 1h/6h/24h/7d after bond.
358
+
359
+ ```ts
360
+ const peak = await client.token.peakHistory("4sVahM4U8js62mQV58ABSkNRhf6Ztc7Xs2LXUznNpump");
361
+ ```
362
+
363
+ ---
364
+
365
+ #### `client.kol.firstTouches(params?)` *(new in 2.2)*
366
+
367
+ 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.
368
+
369
+ **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](https://madeonsol.com/kol/scouts).
370
+
371
+ ```ts
372
+ // S-tier scouts on tokens younger than 1h
373
+ const { events } = await client.kol.firstTouches({
374
+ preset: "scout",
375
+ min_scout_tier: "S",
376
+ limit: 20,
377
+ });
378
+
379
+ for (const e of events) {
380
+ console.log(e.first_kol.name, "scouted", e.token_symbol, `(scout_score=${e.first_kol.scout_score}%)`);
381
+ }
382
+ ```
383
+
384
+ 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"`).
385
+
386
+ > **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_touches` WebSocket channel (PRO+) or, on Ultra, create an HMAC-signed webhook subscription via `client.firstTouchSubscriptions.create({...})`.
387
+
388
+ Returns: `FirstTouchesResponse`
389
+
390
+ ---
391
+
392
+ #### `client.firstTouchSubscriptions.*` *(Ultra)*
393
+
394
+ Create push-delivery rules for first-touch events. Up to 10 active subscriptions per Ultra user.
395
+
396
+ ```ts
397
+ const { subscription, webhook_secret } = await client.firstTouchSubscriptions.create({
398
+ name: "S-tier scouts on pump tokens",
399
+ filters: { min_scout_tier: "S", mint_suffix: "pump" },
400
+ delivery_mode: "webhook",
401
+ webhook_url: "https://my.bot/hooks/scout",
402
+ });
403
+ // store webhook_secret — shown once
404
+
405
+ await client.firstTouchSubscriptions.list();
406
+ await client.firstTouchSubscriptions.update(subscription.id, { is_active: false });
407
+ await client.firstTouchSubscriptions.delete(subscription.id);
408
+ ```
409
+
410
+ Same HMAC scheme as coordination alerts. WebSocket channel: `kol:first_touches`.
411
+
412
+ ---
413
+
414
+ #### `client.kol.token(mint)`
415
+
416
+ KOL buy/sell activity for a specific token mint.
417
+
418
+ ```ts
419
+ const activity = await client.kol.token("EPjFW...");
420
+ ```
421
+
422
+ Returns: `KolTokenActivity`
423
+
424
+ ---
425
+
426
+ #### `client.kol.pnl(wallet, params?)`
427
+
428
+ Deep per-wallet PnL breakdown with equity curve, risk metrics, and position history.
429
+
430
+ ```ts
431
+ const pnl = await client.kol.pnl("7xKX...", {
432
+ period: "30d", // "7d" | "30d" | "90d" | "180d", default "30d"
433
+ });
434
+ // All tiers: summary + equity curve + closed positions
435
+ // ULTRA: + open positions (tokens bought but not yet sold)
436
+ ```
437
+
438
+ Returns: `KolPnlResponse`
439
+
440
+ ---
441
+
442
+ #### `client.kol.trendingTokens(params?)`
443
+
444
+ Tokens ranked by KOL buy volume across multiple time windows.
445
+
446
+ ```ts
447
+ const { tokens } = await client.kol.trendingTokens({
448
+ period: "1h", // "5m" | "15m" | "30m" | "1h" | "4h" | "8h" | "12h", default "1h"
449
+ min_kols: 2, // minimum distinct KOL buyers
450
+ limit: 20, // 1–50, default 20
451
+ });
452
+ // Available on all tiers; ULTRA unlocks full KOL wallet addresses per token
453
+ ```
454
+
455
+ Returns: `KolTrendingTokensResponse`
456
+
457
+ ---
458
+
459
+ ### Alpha Wallet Intelligence — `client.alpha`
460
+
461
+ #### `client.alpha.leaderboard(params?)`
462
+
463
+ Leaderboard of 1M+ scored early-buyer wallets ranked by win rate, PnL, or ROI.
464
+
465
+ ```ts
466
+ const { wallets } = await client.alpha.leaderboard({
467
+ period: "30d", // "7d" | "30d" | "90d", default "30d"
468
+ sort: "win_rate", // "win_rate" | "pnl" | "roi"
469
+ min_tokens: 5,
470
+ exclude_bots: true,
471
+ });
472
+ // Up to 100 results on Free/Pro; ULTRA unlocks 500 + bot signals
473
+ ```
474
+
475
+ Returns: `AlphaLeaderboardResponse`
476
+
477
+ ---
478
+
479
+ #### `client.alpha.wallet(wallet)`
480
+
481
+ Full profile for an alpha wallet including per-token history and bot signals. ULTRA only.
482
+
483
+ ```ts
484
+ const profile = await client.alpha.wallet("7xKX...");
485
+ ```
486
+
487
+ Returns: `AlphaWalletResponse`
488
+
489
+ ---
490
+
491
+ #### `client.alpha.linked(wallet)`
492
+
493
+ Linked-wallet clustering — wallets that co-bought with this address within 2 seconds. ULTRA only.
494
+
495
+ ```ts
496
+ const { linked } = await client.alpha.linked("7xKX...");
497
+ ```
498
+
499
+ Returns: `AlphaLinkedResponse`
500
+
501
+ ---
502
+
503
+ #### `client.alpha.capTable(mint)`
504
+
505
+ First buyers for a token enriched with historical win rates, PnL, and KOL identity. PRO/ULTRA.
506
+
507
+ ```ts
508
+ const { buyers } = await client.alpha.capTable("EPjFW...");
509
+ ```
510
+
511
+ Returns: `AlphaCapTableResponse`
512
+
513
+ ---
514
+
515
+ #### `client.alpha.buyerQuality(mint)`
516
+
517
+ 0–100 cohort quality score based on the profile of a token's first buyers. All tiers. 5-minute cache.
518
+
519
+ ```ts
520
+ const { score } = await client.alpha.buyerQuality("EPjFW...");
521
+ ```
522
+
523
+ Returns: `AlphaBuyerQualityResponse`
524
+
525
+ ---
526
+
527
+ #### `client.alpha.risk(mint)`
528
+
529
+ Transparent 0–100 token rug-risk/safety score (higher = riskier). Returns a `band` (`safe`/`caution`/`danger`), an explainable `factors[]` array that sums into `risk_score`, and the raw `inputs` (mint/freeze authority revocation, liquidity USD + liquidity-to-MC ratio, transfer fee bps, Token-2022 flag, burn detection, launch cohort SOL + size, deployer bond rate + total deployed, KOL signal, blacklist status). **v2.20:** `inputs` also carries `sniper_footprint` (`SniperFootprint | null`) — the slot-window snipe rollup (`buys`/`buyers`/`sol`/`supply_pct`/`sniper_wallet_buys`/`data_available`/`as_of`). Informational: it does not move the score; `null` when not yet computed. PRO/ULTRA — BASIC receives HTTP 403.
530
+
531
+ ```ts
532
+ const { risk_score, band, factors } = await client.alpha.risk("EPjFW...");
533
+ if (band === "danger") return; // skip risky tokens
534
+ ```
535
+
536
+ Returns: `TokenRiskResponse`
537
+
538
+ ---
539
+
540
+ #### `client.alpha.bundle(mint)`
541
+
542
+ Bundle-cohort holdings — the wallets that bought a token together (one atomic transaction, `bundle_kind: "atomic_tx"`, or the same slot, `"same_slot"`) and, headline first, how much of supply they still hold. The `bundle` summary block (`held_pct_of_supply`, `bundle_kind`, `wallet_count`, `held_ratio`, `fully_exited`, `buy_volume`, `tokens_held`) is returned on **every** tier. BASIC/TRADER get `wallets: []`; PRO adds the top-10 cohort wallets with flags (`held_ratio`, `has_sold`, `atomic`, `is_kol`); ULTRA returns the full cohort plus identity (`kol_name`, `win_rate`, `bot_confidence`) and per-wallet `tokens_held`.
543
+
544
+ ```ts
545
+ const { bundle, wallets } = await client.alpha.bundle("EPjFW...");
546
+ if ((bundle.held_pct_of_supply ?? 0) > 0.2 && !bundle.fully_exited) return; // bundle still holds supply
547
+ ```
548
+
549
+ Returns: `TokenBundleResponse`
550
+
551
+ ---
552
+
553
+ #### `client.alpha.candles(mint, params?)`
554
+
555
+ OHLCV candlestick time-series — the persisted price/MC trajectory, rolled up to any timeframe on read. **PRO**: OHLCV (last 30 days). **ULTRA**: + per-bar net flow (buy/sell volume, `net_volume_usd`, counts, MEV volume), liquidity delta, and full retained history. Params: `tf` (`1m`|`5m`|`15m`|`1h`|`4h`|`1d`, default `1h`), `limit` (1–1000, default 200), `from`/`to` (ISO8601). `net_flow_included` flags whether the ULTRA fields are populated.
556
+
557
+ ```ts
558
+ const { candles, net_flow_included } = await client.alpha.candles("EPjFW...", { tf: "5m", limit: 100 });
559
+ const last = candles.at(-1);
560
+ console.log(last.close, net_flow_included ? `net flow $${last.net_volume_usd}` : "(ULTRA for net flow)");
561
+ ```
562
+
563
+ Returns: `CandlesResponse`
564
+
565
+ ---
566
+
567
+ #### `client.alpha.tokenFlow(mint, params?)`
568
+
569
+ Aggregated buy/sell flow for a token over a rolling window. **PRO+** (keyed). Params: `window` (`1h` default, or `24h`). Returns unique wallet/buyer/seller counts, buy/sell counts and SOL volumes, `net_sol` (`buy_sol − sell_sol`), and `trades_per_wallet`, plus the window `from` timestamp.
570
+
571
+ ```ts
572
+ const flow = await client.alpha.tokenFlow("EPjFW...", { window: "24h" });
573
+ console.log(`${flow.unique_wallets} wallets · net ${flow.net_sol} SOL`);
574
+ ```
575
+
576
+ Returns: `TokenFlowResponse`
577
+
578
+ ---
579
+
580
+ #### `client.alpha.tokenPools(mint)`
581
+
582
+ Per-venue liquidity map — every DEX pool a token trades in, each flagged live (`is_active`) or parked, with `liquidity_usd`, `last_price_sol`, `last_swap_at`, `dex`, `quote_mint`, and `amm_id`. The `summary` block rolls up `pool_count`/`active_pool_count`/`dex_count`, `dexes[]`, `total_liquidity_usd`, the `primary_pool`/`primary_dex`, and `top_pool_share_pct` (largest-pool concentration) — a fragmentation read on a token's liquidity. **PRO/ULTRA only** — BASIC receives HTTP 403.
583
+
584
+ ```ts
585
+ const { pools, summary } = await client.alpha.tokenPools("EPjFW...");
586
+ console.log(`${summary.active_pool_count}/${summary.pool_count} live across ${summary.dex_count} DEXs · top pool ${summary.top_pool_share_pct}%`);
587
+ ```
588
+
589
+ Returns: `TokenPoolsResponse`
590
+
591
+ ---
592
+
593
+ #### `client.alpha.holders(mint)`
594
+
595
+ Live holders, holder count + concentration (`GET /tokens/{mint}/holders`) 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**; `capTable` is who bought first. **PRO+** BASIC receives HTTP 403.
596
+
597
+ - `concentration.holder_count` is **exact** (distinct non-zero owners minus excluded pools/curves/burns, at `slot`) and `null` only when the provider refused the census for a mega-cap mint then `source.method` is `"getTokenLargestAccounts"` (top-20 view) and `source.census_fallback_reason` is set. It is never estimated from trades.
598
+ - `amount_raw` on every holder and excluded row is a raw u64 **string** never a float; use `BigInt()`. `amount` is the UI-scaled convenience number.
599
+ - Pools, bonding curves, burns and unattributed program accounts are **excluded** from the circulating denominator and listed in `excluded[]`, each named where possible: `reason` `pool` (+ `dex`, `pool_address`), `bonding_curve` (pump.fun / LaunchLab), `burn`, else `program_account`. The #1 raw account of a fresh memecoin is its own bonding curve. `concentration.pool_pct` / `burned_pct` / `program_pct` split them (over total supply).
600
+ - Disclosure is tier-gated: **PRO** ranks 1–10, **ULTRA** 150, **BUSINESS** 1–100 (`disclosed` tells you your cap); `top1/top10/top20/top50/top100_share`, the cohort `*_pct` values and `holder_count` are computed over the full set and are identical on every tier. All shares are 0–100.
601
+ - Each holder carries `labels[]` from MadeOnSol wallet intelligence (`deployer` / `kol` / `early_buyer` / `buyer` / `bundle` / `bot` / `dump_cluster`) plus `kol_name`, `early_buyer_rank`, `bot_confidence`, `historical_win_rate`. Empty labels = unknown to us, not verified clean.
602
+ - 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"` with `retry_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 throw `MadeOnSolError` with `status === 503`; inspect `error.body`. Unknown mint: 404 `error_kind: "not_a_mint"`.
603
+
604
+ ```ts
605
+ import { MadeOnSolError } from "madeonsol";
606
+
607
+ async function holders(mint: string) {
608
+ for (;;) {
609
+ try {
610
+ return await client.alpha.holders(mint);
611
+ } catch (e) {
612
+ const body = e instanceof MadeOnSolError ? (e.body as { error_kind?: string; retry_after_seconds?: number }) : null;
613
+ if (e instanceof MadeOnSolError && e.status === 503 && body?.error_kind === "holder_scan_in_progress") {
614
+ await new Promise((r) => setTimeout(r, (body.retry_after_seconds ?? 20) * 1000)); // scan is cached — retry is instant
615
+ continue;
616
+ }
617
+ throw e;
618
+ }
619
+ }
620
+ }
621
+
622
+ const { holders: top, concentration, excluded } = await holders("EPjFW...");
623
+ console.log(`${concentration.holder_count} holders · top10 ${concentration.top10_share}% of circulating`);
624
+ console.log(`bonding curve / pools hold ${concentration.pool_pct}% of supply (${excluded.length} excluded owners)`);
625
+ console.log(top[0].owner, BigInt(top[0].amount_raw), top[0].labels);
626
+ ```
627
+
628
+ Returns: `TokenHoldersResponse` (`TokenHolder`, `TokenHoldersExcluded`, `TokenHoldersConcentration`, `TokenHoldersDeployer`, `TokenHoldersSource`, `TokenHolderLabel`, `TokenHolderExcludedReason`, `TokenHoldersMethod`)
629
+
630
+ ---
631
+
632
+ ### Wallet Tracker — `client.walletTracker`
633
+
634
+ #### `client.walletTracker.watchlist()`
635
+
636
+ List your tracked wallets and remaining capacity.
637
+
638
+ ```ts
639
+ const { wallets, capacity } = await client.walletTracker.watchlist();
640
+ // capacity: { used, limit } — Free: 10, Pro: 50, Ultra: 100
641
+ ```
642
+
643
+ Returns: `WatchlistResponse`
644
+
645
+ ---
646
+
647
+ #### `client.walletTracker.addToWatchlist(wallet, params?)`
648
+
649
+ Add a wallet to your watchlist. Tracking begins immediately.
650
+
651
+ ```ts
652
+ await client.walletTracker.addToWatchlist("7xKX...", { label: "whale" });
653
+ ```
654
+
655
+ ---
656
+
657
+ #### `client.walletTracker.removeFromWatchlist(wallet)`
658
+
659
+ Remove a wallet from your watchlist.
660
+
661
+ ```ts
662
+ await client.walletTracker.removeFromWatchlist("7xKX...");
663
+ ```
664
+
665
+ ---
666
+
667
+ #### `client.walletTracker.updateLabel(wallet, label)`
668
+
669
+ Update the label for a tracked wallet.
670
+
671
+ ```ts
672
+ await client.walletTracker.updateLabel("7xKX...", "smart money");
673
+ ```
674
+
675
+ ---
676
+
677
+ #### `client.walletTracker.trades(params?)`
678
+
679
+ Historical swap and transfer events for your watched wallets. 120-day retention.
680
+
681
+ ```ts
682
+ const { events } = await client.walletTracker.trades({
683
+ wallet: "7xKX...", // filter by specific wallet
684
+ action: "buy", // "buy" | "sell"
685
+ event_type: "swap", // "swap" | "transfer"
686
+ limit: 50,
687
+ before: "2026-04-01T00:00:00Z", // ISO 8601 cursor
688
+ });
689
+ ```
690
+
691
+ Returns: `WalletTrackerTradesResponse`
692
+
693
+ ---
694
+
695
+ #### `client.walletTracker.summary(params?)`
696
+
697
+ Per-wallet stats across your watchlist: swap counts, SOL bought/sold, last event time.
698
+
699
+ ```ts
700
+ const { wallets } = await client.walletTracker.summary({
701
+ period: "7d", // "24h" | "7d" | "30d", default "7d"
702
+ wallet: "7xKX...", // optional: single wallet
703
+ });
704
+ ```
705
+
706
+ Returns: `WalletTrackerSummaryResponse`
707
+
708
+ ---
709
+
710
+ ### Universal Wallet `client.wallet` *(new in 2.7)*
711
+
712
+ 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.
713
+
714
+ **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.
715
+
716
+ #### `client.wallet.stats(address)`
717
+
718
+ 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. **v2.20** adds reputation flags to `flags`: `is_sniper`, `is_bundler` (lifetime), `is_dumper` (rolling 42d), and `dump_cluster` cohort stats — pump.fun-pipeline scoped, so `false` means "not observed", NOT verified clean. **v2.20 type fix:** `flags.bot_confidence` is a string enum (`"none" | "low" | "medium" | "high" | null`), not a number — the old `number | null` typing never matched a real value (the API returned `null` unconditionally due to a bug, now fixed).
719
+
720
+ ```ts
721
+ const { stats, flags, derived } = await client.wallet.stats("ASVz...ybJk");
722
+ console.log(`${flags.kol_name ?? address}: ${stats?.total_trades} trades`);
723
+ if (derived?.verdict) {
724
+ console.log(`${derived.verdict.label}: ${derived.verdict.description}`);
725
+ }
726
+ if (derived?.biggest_miss) {
727
+ console.log(`Biggest miss: ${derived.biggest_miss.token_symbol} — missed +${derived.biggest_miss.missed_sol.toFixed(1)} SOL`);
728
+ }
729
+ ```
730
+
731
+ Returns: `WalletStatsResponse` (404 if the wallet has no trades and no flag-table presence).
732
+
733
+ ---
734
+
735
+ #### `client.wallet.pnl(address)`
736
+
737
+ 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.
738
+
739
+ ```ts
740
+ const pnl = await client.wallet.pnl("ASVz...ybJk");
741
+ console.log(`Realized: ${pnl.summary.realized_sol} SOL · Unrealized: ${pnl.summary.unrealized_sol} SOL`);
742
+ console.log(`Win rate: ${(pnl.summary.win_rate! * 100).toFixed(1)}% · PF: ${pnl.summary.profit_factor}`);
743
+ for (const c of pnl.closed_positions.slice(0, 5)) {
744
+ const sign = c.pnl_sol > 0 ? "+" : "";
745
+ console.log(` ${c.token_mint.slice(0,8)}… ${sign}${c.pnl_sol} SOL (${c.roi_pct}% ROI, ${c.hold_minutes}m hold)`);
746
+ }
747
+ ```
748
+
749
+ Returns: `WalletPnlResponse`. Cache hits include `cache_hit: true` + `computed_at`; misses include `ttl_seconds`.
750
+
751
+ ---
752
+
753
+ #### `client.wallet.positions(address)`
754
+
755
+ Open positions only — lighter slice of `pnl()`. Shares the same cache, so calling this right after `pnl()` is an immediate hit.
756
+
757
+ ```ts
758
+ const { positions } = await client.wallet.positions("ASVz...ybJk");
759
+ for (const p of positions) {
760
+ const u = p.unrealized_sol;
761
+ console.log(` ${p.token_mint.slice(0,8)}… cost ${p.cost_basis_sol} SOL unrealized ${u ?? "—"} SOL (${p.unrealized_pct ?? "—"}%)`);
762
+ }
763
+ ```
764
+
765
+ Returns: `WalletPositionsResponse`. Mints without a current price return `unrealized_sol: null` rather than fabricated zero.
766
+
767
+ ---
768
+
769
+ #### `client.wallet.holdings(address, params?)`
770
+
771
+ Verified **current** on-chain holdings — reads the wallet's actual SPL + Token-2022 token accounts and SOL balance straight from chain, enriches each with our price/MC/name/symbol, and computes a `transfer_delta` (on-chain `amount` − trade-derived net position). A nonzero `transfer_delta` exposes tokens that arrived or left **without a swap** — airdrops, insider funding, wallet-hopping. Distinct from `positions()` (trade-derived FIFO): holdings is "what they actually hold right now". **ULTRA only.**
772
+
773
+ ```ts
774
+ const h = await client.wallet.holdings("ASVz...ybJk", { min_value_usd: 10 });
775
+ console.log(`${h.summary.non_zero} tokens · $${h.summary.total_value_usd} · ${h.sol_balance} SOL`);
776
+ for (const t of h.holdings) {
777
+ const d = t.transfer_delta;
778
+ const flag = d && d > 0 ? " ⬅ arrived without a swap" : "";
779
+ console.log(` ${t.symbol ?? t.mint.slice(0,8)}… ${t.amount} ($${t.value_usd ?? "—"})${flag}`);
780
+ }
781
+ ```
782
+
783
+ Params:
784
+ - `limit` — 1-500, default 200
785
+ - `min_value_usd` — number ≥0, default 0 (minimum USD value per holding to include)
786
+
787
+ Returns: `WalletHoldingsResponse` — `holdings[]` (typed `Holding`), `sol_balance`, `summary` (`token_accounts` / `non_zero` / `returned` / `priced` / `total_value_usd` / `truncated`), `verified_at`, `trade_window_days`, `cache_hit`, `ttl_seconds`.
788
+
789
+ ---
790
+
791
+ #### `client.wallet.trades(address, params?)`
792
+
793
+ Cursor-paginated raw trades. Default window is the last 90 days; override via `since` / `until` (Unix epoch seconds). Default limit 100, max 500.
794
+
795
+ ```ts
796
+ let cursor: string | undefined;
797
+ while (true) {
798
+ const page = await client.wallet.trades("ASVz...ybJk", { limit: 200, cursor, action: "buy" });
799
+ for (const t of page.trades) processBuy(t);
800
+ if (!page.has_more) break;
801
+ cursor = page.next_cursor!;
802
+ }
803
+ ```
804
+
805
+ Params:
806
+ - `limit` — 1-500, default 100
807
+ - `cursor` — from `next_cursor` of previous response
808
+ - `action` `"buy"` or `"sell"`
809
+ - `token_mint` — filter to one token
810
+ - `since` / `until` Unix epoch seconds (default last 90d)
811
+
812
+ Returns: `WalletTradesResponse` with `trades[]` + `next_cursor` + `has_more` + `filters` echo.
813
+
814
+ ---
815
+
816
+ #### `client.wallet.batchClassify(wallets)` *(new in 2.20 PRO+)*
817
+
818
+ Bulk wallet reputation flags 1–100 addresses in one request (`POST /wallet/batch/classify`). Each entry carries the same flag values as the `flags` block of `stats()`: `is_sniper`, `is_bundler`, `is_dumper`, `is_kol` + `kol_name`, `bot_confidence` (`"none"`/`"low"`/`"medium"`/`"high"` or null), and `dump_cluster` (`{ dump_cohorts, runner_cohorts, total_cohorts, as_of }` or null).
819
+
820
+ **Semantics** — flags are pump.fun-pipeline scoped: `false` means the behavior was **not observed** by our pipeline, NOT that the wallet is verified clean. `is_bundler` is a lifetime flag; `is_dumper` is a rolling 42-day window.
821
+
822
+ ```ts
823
+ const { wallets, as_of } = await client.wallet.batchClassify([buyerA, buyerB, buyerC]);
824
+ for (const w of wallets) {
825
+ const tags = [w.is_sniper && "sniper", w.is_bundler && "bundler", w.is_dumper && "dumper", w.is_kol && `KOL ${w.kol_name}`].filter(Boolean);
826
+ console.log(`${w.address.slice(0, 8)}… ${tags.join(" · ") || "clean-ish (not observed)"} bot=${w.bot_confidence ?? "?"}`);
827
+ }
828
+ ```
829
+
830
+ Returns: `WalletBatchClassifyResponse` — `{ wallets: WalletClassification[], count, as_of }`.
831
+
832
+ ---
833
+
834
+ ### Deployer Hunter — `client.deployer`
835
+
836
+ #### `client.deployer.stats()`
837
+
838
+ Global statistics across all tracked deployer wallets.
839
+
840
+ ```ts
841
+ const stats = await client.deployer.stats();
842
+ console.log(stats.overall_bonding_rate); // e.g. 0.043
843
+ ```
844
+
845
+ Returns: `DeployerStats`
846
+
847
+ ---
848
+
849
+ #### `client.deployer.leaderboard(params?)`
850
+
851
+ Deployers ranked by bonding rate or recent performance.
852
+
853
+ ```ts
854
+ const { deployers } = await client.deployer.leaderboard({
855
+ tier: "elite", // "elite" | "good" | "moderate" | "rising" | "cold"
856
+ sort: "bonding_rate", // "bonding_rate" | "recent_bond_rate" | "total_bonded" | "last_deploy_at"
857
+ limit: 20, // 1–50, default 20
858
+ offset: 0,
859
+ });
860
+ ```
861
+
862
+ Returns: `DeployerLeaderboardResponse`
863
+
864
+ ---
865
+
866
+ #### `client.deployer.profile(wallet)`
867
+
868
+ Full profile for a single deployer wallet.
869
+
870
+ ```ts
871
+ const deployer = await client.deployer.profile("3xAB...");
872
+ console.log(deployer.tier, deployer.bonding_rate);
873
+ ```
874
+
875
+ Returns: `DeployerProfile`
876
+
877
+ ---
878
+
879
+ #### `client.deployer.tokens(wallet, params?)`
880
+
881
+ All tokens deployed by a specific wallet.
882
+
883
+ ```ts
884
+ const { tokens } = await client.deployer.tokens("3xAB...", {
885
+ limit: 20,
886
+ offset: 0,
887
+ });
888
+ ```
889
+
890
+ Returns: `DeployerTokensResponse`
891
+
892
+ ---
893
+
894
+ #### `client.deployer.alerts(params?)`
895
+
896
+ Real-time deploy alerts — fired when a tracked deployer launches a new token.
897
+
898
+ ```ts
899
+ const { alerts } = await client.deployer.alerts({
900
+ since: "2025-01-01T00:00:00Z", // ISO 8601
901
+ limit: 20,
902
+ tier: "elite", // "elite" | "good" | "moderate" | "rising" | "cold"
903
+ offset: 0,
904
+ });
905
+ ```
906
+
907
+ Each `DeployerAlert` carries the deploy details plus `deployer_sol_balance` — the deployer wallet's SOL balance at alert time (`number | null` when unknown).
908
+
909
+ Returns: `DeployerAlertsResponse`
910
+
911
+ ---
912
+
913
+ #### `client.deployer.alertStats(params?)`
914
+
915
+ Aggregated alert statistics by tier.
916
+
917
+ ```ts
918
+ const stats = await client.deployer.alertStats({ period: "7d" });
919
+ // "7d" | "30d" | "all", default "all"
920
+ ```
921
+
922
+ Returns: `DeployerAlertStats`
923
+
924
+ ---
925
+
926
+ #### `client.deployer.bestTokens(params?)`
927
+
928
+ Top-performing tokens from tracked deployers by peak market cap.
929
+
930
+ ```ts
931
+ const { tokens } = await client.deployer.bestTokens({
932
+ period: "7d", // "7d" | "30d" | "all", default "7d"
933
+ limit: 5, // 1–20, default 5
934
+ });
935
+ ```
936
+
937
+ Returns: `BestTokensResponse`
938
+
939
+ ---
940
+
941
+ #### `client.deployer.recentBonds(params?)`
942
+
943
+ Most recently bonded tokens from tracked deployers.
944
+
945
+ ```ts
946
+ const { bonds } = await client.deployer.recentBonds({ limit: 20 });
947
+ ```
948
+
949
+ Returns: `RecentBondsResponse`
950
+
951
+ ---
952
+
953
+ #### `client.deployer.deployerHistory(wallet, opts?)`
954
+
955
+ Daily reputation time-series for a deployer — one snapshot per `date` capturing the `tier`, `is_tracked` flag, `total_deployed`/`total_bonded`, `bonding_rate`, `recent_bond_rate`, `avg_peak_mc`, and `best_token_peak_mc` that were true on that day. Backtest "was this deployer elite when it launched token X?" without look-ahead bias. `opts.limit` is 1–365 daily snapshots (default 90).
956
+
957
+ ```ts
958
+ const { is_deployer, snapshots } = await client.deployer.deployerHistory("3xAB...", { limit: 180 });
959
+ const onLaunch = snapshots.find((s) => s.date === "2025-01-01");
960
+ console.log(onLaunch?.tier, onLaunch?.bonding_rate);
961
+ ```
962
+
963
+ Returns: `DeployerHistoryResponse`
964
+
965
+ ---
966
+
967
+ ### Token Intelligence `client.token`
968
+
969
+ Per-mint snapshots (price, MC, volume, deployer rep, KOL activity, blacklist flags, **v1.7 velocity windows + MEV-share**) and a filtered directory.
970
+
971
+ #### `client.token.get(mint)`
972
+
973
+ Comprehensive per-mint snapshot in one call. **ULTRA** also returns individual KOL wallet addresses in `top_buyers[]`.
974
+
975
+ ```ts
976
+ const { token } = await client.token.get("So11111111111111111111111111111111111111112");
977
+ console.log(token.price_usd, token.market_cap);
978
+ console.log(token.mc_change_pct?.["1h"]); // v1.7
979
+ console.log(token.mev_volume_pct?.["1h"]); // v1.7
980
+ ```
981
+
982
+ Invalid mints return a 400 with `code: "invalid_mint"`, `reason`, `received_length`, `example`, and `docs` URL no trial and error.
983
+
984
+ 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). **New in 2.12:** also returns `liquidity_to_mc_ratio` (liquidity_usd / market_cap), `launch_cohort_sol` (total SOL spent by the first-20 buyers), and `launch_cohort_size` (count of first-20 buyers, 0–20).
985
+
986
+ #### `client.token.batch(mints)`
987
+
988
+ Batch lookup up to 50 mints in one round-trip. ~10–20× cheaper than N sequential calls. Each item returns the same shape as `get()` — including `liquidity_to_mc_ratio`, `launch_cohort_sol`, and `launch_cohort_size` *(new in 2.12)*.
989
+
990
+ ```ts
991
+ const { tokens } = await client.token.batch(["mint1", "mint2", "mint3"]);
992
+ ```
993
+
994
+ Returns: `TokenBatchResponse`
995
+
996
+ #### `client.token.list(params?)` *(new in 2.6 PRO+)*
997
+
998
+ 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.
999
+
1000
+ **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`, `deployer_tier` (`elite`/`good`/`moderate`/`rising`/`cold`/`unranked`), `min_liq_mc_ratio`, `max_liq_mc_ratio`.
1001
+
1002
+ **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`.
1003
+
1004
+ **Sort** (`sort`): `mc_desc` (default), `mc_asc`, `last_trade_desc`, `liquidity_desc`, `cumulative_volume_desc`, plus *(new in 2.15)* the momentum sorts `mc_change_5m_desc`, `mc_change_1h_desc`, `volume_1h_desc`, and `trending` (composite recent-volume × positive-momentum rank, DB-native — paginates correctly with no over-fetch).
1005
+
1006
+ Each item in `tokens[]` includes `liquidity_to_mc_ratio` and `deployer_tier` *(new in 2.12)*.
1007
+
1008
+ ```ts
1009
+ // Momentum scanner: liquid mints up >20% in 1h, low bot share
1010
+ const { tokens, pagination } = await client.token.list({
1011
+ min_liq: 10000,
1012
+ min_volume_1h_usd: 5000,
1013
+ max_mev_share_pct: 60,
1014
+ mc_change_1h_min_pct: 20,
1015
+ sort: "mc_desc",
1016
+ limit: 50,
1017
+ });
1018
+
1019
+ // Cleanest filter for a sane "top by MC" feed
1020
+ const { tokens } = await client.token.list({
1021
+ min_liq: 25000,
1022
+ active_h: 1,
1023
+ authority_revoked: true,
1024
+ sort: "mc_desc",
1025
+ });
1026
+ ```
1027
+
1028
+ Returns: `TokenListResponse` (with `tokens[]`, `pagination`, `filters` echo)
1029
+
1030
+ #### `client.token.almostBonded(params?)` *(new in 2.15 PRO+)*
1031
+
1032
+ Pre-bond pump.fun tokens approaching graduation, ranked by **velocity** (Δprogress/min) "95% and accelerating" beats "92% stalled". Each token is enriched with its deployer's reputation tier.
1033
+
1034
+ **Params** (all optional): `min_progress` (default 80), `max_progress` (default 99.99), `min_velocity_pct_per_min`, `max_age_minutes`, `deployer_tier` (`elite`/`good`/`moderate`/`rising`/`cold`/`unranked`), `authority_revoked`, `min_liq`, `sort` (`velocity_desc` default / `progress_desc` / `eta_asc`), `limit` (1–100, default 50).
1035
+
1036
+ Each item in `tokens[]`: `mint`, `symbol`, `name`, `progress_pct`, `velocity_pct_per_min` (null until a 5m snapshot exists), `eta_minutes` (linear projection), `stalled`, `real_sol_reserves`, `market_cap_usd`, `liquidity_usd`, `authorities_revoked`, `deployer_tier`, `age_minutes`.
1037
+
1038
+ ```ts
1039
+ // Tokens >90% bonded, accelerating, from a credible deployer — soonest first
1040
+ const { tokens } = await client.token.almostBonded({
1041
+ min_progress: 90,
1042
+ min_velocity_pct_per_min: 0.5,
1043
+ deployer_tier: "elite",
1044
+ sort: "eta_asc",
1045
+ limit: 25,
1046
+ });
1047
+ ```
1048
+
1049
+ Returns: `AlmostBondedResponse` (with `tokens[]`, `filters`, `returned`, `note`)
1050
+
1051
+ #### `client.token.batchBuyerQuality(mints)`
1052
+
1053
+ Batch buyer-quality scoring for up to 50 mints. Shares the same 5-minute LRU cache as `client.alpha.buyerQuality(mint)`.
1054
+
1055
+ Returns: `AlphaBuyerQualityBatchResponse`
1056
+
1057
+ #### `client.token.batchRisk(mints)` *(new in 2.17 PRO+)*
1058
+
1059
+ Batch token risk scoring for up to 50 mints in a single request that **counts as 1** against your quota — each item is the same transparent 0–100 rug-risk result as `client.alpha.risk(mint)` (with `band`, `factors[]`, `inputs`, `score_version`, `as_of`). Untracked mints come back as `{ mint, error: "not_tracked" }` without failing the batch. `tokens` preserves de-duplicated input order; `count` is the number of unique mints. **PRO/ULTRA only.**
1060
+
1061
+ ```ts
1062
+ const { tokens, count } = await client.token.batchRisk(["mint1", "mint2", "mint3"]);
1063
+ for (const t of tokens) {
1064
+ if ("error" in t) continue; // untracked / per-mint failure
1065
+ if (t.band === "danger") console.log(`skip ${t.mint} (${t.risk_score})`);
1066
+ }
1067
+ ```
1068
+
1069
+ Returns: `TokenRiskBatchResponse` — `{ tokens: (TokenRiskResponse | TokenRiskBatchError)[], count }`
1070
+
1071
+ ---
1072
+
1073
+ #### `client.token.trades(mint, params?)` *(new in 2.20 PRO+)*
1074
+
1075
+ Mint-scoped trade tape every captured trade for a token, cursor-paginated newest first (`GET /tokens/{mint}/trades`). Each trade carries `tx_signature`, `wallet_address`, `action`, `sol_amount`, `token_amount`, `price_sol`/`price_usd`, `early_buyer_rank`, `slot`, `block_time`, `traded_at`. Unlike `client.wallet.trades()` (90-day default), the default window here is the **full history**.
1076
+
1077
+ **Coverage honesty** — the tape starts 2026-04-12 and is pump.fun-pipeline scoped; the `coverage` block (`history_start`, `scope`) makes both visible on every response. Trades outside that pipeline are not on the tape.
1078
+
1079
+ ```ts
1080
+ let cursor: string | undefined;
1081
+ while (true) {
1082
+ const page = await client.token.trades(mint, { limit: 500, cursor, action: "buy" });
1083
+ for (const t of page.trades) processBuy(t);
1084
+ if (!page.has_more) break;
1085
+ cursor = page.next_cursor!;
1086
+ }
1087
+ ```
1088
+
1089
+ Params:
1090
+ - `limit` — 1-500, default 100
1091
+ - `cursor` — from `next_cursor` of previous response
1092
+ - `action` `"buy"` or `"sell"`
1093
+ - `wallet` — filter to one wallet address
1094
+ - `since` / `until` Unix epoch seconds (default: full history)
1095
+
1096
+ Returns: `TokenTradesResponse` with `trades[]` + `next_cursor` + `has_more` + `filters` echo + `coverage`.
1097
+
1098
+ ---
1099
+
1100
+ #### `client.token.locks(mint, params?)` *(new in 2.25 PRO+)*
1101
+
1102
+ Token locks & vesting on a mint (`GET /tokens/{mint}/locks`) every on-chain **Streamflow** stream, **Jupiter Lock** vesting escrow and **Bonfida** token-vesting contract, decoded from the locker programs' account state. Each row carries the schedule (`start_at` / `cliff_at` / `period_seconds` / `end_at`, `cliff_amount`, `amount_per_period`), the terms (`cancelable_by_sender` funds are locked against the *recipient*, not the locker; `cancelable_by_recipient`, `transferable`, `can_topup`) and a **live-derived** view computed at request time: `locked_raw` (still locked now), `unlocked`, `withdrawn`, `claimable`, `status` (active / completed / cancelled / closed) and `next_unlock` (cliff | period | final | tranche). `summary` rolls up `lock_count`, `active_count`, `by_program` / `by_kind`, `distinct_lockers`, locked / deposited totals (raw + ui + usd + % of supply), `unlocking_7d_*` / `unlocking_30d_*`, the nearest `next_unlock` and `active_cancelable_by_sender`. **PRO+** — BASIC receives HTTP 403.
1103
+
1104
+ - `*_raw` amounts are base-unit **strings** — never floats; use `BigInt()`. `amount` / `locked` / `*_usd` / `*_pct_of_supply` are `null` when decimals or price are unknown (`token.facts_resolved`).
1105
+ - **LP locks are NOT included** this is token / vesting locks only.
1106
+ - `status` / `program` narrow `locks[]` only; `summary` always covers every contract on the mint (`summary.complete` is false past 5000 contracts — totals then cover the newest 5000).
1107
+ - `created_at_estimated: true` marks a backfilled Jupiter Lock row with no on-chain creation time.
1108
+
1109
+ ```ts
1110
+ const { summary, locks } = await client.token.locks(mint, { status: "active" });
1111
+ console.log(`${summary.locked_pct_of_supply}% of supply locked · ${summary.unlocking_7d_usd} USD unlocks in 7d`);
1112
+ console.log(`${summary.active_cancelable_by_sender} active locks the locker can still cancel`);
1113
+ for (const l of locks) console.log(l.program, l.kind, BigInt(l.locked_raw), "until", l.end_at, l.cancelable_by_sender ? "(cancelable)" : "");
1114
+ ```
1115
+
1116
+ Params: `status` (active | completed | cancelled | closed), `program` (streamflow | jupiter_lock | bonfida_vesting), `limit` (1–500, default 200).
1117
+
1118
+ Returns: `TokenLocksResponse` (`TokenLock`, `TokenLocksSummary`, `TokenLockNextUnlock`, `TokenLockToken`, `TokenLockProgram`, `TokenLockKind`, `TokenLockStatus`, `TokenUnlockEventKind`)
1119
+
1120
+ ---
1121
+
1122
+ #### `client.token.locksFeed(params?)` *(new in 2.25 PRO+)*
1123
+
1124
+ Cross-token feed of **new** lock / vesting contracts, newest first (`GET /tokens/locks`) — who just locked tokens, of what mint, how much, until when. Rows are the same live-derived contract as `locks()` plus `token` (`symbol`, `decimals`, `price_usd`, `market_cap_usd`). Poll with `since = pagination.next_since`, page back with `before = pagination.next_before`, or subscribe to the **`token:locks`** WS channel (event `token:lock`) for a push the moment the contract lands on-chain. `min_usd` / `min_pct_of_supply` / `status` post-filter (×4 over-fetch, so a page may be shorter than `limit`). Backfilled Jupiter Lock rows are excluded unless `include_estimated: true`. **LP locks are NOT included.** **PRO+**.
1125
+
1126
+ ```ts
1127
+ let since: string | undefined;
1128
+ for (;;) {
1129
+ const page = await client.token.locksFeed({ since, min_usd: 10_000 });
1130
+ for (const l of page.locks) console.log(l.token.symbol, l.amount_usd, "USD locked until", l.end_at, "by", l.sender);
1131
+ since = page.pagination.next_since ?? since;
1132
+ await new Promise((r) => setTimeout(r, 30_000));
1133
+ }
1134
+ ```
1135
+
1136
+ Params: `since` / `before` (ISO date-time cursors), `mint`, `sender`, `recipient`, `program`, `kind` (lock | vesting), `status`, `min_usd`, `min_pct_of_supply` (0–100), `include_estimated` (boolean), `limit` (1–100, default 50).
1137
+
1138
+ Returns: `TokenLocksFeedResponse` (`TokenLockFeedEntry`, `TokenFeedPagination`, `TokenFeedStreamPointer`)
1139
+
1140
+ ---
1141
+
1142
+ #### `client.token.unlocks(params?)` *(new in 2.25 PRO+)*
1143
+
1144
+ Upcoming **unlock events** across all active lock / vesting contracts inside a window (`GET /tokens/unlocks`) — which tokens have locked supply hitting the market, how much, from whose lock. One entry per active contract = its **next** unlock event in the window (`event`: cliff | period | final | tranche) with `unlock_at` / `in_seconds` / `amount_*`, plus `window_amount_*` = that contract's total release over the whole window, the mint's `token` facts and the parent `lock` (subset of the `locks()` row). Continuous per-second streams (Streamflow payroll) contribute only their cliff / final events. **LP locks are NOT included.** **PRO+**.
1145
+
1146
+ ```ts
1147
+ const { window, unlocks } = await client.token.unlocks({ within: "24h", sort: "largest_usd", min_usd: 50_000 });
1148
+ console.log(window.from, "→", window.to);
1149
+ for (const u of unlocks) console.log(u.token.symbol, u.event, u.amount_usd, "USD in", u.in_seconds, "s —", u.lock.program, u.lock.sender);
1150
+ ```
1151
+
1152
+ Params: `within` (1h | 6h | 24h | 3d | 7d (default) | 14d | 30d | 90d), `mint`, `program`, `kind`, `min_usd`, `min_pct_of_supply`, `sort` (soonest (default) | largest_usd | largest_pct), `limit` (1–200, default 50).
1153
+
1154
+ Returns: `TokenUnlocksResponse` (`TokenUnlockEvent`, `TokenUnlocksWithin`)
1155
+
1156
+ ---
1157
+
1158
+ #### `client.token.feeShares(mint)` *(new in 2.25PRO+)*
1159
+
1160
+ pump.fun **creator-fee sharing** on a coin (`GET /tokens/{mint}/fee-shares`) — who its creator fees are redirected to. Decodes the on-chain `SharingConfig` (pump_fees PDA `["sharing-config", mint]`): `admin`, `status`, each shareholder's `share_bps` / `share_pct` with `is_admin` and `is_social_pda` (a SocialFeePda holds fees earmarked for a platform identity — `social.platform` 2 = X, `social.user_id` is the platform-native numeric id, **not** the handle — with `lifetime_claimed`), `redirected_bps` (share going to non-admin addresses), `social_bps` and `is_default` (100% to the creator — a real answer: pump creates one config per coin). Plus `distributions` (every `distribute_creator_fees` payout, pro-rata per shareholder; per-recipient `received_*` totals; `past_recipients` no longer in the split), `history` (config created / updated / reset, creator transferred) and `recent_distributions`. `config.source` is `"stream"` (our table — only non-default configs are stored) or `"chain"` (live PDA read; `config_error` set and `config` null if every RPC endpoint failed). Amounts are quote base units (SOL lamports unless a stable-quoted coin) as **strings**. **Event / distribution history starts 2026-08-17.** **PRO+**.
1161
+
1162
+ ```ts
1163
+ const fs = await client.token.feeShares(mint);
1164
+ if (fs.config?.is_default) console.log("100% of creator fees go to the creator");
1165
+ else for (const s of fs.config?.shareholders ?? []) console.log(s.address, s.share_pct, "%", s.social?.platform_label ?? "", "received", s.received_usd, "USD");
1166
+ console.log(fs.distributions.count, "distributions,", fs.distributions.total_usd, "USD since 2026-08-17");
1167
+ ```
1168
+
1169
+ Returns: `TokenFeeSharesResponse` (`TokenFeeSharingConfig`, `TokenFeeShareholder`, `TokenFeeSocialIdentity`, `TokenFeeShareHistoryEntry`, `TokenFeeDistribution`, `TokenFeeShareEntry`)
1170
+
1171
+ ---
1172
+
1173
+ #### `client.token.feeClaims(params?)` *(new in 2.25PRO+)*
1174
+
1175
+ pump.fun **fee-event feed**, newest first (`GET /tokens/fee-claims`). `type`s: `distribution` (creator fees paid out pro-rata to the SharingConfig shareholders — fees redirected to others — with `payouts[]` per address), `social_claim` (fees earmarked for a platform identity — 2 = X — claimed to a `recipient` wallet), `shares_created` / `shares_updated` / `shares_reset` (config changes), `creator_transferred`, and `creator_claim` (the plain creator vault claim — per creator, carries **no mint**; excluded unless you ask for it via `type`). Default 100%-to-creator configs and zero-amount distributions are not stored. Poll with `since = pagination.next_since` or subscribe to the **`token:fee_claims`** WS channel (event `token:fee_claim`). Amounts are quote base units as **strings** + `amount` / `amount_usd`. **History starts 2026-08-17.** **PRO+**.
1176
+
1177
+ ```ts
1178
+ const { events } = await client.token.feeClaims({ type: "distribution,social_claim", min_sol: 1 });
1179
+ for (const e of events) console.log(e.type, e.mint, e.amount, e.quote, e.recipient ?? `${e.payouts?.length} payouts`);
1180
+ ```
1181
+
1182
+ Params: `type` (comma list), `mint`, `recipient`, `actor`, `social_platform` (2 = X), `social_user_id`, `min_sol`, `since` / `before` (ISO date-time cursors), `limit` (1–100, default 50).
1183
+
1184
+ Returns: `TokenFeeClaimsResponse` (`TokenFeeClaimEvent`, `TokenFeePayout`, `TokenFeeEventType`, `TokenFeedPagination`, `TokenFeedStreamPointer`)
1185
+
1186
+ ---
1187
+
1188
+ #### `client.token.surges(params?)` *(new in 2.26PRO+)*
1189
+
1190
+ Token **momentum fires**, newest first (`GET /tokens/surges`). Two kinds. **`surge`** a token < 30 min old whose market cap runs hard vs its *launch* MC: tier `early` (≤ 10 min, $12k, launch MC), `strong` (≤ 30 min, $30k, launch **and** the lowest sample of the last 3 min — it is climbing *now*), `breakout` ( 2 min, $45k, ). Each tier fires at most once per mint; tiers are independent (a token can go straight to breakout). 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. **PRO+** — BASIC receives HTTP 403.
1191
+
1192
+ Every row carries `tape` (buys / sells / volume since birth or revival; `tape.source` = `candles` or `wallet_trades`, `tape.available: false` with nulls while no tape covers the window yet; `unique_buyers` / `trades_per_wallet` only when the mint is in `token_trades` coverage `wallet_data_available: false` otherwise, **never inferred zero**), `kol` (tracked-KOL buyers + names), `early_buyers` (first-20 cohort: bundled, cohort SOL, sold, sniper wallets), `deployer` (tier, bonding / runner rate, labeled tokens) and **`risk_flags[]`** — the honest half (thresholds echoed in `definitions.risk_flags`). Rows 65 min old carry `outcome` (`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: true` adds per-(kind, tier) hit-rates over `days` (`up_1h_pct`, `median_peak_multiple`, `doubled_1h_pct`) — out-of-sample by construction, the fire is recorded before the outcome exists. The live thresholds are echoed in `definitions` (read from the engine, so they cannot drift from what fires). Poll with `since = pagination.next_since`, or subscribe to the **`token:surges`** WS channel (events `token:surge` / `token:revival` the same object minus `outcome`).
1193
+
1194
+ - Nearly every scalar is `| null` — null means unknown, never zero. `tier` is null on revivals; `dormant_hours` / `prev_mc_usd` / `mc_vs_prev_multiple` are null on surges; `baseline_*` / `mc_multiple` / `mc_change_3m_pct` are null on revivals.
1195
+ - `launchpad` is the venue at *birth*, `primary_dex` where it trades at fire time — a pump token that graduated inside its first 10 min is `pumpfun` / `pumpswap`.
1196
+ - `tier` with `kind: "revival"` is a 400; an unknown flag in `exclude_flags` is a 400 with `known_flags[]`.
1197
+
1198
+ ```ts
1199
+ const { events, stats } = await client.token.surges({ kind: "surge", tier: "strong", exclude_flags: ["bundled_launch", "sniper_heavy"], stats: true });
1200
+ for (const e of events) {
1201
+ console.log(e.symbol, `$${e.market_cap_usd}`, `${e.mc_multiple}× launch`, e.tape.buys, "buys /", e.tape.unique_buyers ?? "n/a", "buyers", e.risk_flags, e.outcome ? `${e.outcome.peak_1h_multiple}× peak in 1h` : "outcome pending");
1202
+ }
1203
+ for (const r of stats?.rows ?? []) console.log(r.kind, r.tier, `${r.up_1h_pct}% up after 1h`, "median peak", r.median_peak_multiple, `(${r.with_outcome} fires)`);
1204
+
1205
+ // live
1206
+ const stream = await client.stream.connect();
1207
+ stream.subscribe(["token:surges"], { kinds: ["surge"], tiers: ["strong", "breakout"], exclude_flags: ["bundled_launch"], min_mc_usd: 30_000 } satisfies TokenSurgesSubscribeFilters);
1208
+ stream.on("token:surge", (d) => { const e = d as TokenSurgeStreamEvent; console.log(e.tier, e.symbol, e.market_cap_usd, e.risk_flags); });
1209
+ stream.on("token:revival", (d) => { const e = d as TokenSurgeStreamEvent; console.log("revived after", e.dormant_hours, "h", e.symbol, e.mc_vs_prev_multiple); });
1210
+ ```
1211
+
1212
+ Params: `kind` (surge | revival), `tier` (early | strong | breakout — surge only), `mint`, `since` / `before` (ISO date-time cursors), `min_mc_usd` / `max_mc_usd`, `min_buys`, `launchpad`, `deployer_tier` (elite | good | moderate | rising | cold | unranked), `exclude_flags` (array or comma list), `only_clean` (boolean), `stats` (boolean), `days` (1–30, default 7), `limit` (1–200, default 50).
1213
+
1214
+ Returns: `TokenSurgesResponse` (`TokenSurgeEvent`, `TokenSurgeStreamEvent`, `TokenSurgeTape`, `TokenSurgeKol`, `TokenSurgeEarlyBuyers`, `TokenSurgeDeployer`, `TokenSurgeOutcome`, `TokenSurgeStats`, `TokenSurgeStatsRow`, `TokenSurgesFilters`, `TokenSurgeDefinitions`, `TokenSurgeKind`, `TokenSurgeTier`, `TokenSurgeRiskFlag`, `TokenSurgeDeployerTier`, `TokenSurgesSubscribeFilters`, `TokenFeedPagination`, `TokenFeedStreamPointer`)
1215
+
1216
+ ---
1217
+
1218
+ ### Account `client.me()` *(new in 2.6)*
1219
+
1220
+ 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).
1221
+
1222
+ ```ts
1223
+ const me = await client.me();
1224
+ console.log(`${me.tier}: ${me.quota.daily.remaining}/${me.quota.daily.limit} req left today`);
1225
+ console.log(`Webhooks: ${me.features.webhooks.used}/${me.features.webhooks.limit}`);
1226
+ console.log(`Copy-trade wallets: ${me.features.copytrade_wallets.used}/${me.features.copytrade_wallets.limit}`);
1227
+
1228
+ if (me.quota.daily.remaining < 100) {
1229
+ // self-throttle
1230
+ }
1231
+ ```
1232
+
1233
+ Returns: `MeResponse`
1234
+
1235
+ ---
1236
+
1237
+ ### Signal Performance `client.getSignalPerformance(name)` *(new in 2.12)*
1238
+
1239
+ Performance stats for a named signal: hit rate, precision, sample count, and lookback window.
1240
+
1241
+ ```ts
1242
+ const perf = await client.getSignalPerformance("kol_coordination");
1243
+ console.log(perf.precision, perf.hit_rate);
1244
+ ```
1245
+
1246
+ Params: `name` — signal name (e.g. `"kol_coordination"`, `"first_touch_scout"`, `"deployer_elite"`).
1247
+
1248
+ Returns: `Promise<unknown>` — shape varies by signal name; see `/api-docs` for the full schema.
1249
+
1250
+ ---
1251
+
1252
+ ### Tool Directory — `client.tools`
1253
+
1254
+ #### `client.tools.search(params?)`
1255
+
1256
+ Search 950+ Solana tools indexed on MadeOnSol.
1257
+
1258
+ ```ts
1259
+ const { tools, count } = await client.tools.search({
1260
+ q: "trading bot", // full-text search
1261
+ category: "trading", // category slug filter
1262
+ limit: 20, // 1–50, default 20
1263
+ });
1264
+ ```
1265
+
1266
+ Returns: `ToolsSearchResponse`
1267
+
1268
+ ---
1269
+
1270
+ ### WebSocket Streaming — `client.stream`
1271
+
1272
+ #### `client.stream.getToken()`
1273
+
1274
+ Issue your 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.
1275
+
1276
+ Stream tokens **never expire** (since 2.25.1): the same token comes back on every call until your subscription lapses or you pass `{ rotate: true }` to replace it (the previous value keeps working for 60 s). Send it as `Authorization: Bearer <token>` on the WebSocket handshake (`?token=` still works). A `4001` close means "mint again", never a timer.
1277
+
1278
+ ```ts
1279
+ const token = await client.stream.getToken();
1280
+ console.log(token.ws_url); // wss://madeonsol.com/ws/v1/stream
1281
+ console.log(token.dex_ws_url); // wss://madeonsol.com/ws/v1/dex-stream (Ultra only)
1282
+ console.log(token.expires_at); // null — never expires
1283
+ const fresh = await client.stream.getToken({ rotate: true }); // replace it
1284
+ ```
1285
+
1286
+ Returns: `StreamToken` `{ token, expires_at: null, next_refresh_at: null, rotated, lifetime, ws_url, dex_ws_url?, usage }`
1287
+
1288
+ #### `client.stream.connect()` *(new in 2.10)*
1289
+
1290
+ Open a **managed** stream — token fetch (the token never expires; `getToken()` is called on every (re)connect), auto-reconnect (backoff + jitter), heartbeat liveness, and typed events are handled for you. No need to touch `getToken()` or `ws` directly.
1291
+
1292
+ ```ts
1293
+ const stream = client.stream.connect();
1294
+ stream.on("kol:trade", (t) => console.log(t.token_symbol, t.action));
1295
+ stream.on("deployer:alert", (a) => console.log("new deploy", a.token_mint));
1296
+ stream.subscribe(["kol:trades", "deployer:alerts"]);
1297
+ // stream.unsubscribe([...]) / stream.close() when done
1298
+ ```
1299
+
1300
+ Channels: `kol:trades`, `kol:coordination`, `kol:first_touches`, `deployer:alerts`, `wallet_tracker:events`, `copytrade:signals`, `price_alert:events`, `sniper:deploys`, `token:graduations` (every pump.fun graduation in real time, tracked deployer or not — typed `GraduationEvent`), `token:locks` (**new 2.25** — event `token:lock` for every NEW Streamflow / Jupiter Lock / Bonfida lock or vesting contract, typed `TokenLockStreamEvent`; PRO+; updates are not pushed — poll `client.token.locks()`), `token:fee_claims` (**new 2.25** — event `token:fee_claim` for every pump.fun fee event: distributions, social-handle claims, config changes, typed `TokenFeeClaimStreamEvent`; PRO+), `token:surges` (**new 2.26**events `token:surge` / `token:revival` the moment a momentum fire is confirmed, typed `TokenSurgeStreamEvent` with `tape` / `kol` / `early_buyers` / `deployer` / `risk_flags[]`; server-side filters `kinds[]`, `tiers[]`, `launchpads[]`, `exclude_flags[]`, `min_mc_usd` / `max_mc_usd`, `deployer_tier[]` — typed `TokenSurgesSubscribeFilters`; PRO+; the +1 h `outcome` is REST-only — poll `client.token.surges()`). Lifecycle: `open`, `close`, `reconnect`, `heartbeat`, `error`. Node 22+ uses the global `WebSocket`; on Node < 22 also `npm i ws`.
1301
+
1302
+ #### `client.stream.sessions()` / `client.stream.deleteSession(id)` *(new in 2.17 PRO+)*
1303
+
1304
+ Audit and evict your **live** WebSocket sessions across the KOL/deployer (`ws-streaming`) and all-DEX (`dex-stream`) services. `sessions()` lists each open connection; `deleteSession(id)` force-closes one — handy for freeing a connection slot held by a ghost/stale socket after a network drop.
1305
+
1306
+ ```ts
1307
+ const { sessions, count } = await client.stream.sessions();
1308
+ for (const s of sessions) {
1309
+ console.log(s.id, s.service, s.channels, s.messages_sent);
1310
+ }
1311
+
1312
+ // Kill a stale session to free a slot
1313
+ const { evicted } = await client.stream.deleteSession(sessions[0].id);
1314
+ ```
1315
+
1316
+ `deleteSession()` returns `{ evicted: true, id }`; it throws a 404 if no live session has that id, or a 400 if `id` is not a positive integer. Types: `StreamSession`, `StreamSessionsResponse`, `StreamSessionEvictResponse`.
1317
+
1318
+ ---
1319
+
1320
+ ### DEX Firehose (Ultra) `wss://madeonsol.com/ws/v1/dex-stream`
1321
+
1322
+ 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.
1323
+
1324
+ **Limits:** ULTRA = 2 connections, **10 named subscriptions per connection**, up to **500 trades replay** from a server-side buffer holding ~5 minutes of firehose history (not connection-scoped — covers trades from before you connected; newest-first, sort by `block_time`). Inbound rate limit: 5 messages/sec (excess emits one error per second).
1325
+
1326
+ #### Quick start
1327
+
1328
+ ```ts
1329
+ import { WebSocket } from "ws"; // or native WebSocket in browsers/Bun
1330
+
1331
+ const { token, dex_ws_url } = await client.stream.getToken();
1332
+ const ws = new WebSocket(`${dex_ws_url}?token=${token}`); // token MUST be appended as query param
1333
+
1334
+ ws.on("open", () => {
1335
+ // Multi-subscription: each sub has its own sub_id and filters
1336
+ ws.send(JSON.stringify({
1337
+ type: "subscribe",
1338
+ sub_id: "fresh-pumpfun",
1339
+ replay: 50, // backfill up to 500 from ring buffer
1340
+ filters: {
1341
+ dex: "pumpfun",
1342
+ token_age_max_seconds: 300, // first seen in last 5 min
1343
+ min_sol: 0.5,
1344
+ action: "buy",
1345
+ },
1346
+ }));
1347
+ });
1348
+
1349
+ ws.on("message", (raw) => {
1350
+ const msg = JSON.parse(raw.toString());
1351
+ if (msg.channel === "dex:trades") {
1352
+ // { sub_id, data: { wallet, mint, action, sol_amount, token_amount, dex, ... }, replay, ts }
1353
+ console.log(msg.sub_id, msg.data.dex, msg.data.action, msg.data.sol_amount);
1354
+ }
1355
+ });
1356
+ ```
1357
+
1358
+ #### Protocol client server
1359
+
1360
+ | `type` | Required fields | Notes |
1361
+ |---|---|---|
1362
+ | `subscribe` | `sub_id`, `filters` | Optional `replay: 1–500` |
1363
+ | `update` | `sub_id`, `filters` | Replaces filters in place no disconnect needed |
1364
+ | `unsubscribe` | `sub_id` | Or omit `sub_id` to clear all subs |
1365
+ | `list` | — | Server replies with `{ type: "list", subs: [...] }` |
1366
+ | `ping` | | Heartbeat — server replies `{ type: "pong" }` |
1367
+
1368
+ #### Server → client message shapes
1369
+
1370
+ ```ts
1371
+ { type: "connected", tier: "ULTRA", capabilities: { max_subs: 10, max_replay: 500, dex_names: [...], deployer_tiers: [...] } } // on connect
1372
+ { type: "subscribed", sub_id: "fresh-pumpfun", filters: { ... } }
1373
+ { type: "replay_done", sub_id: "fresh-pumpfun", count: 50 } // after backfill
1374
+ { type: "updated", sub_id: "fresh-pumpfun", filters: { ... } }
1375
+ { type: "unsubscribed", sub_id: "fresh-pumpfun" }
1376
+ { type: "list", subs: [{ sub_id, filters }] } // reply to { type: "list" }
1377
+ { type: "heartbeat", ts: 1712160000000 } // every 30s
1378
+ { type: "error", sub_id?, message: "..." }
1379
+ { channel: "dex:trades", sub_id, data: { ... }, replay: false, ts: 1712160000000 }
1380
+ ```
1381
+
1382
+ #### Filter dimensions
1383
+
1384
+ At least **one targeting filter** is required (otherwise the firehose would dump every trade). Filters compose with AND semantics.
1385
+
1386
+ | Filter | Type | Notes |
1387
+ |---|---|---|
1388
+ | `token_mint` / `token_mints` | string / string[] (≤50) | Targeting |
1389
+ | `wallet` / `wallets` | string / string[] (≤50) | Targeting |
1390
+ | `dex` | string \| string[] | `pumpfun`, `pumpamm`, `pumpswap`, `raydium`, `jupiter`, `orca`, `meteora`, `launchlab` |
1391
+ | `program` | string | Raw program ID |
1392
+ | `deployer_tier` | string \| string[] | `elite`, `good`, `moderate`, `rising`, `cold`, `unranked` (uses Deployer Hunter scoring) |
1393
+ | `token_age_max_seconds` | number | Only trades on mints first seen within window (uses persisted first-seen table) |
1394
+ | `market_cap_min_sol` / `market_cap_max_sol` | number | Bounded by current market cap (last trade price × cached supply, 1h TTL) |
1395
+ | `min_sol` / `max_sol` | number | Trade size bounds |
1396
+ | `action` | `"buy"` \| `"sell"` | Direction |
1397
+
1398
+ **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.
1399
+
1400
+ #### Multi-sub example
1401
+
1402
+ ```ts
1403
+ ws.send(JSON.stringify({ type: "subscribe", sub_id: "snipers", filters: { token_age_max_seconds: 60 } }));
1404
+ ws.send(JSON.stringify({ type: "subscribe", sub_id: "whales", filters: { min_sol: 50 } }));
1405
+ ws.send(JSON.stringify({ type: "subscribe", sub_id: "kol-mints", filters: { token_mints: ["EPjF...", "So11..."] } }));
1406
+
1407
+ // Tighten the snipers filter without disconnecting
1408
+ ws.send(JSON.stringify({ type: "update", sub_id: "snipers", filters: { token_age_max_seconds: 30, min_sol: 0.3 } }));
1409
+
1410
+ // Drop whales when you're done
1411
+ ws.send(JSON.stringify({ type: "unsubscribe", sub_id: "whales" }));
1412
+ ```
1413
+
1414
+ Each `dex:trades` message echoes the `sub_id` that matched, so you can route them locally without reapplying filter logic client-side.
1415
+
1416
+ ---
1417
+
1418
+ ### Copy-Trade `client.copytrade` *(new in 2.10)*
1419
+
1420
+ Mirror N source wallets into actionable signals (delivered via webhook/WebSocket). PRO/ULTRA — PRO: 3 rules × 5 wallets, ULTRA: 20 × 50.
1421
+
1422
+ ```ts
1423
+ const { subscription, webhook_secret } = await client.copytrade.create({
1424
+ name: "whale mirror",
1425
+ source_wallets: ["WalletA…", "WalletB…"],
1426
+ sizing_mode: "fixed",
1427
+ sizing_amount: 0.5, // SOL per mirrored buy
1428
+ only_action: "buy",
1429
+ delivery_mode: "webhook",
1430
+ webhook_url: "https://you.example/hook",
1431
+ });
1432
+
1433
+ await client.copytrade.subscriptions(); // list rules
1434
+ await client.copytrade.update(subscription.id, { is_active: false });
1435
+ await client.copytrade.signals({ limit: 50 }); // 7-day fired-signal history
1436
+ await client.copytrade.delete(subscription.id);
1437
+ ```
1438
+
1439
+ ### Webhooks `client.webhooks`
1440
+
1441
+ Manage push notification webhooks for real-time events (Pro: 3, Ultra: 10).
1442
+
1443
+ ```ts
1444
+ // Create a webhook
1445
+ const webhook = await client.webhooks.create({
1446
+ url: "https://example.com/hook",
1447
+ events: ["kol:trade", "deployer:alert"],
1448
+ filters: { min_sol: 1 },
1449
+ });
1450
+
1451
+ // List, update, delete
1452
+ const { webhooks } = await client.webhooks.list();
1453
+ await client.webhooks.update(webhook.id, { status: "paused" });
1454
+ await client.webhooks.delete(webhook.id);
1455
+ await client.webhooks.test(webhook.id);
1456
+ ```
1457
+
1458
+ ---
1459
+
1460
+ ## Error handling
1461
+
1462
+ All methods throw `MadeOnSolError` on non-2xx responses.
1463
+
1464
+ ```ts
1465
+ import { MadeOnSol, MadeOnSolError } from "madeonsol";
1466
+
1467
+ try {
1468
+ const profile = await client.kol.wallet("invalid-wallet");
1469
+ } catch (err) {
1470
+ if (err instanceof MadeOnSolError) {
1471
+ console.error(err.message); // human-readable message
1472
+ console.error(err.status); // HTTP status code, e.g. 404
1473
+ console.error(err.body); // raw response body
1474
+ }
1475
+ }
1476
+ ```
1477
+
1478
+ ---
1479
+
1480
+ ## Exported types
1481
+
1482
+ All types are exported from the main entry point:
1483
+
1484
+ ```ts
1485
+ import type {
1486
+ // Errors
1487
+ MadeOnSolError,
1488
+
1489
+ // KOL
1490
+ KolTrade,
1491
+ KolFeedParams,
1492
+ KolFeedResponse,
1493
+ KolLeaderboardParams,
1494
+ KolLeaderboardResponse,
1495
+ KolLeaderboardEntry,
1496
+ KolWalletParams,
1497
+ KolWalletProfile,
1498
+ KolCoordinationParams,
1499
+ KolCoordinationResponse,
1500
+ CoordinatedToken,
1501
+ KolTokenActivity,
1502
+ KolPnlByToken,
1503
+
1504
+ // Deployer
1505
+ DeployerStats,
1506
+ DeployerLeaderboardParams,
1507
+ DeployerLeaderboardResponse,
1508
+ DeployerLeaderboardEntry,
1509
+ DeployerProfile,
1510
+ DeployerToken,
1511
+ DeployerTokensParams,
1512
+ DeployerTokensResponse,
1513
+ DeployerAlertsParams,
1514
+ DeployerAlertsResponse,
1515
+ DeployerAlert,
1516
+ DeployerAlertStatsParams,
1517
+ DeployerAlertStats,
1518
+ BestTokensParams,
1519
+ BestTokensResponse,
1520
+ BestToken,
1521
+ RecentBondsParams,
1522
+ RecentBondsResponse,
1523
+ RecentBond,
1524
+
1525
+ // Tools
1526
+ ToolsSearchParams,
1527
+ ToolsSearchResponse,
1528
+ Tool,
1529
+
1530
+ // KOL PnL & Trending
1531
+ KolPnlResponse,
1532
+ KolTrendingTokensResponse,
1533
+ TrendingToken,
1534
+
1535
+ // Alpha Wallet Intelligence
1536
+ AlphaWalletEntry,
1537
+ AlphaLeaderboardResponse,
1538
+ AlphaWalletResponse,
1539
+ AlphaLinkedResponse,
1540
+ AlphaCapTableResponse,
1541
+ AlphaBuyerQualityResponse,
1542
+
1543
+ // Wallet Tracker
1544
+ WalletEntry,
1545
+ WatchlistResponse,
1546
+ WalletTrackerEvent,
1547
+ WalletTrackerTradesResponse,
1548
+ WalletTrackerSummaryResponse,
1549
+
1550
+ // Wallet classification (v2.20)
1551
+ WalletClassification,
1552
+ WalletBatchClassifyResponse,
1553
+ BotConfidence,
1554
+ DumpClusterStats,
1555
+
1556
+ // Token trade tape (v2.20)
1557
+ TokenTradesParams,
1558
+ TokenTrade,
1559
+ TokenTradesResponse,
1560
+ TokenTradesCoverage,
1561
+
1562
+ // Sniper footprint (v2.20)
1563
+ SniperFootprint,
1564
+
1565
+ // Streaming
1566
+ StreamToken,
1567
+
1568
+ // Webhooks
1569
+ Webhook,
1570
+ WebhookCreateParams,
1571
+ WebhookUpdateParams,
1572
+ WebhookListResponse,
1573
+
1574
+ // Enums / unions
1575
+ KolAction,
1576
+ LeaderboardPeriod,
1577
+ CoordinationPeriod,
1578
+ DeployerTier,
1579
+ DeployerSortField,
1580
+ AlertPeriod,
1581
+ BestTokensPeriod,
1582
+ } from "madeonsol";
1583
+ ```
1584
+
1585
+ ---
1586
+
1587
+ ## Related
1588
+
1589
+ - [MadeOnSol website](https://madeonsol.com) — Browse 950+ Solana tools
1590
+ - [API documentation](https://madeonsol.com/api-docs) — Interactive endpoint reference
1591
+ - [Pricing & API keys](https://madeonsol.com/pricing) — Free tier: 200 requests/day, no signup payment
1592
+ - [MadeOnSol on GitHub](https://github.com/madeonsol/madeonsol) — Main project repository
1593
+
1594
+ ## Also Available
1595
+
1596
+ | Platform | Package |
1597
+ |---|---|
1598
+ | Rust | [`madeonsol`](https://crates.io/crates/madeonsol) on crates.io |
1599
+ | Python (LangChain, CrewAI) | [`madeonsol-x402`](https://pypi.org/project/madeonsol-x402/) on PyPI |
1600
+ | MCP Server (Claude, Cursor) | [`mcp-server-madeonsol`](https://www.npmjs.com/package/mcp-server-madeonsol) · [Smithery](https://smithery.ai/servers/madeonsol/solana-kol-intelligence) · [Glama](https://glama.ai/mcp/servers/LamboPoewert/mcp-server-madeonsol) |
1601
+ | ElizaOS | [`@madeonsol/plugin-madeonsol`](https://www.npmjs.com/package/@madeonsol/plugin-madeonsol) |
1602
+ | Solana Agent Kit | [`solana-agent-kit-plugin-madeonsol`](https://www.npmjs.com/package/solana-agent-kit-plugin-madeonsol) |
1603
+
1604
+ ---
1605
+
1606
+ ## License
1607
+
1608
+ MIT © [MadeOnSol](https://madeonsol.com)