madeonsol-x402 2.1.0 β†’ 2.3.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,525 +1,570 @@
1
- # madeonsol-x402
2
-
3
- [![npm version](https://img.shields.io/npm/v/madeonsol-x402?style=flat-square)](https://www.npmjs.com/package/madeonsol-x402)
4
- [![npm downloads](https://img.shields.io/npm/dm/madeonsol-x402?style=flat-square)](https://www.npmjs.com/package/madeonsol-x402)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.4+-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](LICENSE)
7
-
8
- > πŸ“‚ **[Examples](./examples/)** Β· πŸ“š **[API docs](https://madeonsol.com/api-docs)** Β· πŸ’° **[Get a free API key](https://madeonsol.com/pricing)**
9
-
10
- TypeScript SDK for the [MadeOnSol](https://madeonsol.com) Solana KOL intelligence API.
11
-
12
- <!-- Stats below are deliberate conservative floors kept in sync with the site's canonical labels (src/lib/constants.ts KOL_COUNT_LABEL / DEPLOYERS_PROFILED_LABEL / ALPHA_WALLETS_LABEL), rounded down from a live count measured on a known date and bumped only when the real count crosses the next threshold -- never the exact live number, which changes every minute. Do not replace with a live/volatile count. -->
13
-
14
- > Real-time Solana trading intelligence: track 2,000+ KOL wallets with <3s latency on paid keys and x402 pay-per-call (free-tier live feeds are 5-min delayed), score 85K+ Pump.fun deployers, surface deshred deploy signals ~500ms before on-chain confirmation, score 1.5M+ early-buyer wallets (incl. dump-cluster detection), read bundle-cohort holdings (`held_pct_of_supply` β€” are the bundlers still holding?), verify any wallet's current on-chain holdings (with airdrop/insider `transfer_delta` detection), push every pump.fun graduation, and stream every DEX trade. 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).
15
-
16
- > **New in 2.0.0 β€” BREAKING for keyless (x402) mode only: an explicit `paymentPolicy` is required (security fix, SDK-01).** `new MadeOnSolX402({ privateKey, paymentPolicy: { payTo, feePayer, maxAmountAtomic, maxTotalAmountAtomic, rpcUrl } })` (optional `beforePayment`); the deprecated `MadeOnSolX402Options` type gains the same required field. Before, keyless mode signed whatever Solana USDC amount, recipient and fee payer a 402 challenge asked for. Now every challenge is checked BEFORE signing against a trusted merchant `payTo`, a trusted facilitator `feePayer` (which must differ from your wallet), the USDC mint, `solana:5eykt…` mainnet, the `exact` scheme, a per-call cap and a lifetime cap. Use the canonical values in the keyless section below; caps must be at least `20000` (0.02 USDC) per call to reach every endpoint. The budget is per client instance / process: not wallet-wide, not shared between processes, reset on a new instance or restart. Keyless requires the base URL exactly `https://madeonsol.com`. **API-key (`msk_`) users: no change, no new config.**
17
-
18
- > **New in 1.30.0 β€” REST/x402 parity fix, top traders, and sniper detection.** Found by an internal agentic-infra coverage audit: `MadeOnSolREST` never got the free-tier/live-feed reads that `MadeOnSolX402` already had (`kolFeed`, `kolCoordination`, `kolLeaderboard`, `deployerAlerts`, `kolPairs`, `kolHotTokens`, `kolTrendingTokens`, `kolTokenEntryOrder`, `kolCompareWallets`, `kolAlertsRecent`), plus `tokenBatch`, `tokensBatchBuyerQuality`, and the sniper feature (`sniperByDeployer`, `sniperWatchlist`, `sniperWatchlistAdd`, `sniperWatchlistRemove` β€” `sniperRecent` already existed). `rest.tokenTopTraders(mint, params?)` β€” previously present but with no MCP/ElizaOS/SAK tool anywhere β€” and `rest.updateWebhook(id, params)` (PATCH) round out the surface.
19
- >
20
- > **New in 1.29.0 β€” deployer reputation as-of a date, and creator-fee rewards.** `rest.deployerAsOf(wallet, opts?)` (typed `DeployerAsOfResponse`) binds `GET /deployer-hunter/{wallet}/as-of`: the deployer's reputation exactly as it stood on `opts.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 the requested date (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. `rest.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`). 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+, KEYED (v1) only β€” `msk_` API key, no x402 route.**
21
-
22
- > **New in 1.28.0 β€” token surges & revivals: momentum fires with the honest half attached.** `rest.tokensSurges(params?)` (typed `TokenSurgesResponse`) binds `GET /tokens/surges` (PRO+): every token momentum fire, newest first. Two kinds, one row shape. **`surge`** β€” a token < 30 min old whose market cap runs hard against its *launch* MC, in three tiers that each fire at most once per mint: `early` (≀10 min, β‰₯$12k, β‰₯3Γ— launch), `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Γ—). A tier must be **sustained** (current tick *and* a sample β‰₯10 s older; nothing fires before 20 s of age) β€” a same-slot bundle marked to $475k at age 1 s is a spike, not a surge. **`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), never by the price mark; `tier` is `null`. Hard gates on both: liquidity β‰₯$1.5k and β‰₯2 % of MC, and the MC gained must be **paid for** by buy volume (a price mark in a spoof pool moves MC on ~$0). Every row carries the burst `tape` (`source` candles / wallet_trades, `unique_buyers` only where the mint is in trade coverage β€” `wallet_data_available:false` otherwise, never an inferred zero), `kol` buyers, the first-20 `early_buyers` cohort (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` β€” typed `TokenSurgeRiskFlag`). Rows β‰₯65 min old carry the +1 h `outcome`; `stats: true` prints per-(kind, tier) hit-rates (`up_1h_pct`, `median_peak_multiple`, `doubled_1h_pct`) β€” 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`; subscribe filters `kinds[]`, `tiers[]`, `launchpads[]`, `exclude_flags[]`, `min_mc_usd` / `max_mc_usd`, `deployer_tier[]`) and accepted by the webhook registry as events `token:surge` / `token:revival` with the same filters. The response echoes the live thresholds in `definitions`. **PRO+, KEYED (v1) only β€” `msk_` API key, no x402 route.**
23
-
24
- > **New in 1.27.1 β€” stream tokens never expire.** `POST /stream/token` (`rest.getStreamToken()`) now returns the **same token on every call, forever**. It stops working only if your subscription lapses or you call `rest.getStreamToken({ rotate: true })` to replace it (the previous value keeps working for 60 s). `StreamToken.expires_at` and the new `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. `rest.stream()` already does the right thing (it calls `getStreamToken()` on every (re)connect); no code change needed on your side.
25
-
26
- > **New in 1.27.0 β€” token locks & vesting, upcoming unlocks, and pump.fun creator-fee sharing / fee claims β€” five endpoints + two live channels.** `rest.tokenLocks(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). `rest.tokenLocksFeed(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`). `rest.tokenUnlocks(params?)` (`GET /tokens/unlocks`) lists upcoming unlock EVENTS (cliff / period / final / tranche) inside `within=1h…90d` with `window_amount_*` per contract. `rest.tokenFeeShares(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; `rest.tokenFeeClaims(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+** and **KEYED (v1) only β€” `msk_` API key, no x402 route.**
27
-
28
- > **New in 1.26.0 β€” live holder census: exact holder count, labelled holders, and pools that are named, not just excluded.** `rest.tokenHolders(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. **KEYED (v1) β€” requires an `msk_` API key; the census is not on the x402 rail.**
29
-
30
- > **New in 1.25.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 β€” it is 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 `rest.tokenTrades(mint)` and `rest.walletTrades(address)` carry all four fields (typed on `TokenTrade` / `WalletTrade`) β€” `walletTrades` returned amounts and no price at all before.
31
-
32
- > **New in 1.24.0 β€” the Deployer Hunter surface completed.** Seven new operations that existed on the API but had no SDK binding: `deployerLeaderboard()`, `deployerStats()`, `deployerProfile()`, `deployerTokens()`, `deployerAlertStats()`, `deployerBestTokens()` and `deployerRecentBonds()` (poll it incrementally with `next_since`). Read `bonding_rate` (lifetime) against `recent_bond_rate` (rolling) β€” the gap between them is the signal, not either number alone. `runner_rate` only means something once `labeled_tokens >= 3`, and an **untracked wallet returns a profile with zeroed counters, not a 404**, so check `total_deployed` before reading a 0% bond rate as a track record. Dependency ranges are now bounded to the versions actually tested (`@x402/*` `^2.x`, `@solana/kit` `^5.5.1`) instead of open-ended `>=0.0.1`, and the lazily-imported x402 peers are marked optional β€” a keyed install no longer pulls the whole Solana stack.
33
-
34
- > **New in 1.23.0** β€” **Clean stream shutdown.** `rest.stream().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`.)
35
- >
36
- > **New in 1.22.0** β€” **Token depth / price impact + deployer self-activity on risk.** `rest.tokenDepth(mint, { sizes? })` (`GET /tokens/{mint}/depth`, PRO+) answers "how much SOL moves this token's price N%" β€” per pool, not router-optimal. Pass up to 8 SOL buy `sizes` (each >0 and ≀10000; default `[0.5, 1, 5, 10]`, sent as a CSV `sizes` param); every computable pool returns `spot_price_sol`, `fee_pct`, a `quotes[]` entry per size (`tokens_out`, `avg_price_sol`, `price_impact_pct`), and `to_move_price` β€” the SOL required to move price **1% / 5% / 10%**. Constant-product AMMs are served from stream reserves (`source: "stream"` with `reserves_age_ms`); pump.fun/bonk curves from a **live** read of the curve's virtual reserves (`source: "live_rpc"`). Pools we can't price honestly β€” concentrated CLMM/Orca/DLMM, Meteora-DBC curves, unclassified models β€” come back in `unsupported_pools[]` with a `reason` (e.g. `concentrated_liquidity_depth_not_supported`, `curve_graduated_use_amm_pool`) rather than a wrong number; `primary_pool` names the deepest computable pool and `found: false` means no pools are tracked at all. Typed `TokenDepthResponse` (+ `TokenDepthParams`, `TokenDepthPool`, `TokenDepthQuote`, `TokenDepthToMovePrice`, `TokenDepthUnsupportedPool`). **KEYED (v1) β€” requires an `msk_` API key; there is no x402 route.** And `rest.tokenRisk(mint)` now returns a top-level **`dev` block** (typed `TokenRiskDev | null`) β€” the deployer's self-activity on its own mint: the create-tx self-buy snapshot (`buy_sol`, `buy_tokens`, `buy_supply_pct`), the post-create rollup (`bought_tokens_after` β€” catches the same-second-separate-tx dev buy the create snapshot reads as 0 β€” `sold_tokens`, `sold_sol`, `first_sell_at`/`last_sell_at`), **live on-chain holdings** (`holdings_tokens`, `holdings_supply_pct` β€” pump.fun 1B denominator, null elsewhere β€” and `wallet_empty`: is the dev wallet empty NOW), and `transferred_out` (tokens left without a sell; `null` = unknown when trade coverage or rollup freshness can't prove it β€” never a guess). `dev` is `null` when the mint has no pending_deploys row; the response also carries `as_of`. `deployer:alert` webhook/WS payloads gain `dev_buy_sol` + `dev_buy_supply_pct` β€” the dev's self-buy visible at alert time.
37
- >
38
- > **New in 1.21.0** β€” **Wallet batch classify, token trade tape, sniper footprint, and 7 new x402-payable endpoints.** `rest.walletClassify(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 (typed `WalletBatchClassifyResponse`) β€” flags are pump.fun-pipeline scoped (`false` = not observed, NOT verified clean). `rest.tokenTrades(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`). `rest.tokenTopTraders(mint, params?)` and `rest.sniperRecent(params?)` are new keyed methods too. The wallet profile `flags` block 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, `SniperFootprint | null`) and sniper deploys each carry the same `footprint` block. **x402 catalog grew 18 β†’ 25**: `tokenCandles` ($0.01), `almostBonded` ($0.01), `tokenTopTraders` ($0.02), `tokenCapTable` ($0.02), `sniperRecent` ($0.01), `tokenFlow` ($0.01 β€” the 1.16 keyed-only guard is gone), and `deployerTrajectory` ($0.01) are now callable on the `MadeOnSolX402` client with per-request USDC micropayments. New types: `WalletClassification`, `WalletBatchClassifyResponse`, `TokenTradesParams`, `TokenTrade`, `TokenTradesResponse`, `TokenTopTradersParams`, `TokenTopTrader`, `TokenTopTradersResponse`, `SniperRecentParams`, `SniperDeploy`, `SniperRecentResponse`, `SniperFootprint`, `DumpClusterStats`.
39
- >
40
- > **New in 1.20.0** β€” **Verified wallet holdings.** `rest.walletHoldings(wallet, { limit?, min_value_usd? })` 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 `walletPositions` (trade-derived FIFO): holdings is "what they actually hold right now". Returns typed `WalletHoldingsResponse` with a `summary` (token_accounts / non_zero / returned / priced / total_value_usd / truncated) and `verified_at`. **KEYED (v1) β€” requires an `msk_` API key; there is no x402 route.** ULTRA only.
41
- >
42
- > **New in 1.19.0** β€” **Bundle-cohort holdings.** `rest.tokenBundle(mint)` returns the bundle wallets' current position for a token β€” the "are the bundlers still holding, or did they dump on you?" read. The `bundle` block carries `wallet_count`, `bundle_kind` (`atomic_tx` / `same_slot` / `none`), `held_ratio` (net held / buy volume β€” churn-sensitive secondary), **`held_pct_of_supply`** (net held / circulating supply β€” the headline signal; null when supply is unknown), `fully_exited`, `buy_volume`, and `tokens_held` (typed `TokenBundleResponse`). Field-gated by tier: BASIC get the `bundle` block only (`wallets: []`); PRO adds the top-10 `wallets` with flags (`has_sold`, `atomic`, `is_kol`); ULTRA returns the full cohort plus per-wallet identity (`kol_name`, `win_rate`, `bot_confidence`, `tokens_held`). All tiers reach it.
43
- >
44
- > **New in 1.18.0** β€” **Batch risk scoring + live stream-session control.** `rest.tokensBatchRisk(mints)` scores up to 50 mints in one call (counts as 1 request) β€” each entry in `tokens` is either a full risk result (same shape as `rest.tokenRisk(mint)`, plus `as_of`) or `{ mint, error: "not_tracked" }`; untracked mints don't fail the batch, and `tokens` preserves de-duplicated input order (typed `TokenBatchRiskResponse`). PRO/ULTRA only. Plus `rest.streamSessions()` lists your live WebSocket sessions across ws-streaming + dex-stream (typed `StreamSessionsResponse`), and `rest.streamSessionKill(id)` force-releases a slot by id (typed `StreamSessionEvictResponse`) β€” the self-serve fix for a 4002 lockout when a deploy overlap leaves a ghost socket holding your slot. PRO/ULTRA only.
45
- >
46
- > **New in 1.17.0** β€” **Almost-bonded discovery + trending sorts.** `rest.almostBonded({ min_progress?, max_progress?, min_velocity_pct_per_min?, max_age_minutes?, deployer_tier?, authority_revoked?, min_liq?, sort?, limit? })` returns 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` (typed `AlmostBondedResponse`). `sort` is `velocity_desc` (default) / `progress_desc` / `eta_asc`. **KEYED (v1) β€” requires an `msk_` API key; there is no x402 route.** PRO/ULTRA only. Plus `client.tokensList({ sort })` gains four momentum sorts β€” `mc_change_5m_desc`, `mc_change_1h_desc`, `volume_1h_desc`, and `trending` (composite recent-volume Γ— positive-momentum rank).
47
- >
48
- > **New in 1.16.0** β€” **Token trade flow.** `client.tokenFlow(mint, { window? })` returns a trade-flow aggregate over a `1h`/`24h` window β€” `unique_wallets` / `unique_buyers` / `unique_sellers`, `buy_count` / `sell_count` / `total_trades`, `buy_sol` / `sell_sol` / `net_sol`, and a `trades_per_wallet` wash-trading proxy (typed `TokenFlowResponse`). It's an **organic-vs-fake volume** read. **KEYED (v1) β€” requires an `msk_` API key; there is no x402 route**, so x402-only clients can't reach it. PRO/ULTRA only. Deployer alerts now carry `deployers.deployer_sol_balance` β€” the deployer wallet's SOL balance at alert time (null for historical rows).
49
- >
50
- > **New in 1.15.0** β€” **Live token snapshot + Signal Scorecard.** `rest.token(mint)` returns a live snapshot β€” price (USD/SOL), VWAP, market cap, FDV, liquidity, liquidity-to-MC ratio, primary DEX + pool, Token-2022 / transfer-fee flags, and a `top_buyers[]` array (typed `TokenSnapshotResponse`). `rest.signalPerformance(name, { history? })` returns the **Signal Scorecard** β€” out-of-sample reliability buckets (hit_rate, base_rate, lift, sample_n, window_days) for `dump_cluster_count`, `runner_rate`, `recycled_early_buyer_count`, or `coordination_count`, with a per-day `series` when `history: true` (typed `SignalPerformanceResponse`). `rest.signals()` is the free catalog of all scored signals (typed `SignalsCatalogResponse`). `rest.tokenRisk(mint)` and `rest.tokenBuyerQuality(mint)` are now fully live server-side.
51
- >
52
- > **New in 1.13.0** β€” **Token risk score.** `rest.tokenRisk(mint)` returns a transparent 0–100 rug-risk/safety score (higher = riskier) with a `band` (safe/caution/danger), an explainable `factors[]` array, and the raw `inputs` (mint/freeze authority, liquidity, liq-to-MC ratio, transfer fee, launch cohort, deployer bond rate, KOL signal, blacklist). Typed as `TokenRiskResponse`. PRO/ULTRA only.
53
- >
54
- > **New in 1.12.0** β€” `/token/{mint}` and `/token/batch` responses now include `liquidity_to_mc_ratio`, `launch_cohort_sol`, and `launch_cohort_size`. `/tokens` gains three new filter params: `min_liq_mc_ratio`, `max_liq_mc_ratio`, and `deployer_tier`. `/tokens` list items now include `liquidity_to_mc_ratio` and `deployer_tier`. `/kol/leaderboard` entries now include `median_hold_minutes_30d` and `percentile_early_entry_30d`.
55
- >
56
- > **New in 1.11.1** β€” Deployer profiles now carry `runner_rate` + `labeled_tokens` (fraction of a deployer's labeled tokens that ran vs dumped, gate on `labeled_tokens` β‰₯3) plus `avg_time_to_bond_minutes`, on `DeployerAlert.deployers` and the deployer-trajectory profile.
57
- >
58
- > **New in 1.11** β€” **Graduation events + dump-cluster detection.** Subscribe `token:graduations` for every pump.fun bond in real time (tracked deployer or not, typed `GraduationEvent`). Buyer-quality `breakdown` adds `dump_cluster_count` (out-of-sample: 3+ β†’ 94% dump vs 61% base) + `recycled_early_buyer_count`. DEX firehose: replay buffer deepened to ~5 min; mint-scoped subs get in-band `dex:graduations` frames.
59
-
60
- > **New in 1.10** β€” **Deshred Sniper.** Deshred deploy feed ~500ms before on-chain confirmation (SDK method `rest.sniperRecent()` shipped in 1.21). PRO: elite/good. ULTRA: all tiers + watchlist. Use `sniper:deploys` WebSocket for push.
61
- >
62
- > **New in 1.9** β€” **Price alerts, scout leaderboard, coordination history.** `rest.priceAlertsCreate()` (PRO=5, ULTRA=25). `scoutLeaderboard()`, `kolConsensus()`, `peakHistory()`, `coordinationHistory()`. `walletStats()` now returns `derived`: win_rate, roi, verdict, biggest_miss.
63
- >
64
- > **New in 1.8** β€” **Universal Wallet API.** `rest.walletStats()`, `rest.walletPnl()`, `rest.walletPositions()`, `rest.walletTrades()` β€” FIFO cost-basis PnL for any Solana wallet. PRO+. Cache hits free.
65
- >
66
- > **New in 1.7.1** *(2026-05-13)* β€” Velocity field shape corrected to match the API: `mc_change_pct`, `volume_usd`, `mev_volume_pct` are top-level on the token response, each keyed by `5m`/`15m`/`1h`/`2h`/`4h`. The 1.7.0 README documented a `velocity[window]` shape that didn't match the wire format. Runtime is unchanged β€” fix is to typed shape + docs.
67
- >
68
- > **New in 1.7.0** *(2026-05-12)* β€” **Token directory + account inspection.** `client.tokensList({ min_liq, min_volume_1h_usd, max_mev_share_pct, mc_change_1h_min_pct, sort, min_liq_mc_ratio, max_liq_mc_ratio, deployer_tier, ... })` filters every active mint by MC band, liquidity floor, primary DEX, authority/safety flags, computed 1h volume, MEV-share ceiling, MC-change deltas, liq/MC ratio, and deployer tier. Response items now include `liquidity_to_mc_ratio` and `deployer_tier`. Default `min_liq=2000` skips phantom-MC dust; pass `min_liq=0` to opt out. `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 token response: `mc_change_pct`, `volume_usd`, `mev_volume_pct` (each keyed by `5m`/`15m`/`1h`/`2h`/`4h`) plus `history_age_seconds`. `/token/{mint}` 400s now ship structured `code`, `reason`, `received_length`, `example`, and `docs` β€” stop guessing why a mint failed. Deprecated `avg_entry_mc_usd` fully removed.
69
-
70
- ## Quick start (10 seconds)
71
-
72
- ```bash
73
- npm install madeonsol-x402
74
- ```
75
-
76
- ```ts
77
- import { createClient } from "madeonsol-x402";
78
- const client = createClient("msk_..."); // free tier at https://madeonsol.com/pricing
79
- const { trades } = await client.kolFeed({ limit: 5 });
80
- ```
81
-
82
- ## Authentication
83
-
84
- Two options:
85
-
86
- | Method | Option | Best for |
87
- |---|---|---|
88
- | **MadeOnSol API key** (recommended) | `apiKey` | Developers β€” [get a free key](https://madeonsol.com/pricing) |
89
- | x402 micropayments | `privateKey` | AI agents with Solana wallets |
90
-
91
- > **v1.0 breaking change:** RapidAPI auth has been removed. The MadeOnSol RapidAPI marketplace was retired on 2026-04-19. If you were using `rapidApiKey`, get a free `msk_` key at [madeonsol.com/pricing](https://madeonsol.com/pricing).
92
-
93
- ## Install
94
-
95
- ```bash
96
- npm install madeonsol-x402
97
- ```
98
-
99
- > x402 peer deps (`@x402/fetch @x402/svm @x402/core @solana/kit @scure/base`) are only needed when using `privateKey`.
100
-
101
- ## Quick Start
102
-
103
- ```ts
104
- import { createClient } from "madeonsol-x402";
105
-
106
- // Option 1: API key β€” get one free at madeonsol.com/pricing
107
- const client = createClient("msk_your_api_key_here");
108
-
109
- // Option 2: x402 micropayments (auto-detected when no msk_ prefix)
110
- // const client = createClient(process.env.SVM_PRIVATE_KEY!, undefined, paymentPolicy);
111
- // See the required paymentPolicy example below.
112
-
113
- const { trades } = await client.kolFeed({ limit: 10 });
114
- console.log(trades);
115
- ```
116
-
117
- ### Advanced initialization
118
-
119
- Keyless mode requires `paymentPolicy`. API-key mode does not initialize signing or require these settings.
120
-
121
- ```ts
122
- import { MadeOnSolX402, type SolanaPaymentPolicy } from "madeonsol-x402";
123
-
124
- const paymentPolicy: SolanaPaymentPolicy = {
125
- payTo: "GLu63pRCYrp4BJu5P5ciYKxgeZFW9c8TJ8jWzK3TB9AR", // canonical merchant (see below)
126
- feePayer: "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4", // PayAI facilitator fee payer (see below)
127
- maxAmountAtomic: "20000", // 0.02 USDC per authorization (the highest Solana leg)
128
- maxTotalAmountAtomic: "1000000", // 1 USDC across this client's lifetime
129
- rpcUrl: process.env.SVM_RPC_URL!, // your trusted HTTPS RPC
130
- timeoutMs: 30_000,
131
- // Optional additional approval; literal true is required if this hook is set.
132
- beforePayment: async proposal => BigInt(proposal.amountAtomic) <= 20000n,
133
- };
134
- const client = new MadeOnSolX402({ privateKey: process.env.SVM_PRIVATE_KEY!, paymentPolicy });
135
- // Equivalent: createClient(privateKey, undefined, paymentPolicy).
136
- console.log(client.authorizedAmountAtomic);
137
- ```
138
-
139
- **Canonical MadeOnSol values (Solana mainnet USDC).** Pinned here (GitHub + npm README) so you do not have to take them from a 402:
140
- - merchant `payTo` / `X402_PAY_TO`: `GLu63pRCYrp4BJu5P5ciYKxgeZFW9c8TJ8jWzK3TB9AR` (also shown on https://madeonsol.com/x402 and https://madeonsol.com/.well-known/x402)
141
- - facilitator `feePayer` / `X402_FEE_PAYER`: `2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4`. This is the fee payer of **PayAI**, the third-party facilitator MadeOnSol's Solana rail uses. If PayAI rotates it, keyless calls fail closed (the client refuses to sign) until you update this value; a MadeOnSol release will announce the new one.
142
- - prices: Solana legs are 5000–20000 atomic (0.005–0.02 USDC), so `maxAmountAtomic` / `X402_MAX_AMOUNT_ATOMIC` must be at least `20000` to reach every endpoint.
143
-
144
- The budget is per client instance / process: not wallet-wide, not shared between processes, reset when a new instance or process starts. Keyless mode requires the base URL exactly `https://madeonsol.com`.
145
-
146
- **Breaking keyless upgrade:** missing policy now fails closed. Only exact mainnet USDC payments to the configured merchant and facilitator are signed. The agent cannot also be the facilitator fee payer. URLs must use HTTPS; requests stay on the configured API origin and redirects are refused. There is no public RPC fallback. Amounts use positive integer strings or bigint, never floating-point numbers; USDC has 6 decimals. Choose caps for the endpoints you use.
147
-
148
- Reuse one long-lived client. Concurrent calls share its allowance. An unsigned approval denial releases its reservation; once payment creation starts, the allowance remains consumed even after RPC/signing/network errors or an ambiguous response. This is **authorized attempts, not settled spend**, with no automatic refund or replay. The frozen `beforePayment` proposal cannot override built-in checks.
149
-
150
- The 30-second default bounds challenge reading, approval, signing waits and submission. Late RPC results cannot invoke the signer after timeout. Solana transaction validity still follows the signed recent blockhash; the client deadline cannot revoke a proof already sent. Response-body consumption after returned headers is not covered by this deadline.
151
-
152
- A new client or process starts a new allowance. These limits are not a durable, wallet-wide budget; coordinate externally when multiple agents/processes share a wallet. API-key behavior and precedence are unchanged.
153
-
154
- ## x402 Endpoints (per-request micropayments)
155
-
156
- | Method | Description |
157
- |---|---|
158
- | `kolFeed(params?)` | Real-time KOL trade feed from 1,000+ tracked wallets |
159
- | `kolCoordination(params?)` | Tokens being accumulated by multiple KOLs simultaneously |
160
- | `kolLeaderboard(params?)` | KOL performance rankings by PnL and win rate (180 days of trade history) |
161
- | `kolPairs(params?)` | KOL affinity matrix β€” which KOLs frequently co-trade the same tokens |
162
- | `kolHotTokens(params?)` | KOL momentum tokens β€” accelerating KOL buy interest |
163
- | `kolTokenEntryOrder(mint, params?)` | Ranked KOL first-buyer order for a token |
164
- | `kolCompareWallets({ wallets })` | Side-by-side comparison of 2–5 KOL wallets |
165
- | `kolAlertsRecent(params?)` | Live KOL alert feed β€” clusters, fresh-token buys, heating-up wallets |
166
- | `deployerAlerts(params?)` | Pump.fun deployer alerts with KOL enrichment. PRO/ULTRA: filter by tier. |
167
- | `walletStats(address)` | **New 1.8** Β· Wallet stats + cross-product flags (is_kol / is_alpha_tracked + bot_confidence / is_deployer). 90-day window. **$0.005** |
168
- | `walletPnl(address)` | **New 1.8** Β· FIFO cost-basis PnL: realized + unrealized SOL, profit factor, drawdown, hold times, daily curve, closed + open positions. **$0.02** |
169
- | `walletPositions(address)` | **New 1.8** Β· Open positions only, live unrealized from market-cap tracker. Shares /pnl cache. **$0.01** |
170
- | `walletTrades(address, params?)` | **New 1.8** Β· Cursor-paginated raw trades with action / token / since-until filters. **$0.005** |
171
- | `tokenFlow(mint, params?)` | Trade-flow aggregate (organic-vs-fake volume) β€” unique wallets/buyers/sellers, buy/sell counts + SOL, net SOL, `trades_per_wallet` wash-trading proxy. `window` ("1h" \| "24h", default "1h"). **Now x402-payable (1.21).** **$0.01** |
172
- | `tokenCandles(mint, params?)` | **New 1.21** Β· OHLCV candles (1m–1d timeframes, 30d history) with per-candle volume, trade count, and market cap. **$0.01** |
173
- | `almostBonded(params?)` | **New 1.21** Β· Launchpad tokens approaching graduation (pump.fun + LetsBonk LaunchLab) β€” bonding progress, velocity (Ξ”progress/min), ETA, deployer tier. **$0.01** |
174
- | `tokenTopTraders(mint, params?)` | **New 1.21** Β· Wallets ranked by realized PnL (or ROI) on a token, enriched with KOL identity + alpha reputation. **$0.02** |
175
- | `tokenCapTable(mint)` | **New 1.21** Β· Early-buyer cap table β€” first 10 non-deployer buyers with PnL, exit status, bundle/KOL/alpha flags + buyer-quality score. **$0.02** |
176
- | `sniperRecent(params?)` | **New 1.21** Β· Deshred sniper deploy feed (elite/good deployers) with per-deploy snipe `footprint`. **$0.01** |
177
- | `deployerTrajectory(wallet, params?)` | **New 1.21** Β· Deployer bond-rate trajectory β€” streaks, rolling bond rates, trend, cadence. `include: "daily_snapshots"` adds 90 days. **$0.01** |
178
- | `discovery()` | Lists all 25 endpoints, prices, and parameter docs (free) |
179
-
180
- ## REST API client
181
-
182
- The `MadeOnSolREST` class exposes the full v1 API (alpha intelligence, token quality, copy-trade rules, wallet tracker, webhooks, streaming). Most endpoints require a Pro or Ultra subscription.
183
-
184
- ```ts
185
- import { MadeOnSolREST } from "madeonsol-x402";
186
-
187
- const rest = new MadeOnSolREST({ apiKey: "msk_your_key" });
188
- const { leaderboard } = await rest.alphaLeaderboard({ period: "30d", sort: "win_rate" });
189
-
190
- // Rate-limit headers from the most recent response
191
- console.log(rest.lastRateLimit); // { limit, remaining, reset, requestId }
192
- ```
193
-
194
- ### Alpha wallet intelligence
195
-
196
- Scored from 1.5M+ early-buyer records (wallets seen in the first 20 buyers of Pump.fun tokens).
197
-
198
- | Method | Tier | Description |
199
- |---|---|---|
200
- | `rest.alphaLeaderboard(params?)` | All | Top profitable wallets. Up to 100 on Free/Pro; ULTRA unlocks 500 + bot signals |
201
- | `rest.alphaWallet(wallet)` | ULTRA | Full per-token breakdown + bot_signals array |
202
- | `rest.alphaLinked(wallet)` | ULTRA | Wallets behaviorally linked (co-bought 3+ tokens within 2s) |
203
-
204
- **alphaLeaderboard params** β€” `period` ("7d" \| "30d" \| "all"), `min_tokens` (1–20), `sort` ("win_rate" \| "pnl" \| "roi"), `exclude_bots` ("true" \| "false")
205
-
206
- ### Token quality
207
-
208
- | Method | Tier | Description |
209
- |---|---|---|
210
- | `rest.token(mint)` | All | **New 1.15** Β· Live token snapshot β€” price (USD/SOL), VWAP, market cap, FDV, liquidity, liq-to-MC ratio, primary DEX + pool, Token-2022 / transfer-fee flags, and `top_buyers[]`. Returns `{ token }` |
211
- | `rest.tokenCapTable(mint)` | PRO+ | First non-deployer early buyers, enriched with PnL/KOL/bot flags. PRO=10, ULTRA=20 |
212
- | `rest.tokenBuyerQuality(mint)` | All | 0–100 buyer-quality score + full breakdown (5-min cached). Live server-side |
213
- | `rest.tokenRisk(mint)` | PRO+ | Transparent 0–100 rug-risk/safety score with `band`, explainable `factors[]`, and raw `inputs`. **1.22:** adds a top-level `dev` block (`TokenRiskDev \| null`) β€” deployer self-buy at create, sells rollup, live on-chain holdings, `wallet_empty`, `transferred_out`. Live server-side |
214
- | `rest.tokenBundle(mint)` | All | **New 1.19** Β· Bundle-cohort holdings β€” `bundle` block (`wallet_count`, `bundle_kind`, `held_ratio`, headline `held_pct_of_supply`, `fully_exited`, `buy_volume`, `tokens_held`). BASIC = block only; PRO = top-10 `wallets` + flags; ULTRA = full cohort + identity fields |
215
- | `rest.tokenPools(mint)` | PRO+ | **New 1.19.2** Β· Per-venue liquidity map β€” every DEX pool a token trades in (`pool_address`, `dex`, `liquidity_usd`, `last_price_sol`, `is_active`), plus a `summary` rollup (`pool_count`, `active_pool_count`, `dex_count`, `total_liquidity_usd`, `primary_pool`/`primary_dex`, `top_pool_share_pct`) |
216
- | `rest.tokenDepth(mint, params?)` | PRO+ | **New 1.22** Β· Per-pool price impact / slippage β€” `quotes[]` per SOL buy size (`tokens_out`, `avg_price_sol`, `price_impact_pct`), `to_move_price` (SOL to move price 1%/5%/10%), `spot_price_sol`, `fee_pct`. Pools we can't price honestly land in `unsupported_pools[]` with a `reason`. `sizes` max 8, default `[0.5, 1, 5, 10]` |
217
- | `rest.tokenHolders(mint)` | PRO+ | **New** Β· Live holder census + concentration β€” who holds NOW (vs `tokenCapTable` = who bought first). `concentration.holder_count` is EXACT (mint-scoped `getProgramAccounts` census, merged per owner; `null` only when the provider refuses a mega-cap β†’ top-20 `getTokenLargestAccounts` fallback with `source.census_fallback_reason` β€” never trade-estimated). Each disclosed owner labelled `deployer` / `kol` / `early_buyer` / `bundle` / `bot` / `dump_cluster` (empty = unknown, not clean). Pools / bonding curves / burns EXCLUDED from the circulating denominator and NAMED in `excluded[]` (`reason`: `pool` + `dex` + `pool_address`, `bonding_curve`, `burn`, `program_account`). `amount_raw` / `supply_raw` / `circulating_raw` are raw u64 STRINGS. Disclosure PRO 10 / ULTRA 50 / BUSINESS 100; maths tier-independent. Big tokens: first call may be HTTP 503 `holder_scan_in_progress` (`retry_after_seconds: 20`) β€” the scan continues and is cached, the retry is instant. Keyed only (no x402 route) |
218
- | `rest.tokenLocks(mint, params?)` | PRO+ | **New 1.27** Β· Token locks & vesting on a mint β€” every Streamflow / Jupiter Lock / Bonfida vesting contract with a live-derived view (`locked_raw` still locked, `unlocked`, `withdrawn`, `claimable`, `status`, `next_unlock`, `cancelable_by_sender`) + `summary` (locked / deposited raw + ui + usd + % of supply, `unlocking_7d` / `unlocking_30d`, nearest `next_unlock`, `active_cancelable_by_sender`). Params `status`, `program`, `limit` (≀500). Base-unit amounts are STRINGS; ui/usd/pct null when unknown. **LP locks NOT included.** Keyed only (no x402 route) |
219
- | `rest.tokenLocksFeed(params?)` | PRO+ | **New 1.27** Β· Cross-token feed of NEW lock / vesting contracts, newest first (same row + `token` facts). Cursor `since` = `pagination.next_since`, `before` = `next_before`; filters `mint`, `sender`, `recipient`, `program`, `kind`, `status`, `min_usd`, `min_pct_of_supply`, `include_estimated` (backfilled Jupiter rows). Pushed live on WS `token:locks`. Keyed only |
220
- | `rest.tokenUnlocks(params?)` | PRO+ | **New 1.27** Β· Upcoming unlock EVENTS across all active contracts inside `within` (1h Β· 6h Β· 24h Β· 3d Β· 7d Β· 14d Β· 30d Β· 90d) β€” each contract's NEXT cliff / period / final / tranche with `amount_*` + `window_amount_*` (total over the window). `sort` soonest Β· largest_usd Β· largest_pct; filters `mint`, `program`, `kind`, `min_usd`, `min_pct_of_supply`. Keyed only |
221
- | `rest.tokenFeeShares(mint)` | PRO+ | **New 1.27** Β· pump.fun creator-fee sharing on a coin β€” the on-chain `SharingConfig` (`admin`, `shareholders[]` with `share_bps` / `is_admin` / `is_social_pda` + `social` identity (platform 2 = X, `user_id` = numeric id, lifetime claimed), `redirected_bps`, `social_bps`, `is_default` = 100% to creator, `source` stream/chain) + `distributions` rollup per recipient, `past_recipients`, `history` (config changes / creator transfers), `recent_distributions`. Quote base units as STRINGS. **History starts 2026-08-17.** Keyed only |
222
- | `rest.tokenFeeClaims(params?)` | PRO+ | **New 1.27** Β· pump.fun fee-event feed, newest first β€” `distribution` (with pro-rata `payouts[]`), `social_claim`, `shares_created` / `shares_updated` / `shares_reset`, `creator_transferred`, `creator_claim` (only when asked via `type`). Filters `type` (comma list), `mint`, `recipient`, `actor`, `social_platform`, `social_user_id`, `min_sol`; cursor `since` = `pagination.next_since`. Pushed live on WS `token:fee_claims`. **History starts 2026-08-17.** Keyed only |
223
- | `rest.tokensSurges(params?)` | PRO+ | **New 1.28** Β· Token momentum fires, newest first β€” `kind` `surge` (token < 30 min old vs its LAUNCH MC; `tier` `early` ≀10 min β‰₯$12k β‰₯3Γ— Β· `strong` ≀30 min β‰₯$30k β‰₯6Γ— and β‰₯2Γ— the 3-min low Β· `breakout` ≀2 min β‰₯$45k β‰₯8Γ—; each once per mint, sustained β‰₯10 s) or `revival` (no trade candle β‰₯24 h, then β‰₯5 buys / β‰₯$500 buy volume / β‰₯1.5Γ— the pre-dormancy MC on the tape β€” never a price mark; `tier` null). Each row: burst `tape` (`unique_buyers` null outside trade coverage), `kol`, `early_buyers` (bundled / sold / sniper), `deployer`, `risk_flags[]`, and `outcome` (+1 h MC / peak / low) once β‰₯65 min old. `stats: true` = per-(kind, tier) hit-rates over `days`. Filters `kind`, `tier`, `mint`, `launchpad`, `deployer_tier`, `min_mc_usd` / `max_mc_usd`, `min_buys`, `exclude_flags` (comma list), `only_clean`; cursors `since` / `before`. Pushed live on WS `token:surges`. Retention 60 d. Keyed only |
224
- | `rest.tokensBatchRisk(mints)` | PRO+ | **New 1.18** Β· Bulk risk scoring β€” up to 50 mints in one call (counts as 1 request). Each `tokens[]` entry is a full risk result or `{ mint, error: "not_tracked" }`; untracked mints don't fail the batch |
225
- | `rest.tokenCandles(mint, params?)` | PRO+ | OHLC candles. PRO = OHLCV, last 30 days; ULTRA = + net flow (buy/sell volume, `net_volume_usd`, counts, MEV vol), liquidity delta, full history |
226
- | `rest.tokenTrades(mint, params?)` | PRO+ | **New 1.21** Β· Mint-scoped trade tape β€” cursor-paginated raw trades (`price_sol`/`price_usd`, `early_buyer_rank`, `slot`), filter by `action`/`wallet`/`since`/`until`. Default window = **full history**; `coverage` block carries `history_start` (2026-04-12) + `scope` (pump.fun pipeline) |
227
- | `rest.tokenTopTraders(mint, params?)` | PRO+ | **New 1.21** Β· Wallets ranked by realized PnL (or ROI) on a token β€” `sort` ("pnl" \| "roi"), `window_days` (1–180), `min_bought_sol`; enriched with KOL identity + alpha reputation (`bot_confidence`, historical win rate/PnL) |
228
- | `rest.sniperRecent(params?)` | PRO+ | **New 1.21** Β· Deshred sniper deploy feed β€” PRO sees elite/good deployers, ULTRA all tiers. Each deploy carries a slot-window snipe `footprint` (`buys`/`buyers`/`sol`/`supply_pct`/`sniper_wallet_buys`; null until the ~10-min settle window) |
229
-
230
- **tokenCandles params** β€” `tf` ("1m" \| "5m" \| "15m" \| "1h" \| "4h" \| "1d", default "1h"), `limit` (1–1000, default 200), `from` (ISO 8601), `to` (ISO 8601)
231
-
232
- ```ts
233
- // Score a basket in one request (counts as 1 against quota)
234
- const { tokens, count } = await rest.tokensBatchRisk([mintA, mintB, mintC]);
235
- for (const t of tokens) {
236
- if ("error" in t) console.log(t.mint, t.error); // e.g. "not_tracked"
237
- else console.log(t.mint, t.risk_score, t.band); // full risk result + as_of
238
- }
239
- ```
240
-
241
- ### Signal Scorecard *(new in 1.15)*
242
-
243
- Out-of-sample reliability for the scored early-buyer / coordination signals β€” every claim is backed by a hit-rate vs base-rate measurement so you can size positions on evidence, not vibes.
244
-
245
- | Method | Tier | Description |
246
- |---|---|---|
247
- | `rest.signals()` | All (free) | Catalog of scored signals β€” name, methodology, and each signal's `performance_endpoint`. No payment required |
248
- | `rest.signalPerformance(name, params?)` | All | Signal Scorecard for one signal β€” `buckets[]` (hit_rate, base_rate, lift, sample_n, window_days, test_from/test_to) + metric_type, outcome, methodology, as_of. Pass `{ history: true }` for a per-day `series[]` |
249
-
250
- Valid signal names: `dump_cluster_count`, `runner_rate`, `recycled_early_buyer_count`, `coordination_count`.
251
-
252
- ```ts
253
- const { signals } = await rest.signals();
254
- const scorecard = await rest.signalPerformance("dump_cluster_count", { history: true });
255
- console.log(scorecard.buckets); // [{ bucket, hit_rate, base_rate, lift, sample_n, ... }]
256
- ```
257
-
258
- ### KOL coordination alerts (v1.1 β€” push signals)
259
-
260
- Real-time push alerts when a cluster of KOLs co-buys the same token. Fires within ~1s of the triggering trade (pg_notify push, not polling). Delivered via WebSocket (`kol:coordination` channel, user-scoped) and/or HMAC-signed webhook. PRO=5 rules, ULTRA=20.
261
-
262
- ```ts
263
- // Create a rule
264
- const { rule, webhook_secret } = await rest.coordinationAlertsCreate({
265
- name: "fresh pump cluster",
266
- min_kols: 4, // minimum distinct KOLs in window
267
- window_minutes: 15, // peak-density window (1-60)
268
- min_score: 70, // 0-100 composite score cutoff
269
- include_majors: false, // filter WIF/BONK/POPCAT
270
- cooldown_min: 60, // one fire per (rule,token) per 60min...
271
- score_jump_break: 10, // ...unless score jumps +10 vs last fire
272
- delivery_mode: "both",
273
- webhook_url: "https://you.com/hooks/coord",
274
- });
275
- // β†’ store webhook_secret β€” shown ONCE
276
- ```
277
-
278
- `coordinationAlertsList`, `coordinationAlertsGet(id)`, `coordinationAlertsUpdate(id, params)`, `coordinationAlertsDelete(id)` round out the CRUD.
279
-
280
- **Webhook signature:** `X-MadeOnSol-Signature: sha256=<hmac>` where `hmac = HMAC-SHA256(webhook_secret, timestamp + "." + rawBody)`, and `X-MadeOnSol-Timestamp` carries the unix seconds used.
281
-
282
- **The `kolCoordination()` response** now includes v1.1 fields: `peak_window_start/end`, `peak_kols`, `peak_buys` (the busiest slice within the period), `exited_count` + per-KOL `exited` flag (net-flow-negative wallets), and `coordination_score` (0-100). Pass `min_score`, `window_minutes`, `include_majors` to filter.
283
-
284
- ### KOL first-touch signal *(new in 1.3)*
285
-
286
- Every "first KOL buy on a token mint" event β€” the moment a tracked KOL is the first of the cohort to touch a token. Filterable by **scout tier** (S/A/B/C from `mv_kol_scout_score`), KOL winrate, token age, mint suffix.
287
-
288
- **Backtest:** top scouts attract β‰₯3 follow-on KOLs within 4h ~50% of the time vs ~14% baseline (38d / 491k buys / 72,549 events). Live leaderboard at [madeonsol.com/kol/scouts](https://madeonsol.com/kol/scouts).
289
-
290
- ```ts
291
- import { MadeOnSolREST } from "madeonsol-x402";
292
- const rest = new MadeOnSolREST({ apiKey: process.env.MADEONSOL_API_KEY! });
293
-
294
- // S-tier scouts on tokens younger than 1h
295
- const { events } = await rest.firstTouches({ preset: "scout", min_scout_tier: "S" });
296
-
297
- for (const e of events) {
298
- console.log(e.first_kol.name, "scouted", e.token_symbol, `(scout_score=${e.first_kol.scout_score}%)`);
299
- }
300
- ```
301
-
302
- Filter knobs: `since`, `before`, `limit`, `kol`, `min_kol_winrate_7d`, `min_scout_tier` (`"S"|"A"|"B"|"C"`), `min_n_touches`, `strategy`, `token_age_max_min`, `min_first_buy_sol`, `mint_suffix` (`"pump"`, `"bonk"`, …), `preset` (`"scout"`/`"fresh_launch"`), `include` (`"followers_4h"`).
303
-
304
- > **Don't poll β€” push.** Median lead time before the second KOL is **12 seconds**. REST polling will miss the swarm. Subscribe to the `kol:first_touches` WebSocket channel (PRO+) or, on Ultra, create an HMAC-signed webhook subscription.
305
-
306
- **Webhook subscriptions (Ultra)** β€” up to 10 active per user, mirrors `coordinationAlerts`:
307
-
308
- ```ts
309
- const { subscription, webhook_secret } = await rest.firstTouchSubscriptionsCreate({
310
- name: "S-tier scouts on pump tokens",
311
- filters: { min_scout_tier: "S", mint_suffix: "pump" },
312
- delivery_mode: "webhook",
313
- webhook_url: "https://my.bot/hooks/scout",
314
- });
315
- // β†’ store webhook_secret β€” shown ONCE
316
- ```
317
-
318
- `firstTouchSubscriptionsList`, `firstTouchSubscriptionsGet(id)`, `firstTouchSubscriptionsUpdate(id, params)`, `firstTouchSubscriptionsDelete(id)` round out the CRUD.
319
-
320
- ### Price alerts *(new in 1.9)*
321
-
322
- CRUD for token dip/recovery price alerts. Fires via WebSocket (`price_alert:events` channel) and/or HMAC-signed webhook when a token's market cap crosses your threshold. PRO=5 rules, ULTRA=25.
323
-
324
- ```ts
325
- const { alert, webhook_secret } = await rest.priceAlertsCreate({
326
- name: "SOL dip buy",
327
- token_mint: "So11111111111111111111111111111111111111112",
328
- condition: "below", // "below" | "above"
329
- threshold_mc_usd: 5_000_000_000,
330
- cooldown_min: 120,
331
- delivery_mode: "both",
332
- webhook_url: "https://you.com/hooks/price",
333
- });
334
- // β†’ store webhook_secret β€” shown ONCE
335
- ```
336
-
337
- `priceAlertsList`, `priceAlertsGet(id)`, `priceAlertsUpdate(id, params)`, `priceAlertsDelete(id)` round out the CRUD.
338
-
339
- ### Scout leaderboard & KOL consensus *(new in 1.9)*
340
-
341
- | Method | Tier | Description |
342
- |---|---|---|
343
- | `rest.scoutLeaderboard(params?)` | PRO+ | Top scout-tier KOLs ranked by first-touch follow-on rate, win rate, and ROI |
344
- | `rest.kolConsensus(params?)` | PRO+ | Tokens with the strongest KOL agreement signal β€” weighted by scout score and recent PnL |
345
- | `rest.peakHistory(mint)` | PRO+ | Historical peak-density windows for a token β€” every coordination spike with KOL breakdown |
346
- | `rest.coordinationHistory(params?)` | PRO+ | Global coordination event log with token, KOL count, score, and outcome |
347
-
348
- ```ts
349
- const { leaderboard } = await rest.scoutLeaderboard({ period: "30d", limit: 25 });
350
- const { tokens } = await rest.kolConsensus({ min_kols: 5, period: "24h" });
351
- ```
352
-
353
- ### Wallet derived stats *(new in 1.9)*
354
-
355
- `walletStats(address)` now includes a `stats` object with derived fields computed from the 90-day trade window:
356
-
357
- ```ts
358
- const { stats } = await rest.walletStats("WALLET_ADDRESS");
359
- // stats.win_rate β€” fraction 0-1, tokens sold above cost basis
360
- // stats.roi β€” aggregate return on invested SOL
361
- // stats.verdict β€” "strong" | "profitable" | "neutral" | "losing"
362
- // stats.biggest_miss β€” token with the highest post-exit gain the wallet missed
363
- ```
364
-
365
- ### Copy-trade rules
366
-
367
- Server-side rules that fire signals when one of your watched source wallets trades. Delivered via webhook (HMAC-signed) and/or WebSocket. PRO=3 rules Γ— 5 source wallets each; ULTRA=20 Γ— 50.
368
-
369
- | Method | Description |
370
- |---|---|
371
- | `rest.copyTradeList()` | List your rules |
372
- | `rest.copyTradeCreate(params)` | Create a rule. Returns `webhook_secret` **once** β€” store it |
373
- | `rest.copyTradeGet(id)` | Get one rule |
374
- | `rest.copyTradeUpdate(id, params)` | Update fields or toggle `is_active` |
375
- | `rest.copyTradeDelete(id)` | Delete permanently |
376
- | `rest.copyTradeSignals(params?)` | Recent fired signals (up to 7 days). Filter by `subscription_id`, `since`, `limit` (1–500) |
377
-
378
- ### Wallet tracker
379
-
380
- Per-account watchlist with historical swap/transfer history.
381
-
382
- | Method | Description |
383
- |---|---|
384
- | `rest.walletTrackerList()` | List tracked wallets + remaining capacity |
385
- | `rest.walletTrackerAdd(wallet, label?)` | Add a wallet |
386
- | `rest.walletTrackerRemove(wallet)` | Remove a wallet |
387
- | `rest.walletTrackerUpdateLabel(wallet, label)` | Update label (pass `null` to clear) |
388
- | `rest.walletTrackerTrades(params?)` | Historical events. Params: `wallet`, `action`, `event_type`, `limit` (1–200), `before` (cursor) |
389
- | `rest.walletTrackerSummary(params?)` | Per-wallet stats. Params: `period` ("24h" \| "7d" \| "30d"), `wallet` |
390
- | `rest.walletStats(address)` | **New 1.8** Β· Universal wallet stats (90d) + cross-product flags. PRO+. |
391
- | `rest.walletPnl(address)` | **New 1.8** Β· Full FIFO PnL + curve + closed/open positions. PRO+. |
392
- | `rest.walletPositions(address)` | **New 1.8** Β· Open positions only with live unrealized. PRO+. |
393
- | `rest.walletTrades(address, params?)` | **New 1.8** Β· Cursor-paginated raw trades. Params: `limit` (1-500), `cursor`, `action`, `token_mint`, `since`, `until`. PRO+. |
394
- | `rest.walletClassify(wallets)` | **New 1.21** Β· Bulk reputation flags for 1–100 wallets in one request β€” `is_sniper` / `is_bundler` (lifetime) / `is_dumper` (rolling 42d) / `is_kol` + `kol_name` / `bot_confidence` / `dump_cluster`. Pump.fun-pipeline scoped: `false` = not observed, NOT verified clean. PRO+. |
395
-
396
- ### Webhooks
397
-
398
- | Method | Description |
399
- |---|---|
400
- | `rest.createWebhook(params)` | Create webhook. Returns `secret` once β€” store it for HMAC verification |
401
- | `rest.listWebhooks()` | List your webhooks |
402
- | `rest.getWebhook(id)` | Get one + recent delivery log |
403
- | `rest.updateWebhook(id, params)` | Update URL, events, filters, or re-enable |
404
- | `rest.deleteWebhook(id)` | Delete |
405
- | `rest.testWebhook(id)` | Send test payload |
406
-
407
- ### KOL/deployer detail
408
-
409
- | Method | Description |
410
- |---|---|
411
- | `rest.kolTiming(wallet, params?)` | Entry/exit timing β€” hold duration, exit speed, hour distribution |
412
- | `rest.kolPnl(wallet, params?)` | Per-wallet PnL breakdown |
413
- | `rest.deployerTrajectory(wallet)` | Deployer skill curve β€” streaks, rolling bond rate, trend |
414
- | `rest.deployerHistory(wallet, opts?)` | **New 1.19.2** Β· PRO+ Β· Daily reputation time-series β€” backtest "was this deployer elite when it launched token X?" without look-ahead. `snapshots[]` carry per-day `tier`, `is_tracked`, `total_deployed`/`total_bonded`, `bonding_rate`, `recent_bond_rate`, `avg_peak_mc`, `best_token_peak_mc`. `opts.limit` (1–365, default 90) |
415
-
416
- ### Streaming token
417
-
418
- ```ts
419
- const token = await rest.getStreamToken();
420
- // token.ws_url β€” KOL/deployer streaming (Pro/Ultra)
421
- // token.dex_ws_url β€” all-DEX trade stream (Ultra only)
422
- // token.expires_at β€” always null: stream tokens never expire (1.27.1)
423
- const fresh = await rest.getStreamToken({ rotate: true }); // replace it; old value works 60 s more
424
- ```
425
-
426
- Stream tokens **never expire**: the same token comes back on every call until your subscription lapses or you rotate it. Send it as `Authorization: Bearer <token>` on the WebSocket handshake (`?token=` still works). A `4001` close means "mint again", never a timer.
427
-
428
- ### Managed streaming client *(new in 1.10)*
429
-
430
- `rest.stream()` handles the token fetch (the token never expires β€” `getStreamToken()` is called on every (re)connect), auto-reconnect (backoff + jitter), heartbeat liveness, and typed events β€” just subscribe and listen.
431
-
432
- ```ts
433
- const stream = rest.stream();
434
- stream.on("kol:trade", (t) => console.log(t.token_symbol, t.action));
435
- stream.on("deployer:alert", (a) => console.log("new deploy", a.token_mint));
436
- stream.subscribe(["kol:trades", "deployer:alerts"]);
437
- // stream.unsubscribe([...]) / stream.close() when done
438
- ```
439
-
440
- 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 1.27** β€” event `token:lock` for every NEW Streamflow / Jupiter Lock / Bonfida lock or vesting contract, typed `TokenLockStreamEvent`; PRO+; updates are not pushed β€” poll `rest.tokenLocks()`), `token:fee_claims` (**new 1.27** β€” event `token:fee_claim` for every pump.fun fee event: distributions, social-handle claims, config changes, typed `TokenFeeClaimStreamEvent`; PRO+), `token:surges` (**new 1.28** β€” events `token:surge` (a token < 30 min old running β‰₯3Γ— / β‰₯6Γ— / β‰₯8Γ— its launch MC β€” `tier` early / strong / breakout, each once per mint, sustained) and `token:revival` (β‰₯24 h with no trade candle, then confirmed buys on the tape; `tier` null), typed `TokenSurgeStreamEvent` β€” the same row as `rest.tokensSurges()` minus `outcome`, `risk_flags[]` included; subscribe filters `kinds[]`, `tiers[]`, `launchpads[]`, `exclude_flags[]`, `min_mc_usd` / `max_mc_usd`, `deployer_tier[]`; PRO+). Lifecycle events: `open`, `close`, `reconnect`, `heartbeat`, `error`. Uses the global `WebSocket` on Node 22+; on Node < 22 also `npm i ws`.
441
-
442
- ### Live stream sessions *(new in 1.18)*
443
-
444
- List and force-release the connection slots your key currently holds across both stream services (ws-streaming + dex-stream). Reflects in-memory state, so every listed slot is evictable β€” the self-serve fix when a deploy overlap leaves a ghost socket holding your slot and reconnects hit the 4002 connection limit. PRO/ULTRA only.
445
-
446
- | Method | Tier | Description |
447
- |---|---|---|
448
- | `rest.streamSessions()` | PRO+ | List your live sessions β€” each with `id`, `service`, `tier`, `channels[]`, `connected_at`, `remote_ip`, `messages_sent`. Typed `StreamSessionsResponse` |
449
- | `rest.streamSessionKill(id)` | PRO+ | Terminate one of your sessions by `id` and free its slot. Throws on a bad id (400) or no matching live session (404). Typed `StreamSessionEvictResponse` |
450
-
451
- ```ts
452
- const { sessions } = await rest.streamSessions();
453
- for (const s of sessions) console.log(s.id, s.service, s.channels, s.messages_sent);
454
-
455
- // Free a stuck slot after a deploy overlap
456
- if (sessions.length) await rest.streamSessionKill(sessions[0].id); // { evicted: true, id }
457
- ```
458
-
459
- ## DEX Firehose (Ultra)
460
-
461
- Connect to `dex_ws_url` and use the multi-subscription protocol β€” up to **10 named subs per connection**, each with its own `sub_id`, server-side filters, and optional replay (up to 500 most recent matching trades) from a server-side buffer holding ~5 minutes of firehose history β€” it backfills trades from before your connection existed. Replayed trades arrive newest-first flagged `"replay": true`, then a `replay_done` frame; sort by `block_time` client-side.
462
-
463
- ```ts
464
- import WebSocket from "ws";
465
-
466
- const { token, dex_ws_url } = await rest.getStreamToken();
467
- const ws = new WebSocket(`${dex_ws_url}?token=${token}`); // token MUST be in the query string
468
-
469
- ws.on("open", () => {
470
- ws.send(JSON.stringify({
471
- type: "subscribe",
472
- sub_id: "fresh-pumpfun",
473
- replay: 50, // up to 500 from ring buffer
474
- filters: {
475
- dex: "pumpfun", // pumpfun | pumpamm | pumpswap | raydium | jupiter | orca | meteora | launchlab
476
- token_age_max_seconds: 300,
477
- min_sol: 0.5,
478
- action: "buy",
479
- },
480
- }));
481
- });
482
-
483
- ws.on("message", (raw) => {
484
- const msg = JSON.parse(raw.toString());
485
- if (msg.channel === "dex:trades") {
486
- // { sub_id, data: { wallet, mint, action, sol_amount, dex, ... }, replay, ts }
487
- }
488
- });
489
- ```
490
-
491
- **Operations** (all carry `sub_id`): `subscribe`, `update` (replace filters in place), `unsubscribe`, `list`, `ping`. **Filters:** `token_mint(s)` (≀50), `wallet(s)` (≀50), `dex`, `program`, `deployer_tier`, `token_age_max_seconds`, `market_cap_min/max_sol`, `min_sol`, `max_sol`, `action`. At least one targeting filter is required. Inbound rate limit: 5 messages/sec.
492
-
493
- Full protocol reference: [madeonsol.com/api-docs#streaming](https://madeonsol.com/api-docs#streaming).
494
-
495
- ## Rate-limit headers
496
-
497
- Every successful REST response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `X-Request-Id`. The SDK exposes them via `rest.lastRateLimit`:
498
-
499
- ```ts
500
- await rest.alphaLeaderboard();
501
- const { limit, remaining, reset, requestId } = rest.lastRateLimit;
502
- if (remaining !== null && remaining < 5) {
503
- console.warn(`Throttle warning β€” ${remaining}/${limit} requests left until ${reset}`);
504
- }
505
- ```
506
-
507
- ## Discovery
508
-
509
- ```ts
510
- const info = await client.discovery();
511
- console.log(info.endpoints); // all endpoints with prices and params
512
- ```
513
-
514
- Docs: [madeonsol.com/solana-api](https://madeonsol.com/solana-api)
515
-
516
- ## Also Available
517
-
518
- | Platform | Package |
519
- |---|---|
520
- | TypeScript SDK | [`madeonsol`](https://www.npmjs.com/package/madeonsol) on npm |
521
- | Rust SDK | [`madeonsol`](https://crates.io/crates/madeonsol) on crates.io |
522
- | Python (LangChain, CrewAI) | [`madeonsol-x402`](https://pypi.org/project/madeonsol-x402/) on PyPI |
523
- | 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/madeonsol/mcp-server-madeonsol) |
524
- | ElizaOS | [`@madeonsol/plugin-madeonsol`](https://www.npmjs.com/package/@madeonsol/plugin-madeonsol) |
525
- | Solana Agent Kit | [`solana-agent-kit-plugin-madeonsol`](https://www.npmjs.com/package/solana-agent-kit-plugin-madeonsol) |
1
+ # madeonsol-x402
2
+
3
+ [![npm version](https://img.shields.io/npm/v/madeonsol-x402?style=flat-square)](https://www.npmjs.com/package/madeonsol-x402)
4
+ [![npm downloads](https://img.shields.io/npm/dm/madeonsol-x402?style=flat-square)](https://www.npmjs.com/package/madeonsol-x402)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.4+-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](LICENSE)
7
+
8
+ > πŸ“‚ **[Examples](./examples/)** Β· πŸ“š **[API docs](https://madeonsol.com/api-docs)** Β· πŸ’° **[Get a free API key](https://madeonsol.com/pricing)**
9
+
10
+ TypeScript SDK for the [MadeOnSol](https://madeonsol.com) Solana KOL intelligence API.
11
+
12
+ <!-- Stats below are deliberate conservative floors kept in sync with the site's canonical labels (src/lib/constants.ts KOL_COUNT_LABEL / DEPLOYERS_PROFILED_LABEL / ALPHA_WALLETS_LABEL), rounded down from a live count measured on a known date and bumped only when the real count crosses the next threshold -- never the exact live number, which changes every minute. Do not replace with a live/volatile count. -->
13
+
14
+ > Real-time Solana trading intelligence: track 2,000+ KOL wallets with <3s latency on paid keys and x402 pay-per-call (free-tier live feeds are 5-min delayed), score 85K+ Pump.fun deployers, surface deshred deploy signals ~500ms before on-chain confirmation, score 1.5M+ early-buyer wallets (incl. dump-cluster detection), read bundle-cohort holdings (`held_pct_of_supply` β€” are the bundlers still holding?), verify any wallet's current on-chain holdings (with airdrop/insider `transfer_delta` detection), push every pump.fun graduation, and stream every DEX trade. 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).
15
+
16
+ > **New in 2.3.0 β€” named subscriptions: several independent subscriptions per socket.** `subscribe({ subId, channels, filters })`, `updateSubscription(subId, filters)`, `unsubscribe(subId)`, `getSubscriptions()` / `listSubscriptions()`. Each named subscription has its own channels and filters (the server caps the total per connection, default included: PRO 5, ULTRA 10, BUSINESS 20); frames carry `evt.sub_id`; an event matching several subscriptions is delivered once per subscription (dedupe per `(sub_id, id)`). Resume is per subscription with one commit for the connection. The plain `subscribe(channels, filters)` API is unchanged. See "Named subscriptions" in the stream section.
17
+
18
+ > **New in 2.2.0 β€” stream recovery: resume cursor, de-duplication, honest gaps.** The managed stream now tracks the cursor `{ instance, seq, ts }` of the last frame your handlers finished and resumes after it on every reconnect (the v1 `resume` request, with an automatic fallback to `replay_since_seq` / `replay_since_ts` on older servers). Delivery is at-least-once, de-duplicated by event `id`; new lifecycle events `cursor`, `replay`, `gap` (what could not be recovered β€” a `seq` gap is never loss) and `fatal`. Close codes are handled: 4001 re-fetches the token (bounded), 4002 waits β‰₯ 60 s instead of looping every second, 4003 stops, 4008 resumes; the backoff resets only after a `subscribed` ack. Every server `warning` frame is emitted (incl. `channels_rejected` / `channels_revoked`). `STREAM_CHANNELS` lists every Solana channel and `token:prices` joins the `StreamChannel` type. See the stream section's "Recovery" notes.
19
+
20
+ > **New in 2.0.0 β€” BREAKING for keyless (x402) mode only: an explicit `paymentPolicy` is required (security fix, SDK-01).** `new MadeOnSolX402({ privateKey, paymentPolicy: { payTo, feePayer, maxAmountAtomic, maxTotalAmountAtomic, rpcUrl } })` (optional `beforePayment`); the deprecated `MadeOnSolX402Options` type gains the same required field. Before, keyless mode signed whatever Solana USDC amount, recipient and fee payer a 402 challenge asked for. Now every challenge is checked BEFORE signing against a trusted merchant `payTo`, a trusted facilitator `feePayer` (which must differ from your wallet), the USDC mint, `solana:5eykt…` mainnet, the `exact` scheme, a per-call cap and a lifetime cap. Use the canonical values in the keyless section below; caps must be at least `20000` (0.02 USDC) per call to reach every endpoint. The budget is per client instance / process: not wallet-wide, not shared between processes, reset on a new instance or restart. Keyless requires the base URL exactly `https://madeonsol.com`. **API-key (`msk_`) users: no change, no new config.**
21
+
22
+ > **New in 1.30.0 β€” REST/x402 parity fix, top traders, and sniper detection.** Found by an internal agentic-infra coverage audit: `MadeOnSolREST` never got the free-tier/live-feed reads that `MadeOnSolX402` already had (`kolFeed`, `kolCoordination`, `kolLeaderboard`, `deployerAlerts`, `kolPairs`, `kolHotTokens`, `kolTrendingTokens`, `kolTokenEntryOrder`, `kolCompareWallets`, `kolAlertsRecent`), plus `tokenBatch`, `tokensBatchBuyerQuality`, and the sniper feature (`sniperByDeployer`, `sniperWatchlist`, `sniperWatchlistAdd`, `sniperWatchlistRemove` β€” `sniperRecent` already existed). `rest.tokenTopTraders(mint, params?)` β€” previously present but with no MCP/ElizaOS/SAK tool anywhere β€” and `rest.updateWebhook(id, params)` (PATCH) round out the surface.
23
+ >
24
+ > **New in 1.29.0 β€” deployer reputation as-of a date, and creator-fee rewards.** `rest.deployerAsOf(wallet, opts?)` (typed `DeployerAsOfResponse`) binds `GET /deployer-hunter/{wallet}/as-of`: the deployer's reputation exactly as it stood on `opts.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 the requested date (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. `rest.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`). 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+, KEYED (v1) only β€” `msk_` API key, no x402 route.**
25
+
26
+ > **New in 1.28.0 β€” token surges & revivals: momentum fires with the honest half attached.** `rest.tokensSurges(params?)` (typed `TokenSurgesResponse`) binds `GET /tokens/surges` (PRO+): every token momentum fire, newest first. Two kinds, one row shape. **`surge`** β€” a token < 30 min old whose market cap runs hard against its *launch* MC, in three tiers that each fire at most once per mint: `early` (≀10 min, β‰₯$12k, β‰₯3Γ— launch), `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Γ—). A tier must be **sustained** (current tick *and* a sample β‰₯10 s older; nothing fires before 20 s of age) β€” a same-slot bundle marked to $475k at age 1 s is a spike, not a surge. **`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), never by the price mark; `tier` is `null`. Hard gates on both: liquidity β‰₯$1.5k and β‰₯2 % of MC, and the MC gained must be **paid for** by buy volume (a price mark in a spoof pool moves MC on ~$0). Every row carries the burst `tape` (`source` candles / wallet_trades, `unique_buyers` only where the mint is in trade coverage β€” `wallet_data_available:false` otherwise, never an inferred zero), `kol` buyers, the first-20 `early_buyers` cohort (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` β€” typed `TokenSurgeRiskFlag`). Rows β‰₯65 min old carry the +1 h `outcome`; `stats: true` prints per-(kind, tier) hit-rates (`up_1h_pct`, `median_peak_multiple`, `doubled_1h_pct`) β€” 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`; subscribe filters `kinds[]`, `tiers[]`, `launchpads[]`, `exclude_flags[]`, `min_mc_usd` / `max_mc_usd`, `deployer_tier[]`) and accepted by the webhook registry as events `token:surge` / `token:revival` with the same filters. The response echoes the live thresholds in `definitions`. **PRO+, KEYED (v1) only β€” `msk_` API key, no x402 route.**
27
+
28
+ > **New in 1.27.1 β€” stream tokens never expire.** `POST /stream/token` (`rest.getStreamToken()`) now returns the **same token on every call, forever**. It stops working only if your subscription lapses or you call `rest.getStreamToken({ rotate: true })` to replace it (the previous value keeps working for 60 s). `StreamToken.expires_at` and the new `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. `rest.stream()` already does the right thing (it calls `getStreamToken()` on every (re)connect); no code change needed on your side.
29
+
30
+ > **New in 1.27.0 β€” token locks & vesting, upcoming unlocks, and pump.fun creator-fee sharing / fee claims β€” five endpoints + two live channels.** `rest.tokenLocks(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). `rest.tokenLocksFeed(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`). `rest.tokenUnlocks(params?)` (`GET /tokens/unlocks`) lists upcoming unlock EVENTS (cliff / period / final / tranche) inside `within=1h…90d` with `window_amount_*` per contract. `rest.tokenFeeShares(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; `rest.tokenFeeClaims(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+** and **KEYED (v1) only β€” `msk_` API key, no x402 route.**
31
+
32
+ > **New in 1.26.0 β€” live holder census: exact holder count, labelled holders, and pools that are named, not just excluded.** `rest.tokenHolders(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. **KEYED (v1) β€” requires an `msk_` API key; the census is not on the x402 rail.**
33
+
34
+ > **New in 1.25.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 β€” it is 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 `rest.tokenTrades(mint)` and `rest.walletTrades(address)` carry all four fields (typed on `TokenTrade` / `WalletTrade`) β€” `walletTrades` returned amounts and no price at all before.
35
+
36
+ > **New in 1.24.0 β€” the Deployer Hunter surface completed.** Seven new operations that existed on the API but had no SDK binding: `deployerLeaderboard()`, `deployerStats()`, `deployerProfile()`, `deployerTokens()`, `deployerAlertStats()`, `deployerBestTokens()` and `deployerRecentBonds()` (poll it incrementally with `next_since`). Read `bonding_rate` (lifetime) against `recent_bond_rate` (rolling) β€” the gap between them is the signal, not either number alone. `runner_rate` only means something once `labeled_tokens >= 3`, and an **untracked wallet returns a profile with zeroed counters, not a 404**, so check `total_deployed` before reading a 0% bond rate as a track record. Dependency ranges are now bounded to the versions actually tested (`@x402/*` `^2.x`, `@solana/kit` `^5.5.1`) instead of open-ended `>=0.0.1`, and the lazily-imported x402 peers are marked optional β€” a keyed install no longer pulls the whole Solana stack.
37
+
38
+ > **New in 1.23.0** β€” **Clean stream shutdown.** `rest.stream().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`.)
39
+ >
40
+ > **New in 1.22.0** β€” **Token depth / price impact + deployer self-activity on risk.** `rest.tokenDepth(mint, { sizes? })` (`GET /tokens/{mint}/depth`, PRO+) answers "how much SOL moves this token's price N%" β€” per pool, not router-optimal. Pass up to 8 SOL buy `sizes` (each >0 and ≀10000; default `[0.5, 1, 5, 10]`, sent as a CSV `sizes` param); every computable pool returns `spot_price_sol`, `fee_pct`, a `quotes[]` entry per size (`tokens_out`, `avg_price_sol`, `price_impact_pct`), and `to_move_price` β€” the SOL required to move price **1% / 5% / 10%**. Constant-product AMMs are served from stream reserves (`source: "stream"` with `reserves_age_ms`); pump.fun/bonk curves from a **live** read of the curve's virtual reserves (`source: "live_rpc"`). Pools we can't price honestly β€” concentrated CLMM/Orca/DLMM, Meteora-DBC curves, unclassified models β€” come back in `unsupported_pools[]` with a `reason` (e.g. `concentrated_liquidity_depth_not_supported`, `curve_graduated_use_amm_pool`) rather than a wrong number; `primary_pool` names the deepest computable pool and `found: false` means no pools are tracked at all. Typed `TokenDepthResponse` (+ `TokenDepthParams`, `TokenDepthPool`, `TokenDepthQuote`, `TokenDepthToMovePrice`, `TokenDepthUnsupportedPool`). **KEYED (v1) β€” requires an `msk_` API key; there is no x402 route.** And `rest.tokenRisk(mint)` now returns a top-level **`dev` block** (typed `TokenRiskDev | null`) β€” the deployer's self-activity on its own mint: the create-tx self-buy snapshot (`buy_sol`, `buy_tokens`, `buy_supply_pct`), the post-create rollup (`bought_tokens_after` β€” catches the same-second-separate-tx dev buy the create snapshot reads as 0 β€” `sold_tokens`, `sold_sol`, `first_sell_at`/`last_sell_at`), **live on-chain holdings** (`holdings_tokens`, `holdings_supply_pct` β€” pump.fun 1B denominator, null elsewhere β€” and `wallet_empty`: is the dev wallet empty NOW), and `transferred_out` (tokens left without a sell; `null` = unknown when trade coverage or rollup freshness can't prove it β€” never a guess). `dev` is `null` when the mint has no pending_deploys row; the response also carries `as_of`. `deployer:alert` webhook/WS payloads gain `dev_buy_sol` + `dev_buy_supply_pct` β€” the dev's self-buy visible at alert time.
41
+ >
42
+ > **New in 1.21.0** β€” **Wallet batch classify, token trade tape, sniper footprint, and 7 new x402-payable endpoints.** `rest.walletClassify(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 (typed `WalletBatchClassifyResponse`) β€” flags are pump.fun-pipeline scoped (`false` = not observed, NOT verified clean). `rest.tokenTrades(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`). `rest.tokenTopTraders(mint, params?)` and `rest.sniperRecent(params?)` are new keyed methods too. The wallet profile `flags` block 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, `SniperFootprint | null`) and sniper deploys each carry the same `footprint` block. **x402 catalog grew 18 β†’ 25**: `tokenCandles` ($0.01), `almostBonded` ($0.01), `tokenTopTraders` ($0.02), `tokenCapTable` ($0.02), `sniperRecent` ($0.01), `tokenFlow` ($0.01 β€” the 1.16 keyed-only guard is gone), and `deployerTrajectory` ($0.01) are now callable on the `MadeOnSolX402` client with per-request USDC micropayments. New types: `WalletClassification`, `WalletBatchClassifyResponse`, `TokenTradesParams`, `TokenTrade`, `TokenTradesResponse`, `TokenTopTradersParams`, `TokenTopTrader`, `TokenTopTradersResponse`, `SniperRecentParams`, `SniperDeploy`, `SniperRecentResponse`, `SniperFootprint`, `DumpClusterStats`.
43
+ >
44
+ > **New in 1.20.0** β€” **Verified wallet holdings.** `rest.walletHoldings(wallet, { limit?, min_value_usd? })` 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 `walletPositions` (trade-derived FIFO): holdings is "what they actually hold right now". Returns typed `WalletHoldingsResponse` with a `summary` (token_accounts / non_zero / returned / priced / total_value_usd / truncated) and `verified_at`. **KEYED (v1) β€” requires an `msk_` API key; there is no x402 route.** ULTRA only.
45
+ >
46
+ > **New in 1.19.0** β€” **Bundle-cohort holdings.** `rest.tokenBundle(mint)` returns the bundle wallets' current position for a token β€” the "are the bundlers still holding, or did they dump on you?" read. The `bundle` block carries `wallet_count`, `bundle_kind` (`atomic_tx` / `same_slot` / `none`), `held_ratio` (net held / buy volume β€” churn-sensitive secondary), **`held_pct_of_supply`** (net held / circulating supply β€” the headline signal; null when supply is unknown), `fully_exited`, `buy_volume`, and `tokens_held` (typed `TokenBundleResponse`). Field-gated by tier: BASIC get the `bundle` block only (`wallets: []`); PRO adds the top-10 `wallets` with flags (`has_sold`, `atomic`, `is_kol`); ULTRA returns the full cohort plus per-wallet identity (`kol_name`, `win_rate`, `bot_confidence`, `tokens_held`). All tiers reach it.
47
+ >
48
+ > **New in 1.18.0** β€” **Batch risk scoring + live stream-session control.** `rest.tokensBatchRisk(mints)` scores up to 50 mints in one call (counts as 1 request) β€” each entry in `tokens` is either a full risk result (same shape as `rest.tokenRisk(mint)`, plus `as_of`) or `{ mint, error: "not_tracked" }`; untracked mints don't fail the batch, and `tokens` preserves de-duplicated input order (typed `TokenBatchRiskResponse`). PRO/ULTRA only. Plus `rest.streamSessions()` lists your live WebSocket sessions across ws-streaming + dex-stream (typed `StreamSessionsResponse`), and `rest.streamSessionKill(id)` force-releases a slot by id (typed `StreamSessionEvictResponse`) β€” the self-serve fix for a 4002 lockout when a deploy overlap leaves a ghost socket holding your slot. PRO/ULTRA only.
49
+ >
50
+ > **New in 1.17.0** β€” **Almost-bonded discovery + trending sorts.** `rest.almostBonded({ min_progress?, max_progress?, min_velocity_pct_per_min?, max_age_minutes?, deployer_tier?, authority_revoked?, min_liq?, sort?, limit? })` returns 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` (typed `AlmostBondedResponse`). `sort` is `velocity_desc` (default) / `progress_desc` / `eta_asc`. **KEYED (v1) β€” requires an `msk_` API key; there is no x402 route.** PRO/ULTRA only. Plus `client.tokensList({ sort })` gains four momentum sorts β€” `mc_change_5m_desc`, `mc_change_1h_desc`, `volume_1h_desc`, and `trending` (composite recent-volume Γ— positive-momentum rank).
51
+ >
52
+ > **New in 1.16.0** β€” **Token trade flow.** `client.tokenFlow(mint, { window? })` returns a trade-flow aggregate over a `1h`/`24h` window β€” `unique_wallets` / `unique_buyers` / `unique_sellers`, `buy_count` / `sell_count` / `total_trades`, `buy_sol` / `sell_sol` / `net_sol`, and a `trades_per_wallet` wash-trading proxy (typed `TokenFlowResponse`). It's an **organic-vs-fake volume** read. **KEYED (v1) β€” requires an `msk_` API key; there is no x402 route**, so x402-only clients can't reach it. PRO/ULTRA only. Deployer alerts now carry `deployers.deployer_sol_balance` β€” the deployer wallet's SOL balance at alert time (null for historical rows).
53
+ >
54
+ > **New in 1.15.0** β€” **Live token snapshot + Signal Scorecard.** `rest.token(mint)` returns a live snapshot β€” price (USD/SOL), VWAP, market cap, FDV, liquidity, liquidity-to-MC ratio, primary DEX + pool, Token-2022 / transfer-fee flags, and a `top_buyers[]` array (typed `TokenSnapshotResponse`). `rest.signalPerformance(name, { history? })` returns the **Signal Scorecard** β€” out-of-sample reliability buckets (hit_rate, base_rate, lift, sample_n, window_days) for `dump_cluster_count`, `runner_rate`, `recycled_early_buyer_count`, or `coordination_count`, with a per-day `series` when `history: true` (typed `SignalPerformanceResponse`). `rest.signals()` is the free catalog of all scored signals (typed `SignalsCatalogResponse`). `rest.tokenRisk(mint)` and `rest.tokenBuyerQuality(mint)` are now fully live server-side.
55
+ >
56
+ > **New in 1.13.0** β€” **Token risk score.** `rest.tokenRisk(mint)` returns a transparent 0–100 rug-risk/safety score (higher = riskier) with a `band` (safe/caution/danger), an explainable `factors[]` array, and the raw `inputs` (mint/freeze authority, liquidity, liq-to-MC ratio, transfer fee, launch cohort, deployer bond rate, KOL signal, blacklist). Typed as `TokenRiskResponse`. PRO/ULTRA only.
57
+ >
58
+ > **New in 1.12.0** β€” `/token/{mint}` and `/token/batch` responses now include `liquidity_to_mc_ratio`, `launch_cohort_sol`, and `launch_cohort_size`. `/tokens` gains three new filter params: `min_liq_mc_ratio`, `max_liq_mc_ratio`, and `deployer_tier`. `/tokens` list items now include `liquidity_to_mc_ratio` and `deployer_tier`. `/kol/leaderboard` entries now include `median_hold_minutes_30d` and `percentile_early_entry_30d`.
59
+ >
60
+ > **New in 1.11.1** β€” Deployer profiles now carry `runner_rate` + `labeled_tokens` (fraction of a deployer's labeled tokens that ran vs dumped, gate on `labeled_tokens` β‰₯3) plus `avg_time_to_bond_minutes`, on `DeployerAlert.deployers` and the deployer-trajectory profile.
61
+ >
62
+ > **New in 1.11** β€” **Graduation events + dump-cluster detection.** Subscribe `token:graduations` for every pump.fun bond in real time (tracked deployer or not, typed `GraduationEvent`). Buyer-quality `breakdown` adds `dump_cluster_count` (out-of-sample: 3+ β†’ 94% dump vs 61% base) + `recycled_early_buyer_count`. DEX firehose: replay buffer deepened to ~5 min; mint-scoped subs get in-band `dex:graduations` frames.
63
+
64
+ > **New in 1.10** β€” **Deshred Sniper.** Deshred deploy feed ~500ms before on-chain confirmation (SDK method `rest.sniperRecent()` shipped in 1.21). PRO: elite/good. ULTRA: all tiers + watchlist. Use `sniper:deploys` WebSocket for push.
65
+ >
66
+ > **New in 1.9** β€” **Price alerts, scout leaderboard, coordination history.** `rest.priceAlertsCreate()` (PRO=5, ULTRA=25). `scoutLeaderboard()`, `kolConsensus()`, `peakHistory()`, `coordinationHistory()`. `walletStats()` now returns `derived`: win_rate, roi, verdict, biggest_miss.
67
+ >
68
+ > **New in 1.8** β€” **Universal Wallet API.** `rest.walletStats()`, `rest.walletPnl()`, `rest.walletPositions()`, `rest.walletTrades()` β€” FIFO cost-basis PnL for any Solana wallet. PRO+. Cache hits free.
69
+ >
70
+ > **New in 1.7.1** *(2026-05-13)* β€” Velocity field shape corrected to match the API: `mc_change_pct`, `volume_usd`, `mev_volume_pct` are top-level on the token response, each keyed by `5m`/`15m`/`1h`/`2h`/`4h`. The 1.7.0 README documented a `velocity[window]` shape that didn't match the wire format. Runtime is unchanged β€” fix is to typed shape + docs.
71
+ >
72
+ > **New in 1.7.0** *(2026-05-12)* β€” **Token directory + account inspection.** `client.tokensList({ min_liq, min_volume_1h_usd, max_mev_share_pct, mc_change_1h_min_pct, sort, min_liq_mc_ratio, max_liq_mc_ratio, deployer_tier, ... })` filters every active mint by MC band, liquidity floor, primary DEX, authority/safety flags, computed 1h volume, MEV-share ceiling, MC-change deltas, liq/MC ratio, and deployer tier. Response items now include `liquidity_to_mc_ratio` and `deployer_tier`. Default `min_liq=2000` skips phantom-MC dust; pass `min_liq=0` to opt out. `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 token response: `mc_change_pct`, `volume_usd`, `mev_volume_pct` (each keyed by `5m`/`15m`/`1h`/`2h`/`4h`) plus `history_age_seconds`. `/token/{mint}` 400s now ship structured `code`, `reason`, `received_length`, `example`, and `docs` β€” stop guessing why a mint failed. Deprecated `avg_entry_mc_usd` fully removed.
73
+
74
+ ## Quick start (10 seconds)
75
+
76
+ ```bash
77
+ npm install madeonsol-x402
78
+ ```
79
+
80
+ ```ts
81
+ import { createClient } from "madeonsol-x402";
82
+ const client = createClient("msk_..."); // free tier at https://madeonsol.com/pricing
83
+ const { trades } = await client.kolFeed({ limit: 5 });
84
+ ```
85
+
86
+ ## Authentication
87
+
88
+ Two options:
89
+
90
+ | Method | Option | Best for |
91
+ |---|---|---|
92
+ | **MadeOnSol API key** (recommended) | `apiKey` | Developers β€” [get a free key](https://madeonsol.com/pricing) |
93
+ | x402 micropayments | `privateKey` | AI agents with Solana wallets |
94
+
95
+ > **v1.0 breaking change:** RapidAPI auth has been removed. The MadeOnSol RapidAPI marketplace was retired on 2026-04-19. If you were using `rapidApiKey`, get a free `msk_` key at [madeonsol.com/pricing](https://madeonsol.com/pricing).
96
+
97
+ ## Install
98
+
99
+ ```bash
100
+ npm install madeonsol-x402
101
+ ```
102
+
103
+ > x402 peer deps (`@x402/fetch @x402/svm @x402/core @solana/kit @scure/base`) are only needed when using `privateKey`.
104
+
105
+ ## Quick Start
106
+
107
+ ```ts
108
+ import { createClient } from "madeonsol-x402";
109
+
110
+ // Option 1: API key β€” get one free at madeonsol.com/pricing
111
+ const client = createClient("msk_your_api_key_here");
112
+
113
+ // Option 2: x402 micropayments (auto-detected when no msk_ prefix)
114
+ // const client = createClient(process.env.SVM_PRIVATE_KEY!, undefined, paymentPolicy);
115
+ // See the required paymentPolicy example below.
116
+
117
+ const { trades } = await client.kolFeed({ limit: 10 });
118
+ console.log(trades);
119
+ ```
120
+
121
+ ### Advanced initialization
122
+
123
+ Keyless mode requires `paymentPolicy`. API-key mode does not initialize signing or require these settings.
124
+
125
+ ```ts
126
+ import { MadeOnSolX402, type SolanaPaymentPolicy } from "madeonsol-x402";
127
+
128
+ const paymentPolicy: SolanaPaymentPolicy = {
129
+ payTo: "GLu63pRCYrp4BJu5P5ciYKxgeZFW9c8TJ8jWzK3TB9AR", // canonical merchant (see below)
130
+ feePayer: "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4", // PayAI facilitator fee payer (see below)
131
+ maxAmountAtomic: "20000", // 0.02 USDC per authorization (the highest Solana leg)
132
+ maxTotalAmountAtomic: "1000000", // 1 USDC across this client's lifetime
133
+ rpcUrl: process.env.SVM_RPC_URL!, // your trusted HTTPS RPC
134
+ timeoutMs: 30_000,
135
+ // Optional additional approval; literal true is required if this hook is set.
136
+ beforePayment: async proposal => BigInt(proposal.amountAtomic) <= 20000n,
137
+ };
138
+ const client = new MadeOnSolX402({ privateKey: process.env.SVM_PRIVATE_KEY!, paymentPolicy });
139
+ // Equivalent: createClient(privateKey, undefined, paymentPolicy).
140
+ console.log(client.authorizedAmountAtomic);
141
+ ```
142
+
143
+ **Canonical MadeOnSol values (Solana mainnet USDC).** Pinned here (GitHub + npm README) so you do not have to take them from a 402:
144
+ - merchant `payTo` / `X402_PAY_TO`: `GLu63pRCYrp4BJu5P5ciYKxgeZFW9c8TJ8jWzK3TB9AR` (also shown on https://madeonsol.com/x402 and https://madeonsol.com/.well-known/x402)
145
+ - facilitator `feePayer` / `X402_FEE_PAYER`: `2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4`. This is the fee payer of **PayAI**, the third-party facilitator MadeOnSol's Solana rail uses. If PayAI rotates it, keyless calls fail closed (the client refuses to sign) until you update this value; a MadeOnSol release will announce the new one.
146
+ - prices: Solana legs are 5000–20000 atomic (0.005–0.02 USDC), so `maxAmountAtomic` / `X402_MAX_AMOUNT_ATOMIC` must be at least `20000` to reach every endpoint.
147
+
148
+ The budget is per client instance / process: not wallet-wide, not shared between processes, reset when a new instance or process starts. Keyless mode requires the base URL exactly `https://madeonsol.com`.
149
+
150
+ **Breaking keyless upgrade:** missing policy now fails closed. Only exact mainnet USDC payments to the configured merchant and facilitator are signed. The agent cannot also be the facilitator fee payer. URLs must use HTTPS; requests stay on the configured API origin and redirects are refused. There is no public RPC fallback. Amounts use positive integer strings or bigint, never floating-point numbers; USDC has 6 decimals. Choose caps for the endpoints you use.
151
+
152
+ Reuse one long-lived client. Concurrent calls share its allowance. An unsigned approval denial releases its reservation; once payment creation starts, the allowance remains consumed even after RPC/signing/network errors or an ambiguous response. This is **authorized attempts, not settled spend**, with no automatic refund or replay. The frozen `beforePayment` proposal cannot override built-in checks.
153
+
154
+ The 30-second default bounds challenge reading, approval, signing waits and submission. Late RPC results cannot invoke the signer after timeout. Solana transaction validity still follows the signed recent blockhash; the client deadline cannot revoke a proof already sent. Response-body consumption after returned headers is not covered by this deadline.
155
+
156
+ A new client or process starts a new allowance. These limits are not a durable, wallet-wide budget; coordinate externally when multiple agents/processes share a wallet. API-key behavior and precedence are unchanged.
157
+
158
+ ## x402 Endpoints (per-request micropayments)
159
+
160
+ | Method | Description |
161
+ |---|---|
162
+ | `kolFeed(params?)` | Real-time KOL trade feed from 1,000+ tracked wallets |
163
+ | `kolCoordination(params?)` | Tokens being accumulated by multiple KOLs simultaneously |
164
+ | `kolLeaderboard(params?)` | KOL performance rankings by PnL and win rate (180 days of trade history) |
165
+ | `kolPairs(params?)` | KOL affinity matrix β€” which KOLs frequently co-trade the same tokens |
166
+ | `kolHotTokens(params?)` | KOL momentum tokens β€” accelerating KOL buy interest |
167
+ | `kolTokenEntryOrder(mint, params?)` | Ranked KOL first-buyer order for a token |
168
+ | `kolCompareWallets({ wallets })` | Side-by-side comparison of 2–5 KOL wallets |
169
+ | `kolAlertsRecent(params?)` | Live KOL alert feed β€” clusters, fresh-token buys, heating-up wallets |
170
+ | `deployerAlerts(params?)` | Pump.fun deployer alerts with KOL enrichment. PRO/ULTRA: filter by tier. |
171
+ | `walletStats(address)` | **New 1.8** Β· Wallet stats + cross-product flags (is_kol / is_alpha_tracked + bot_confidence / is_deployer). 90-day window. **$0.005** |
172
+ | `walletPnl(address)` | **New 1.8** Β· FIFO cost-basis PnL: realized + unrealized SOL, profit factor, drawdown, hold times, daily curve, closed + open positions. **$0.02** |
173
+ | `walletPositions(address)` | **New 1.8** Β· Open positions only, live unrealized from market-cap tracker. Shares /pnl cache. **$0.01** |
174
+ | `walletTrades(address, params?)` | **New 1.8** Β· Cursor-paginated raw trades with action / token / since-until filters. **$0.005** |
175
+ | `tokenFlow(mint, params?)` | Trade-flow aggregate (organic-vs-fake volume) β€” unique wallets/buyers/sellers, buy/sell counts + SOL, net SOL, `trades_per_wallet` wash-trading proxy. `window` ("1h" \| "24h", default "1h"). **Now x402-payable (1.21).** **$0.01** |
176
+ | `tokenCandles(mint, params?)` | **New 1.21** Β· OHLCV candles (1m–1d timeframes, 30d history) with per-candle volume, trade count, and market cap. **$0.01** |
177
+ | `almostBonded(params?)` | **New 1.21** Β· Launchpad tokens approaching graduation (pump.fun + LetsBonk LaunchLab) β€” bonding progress, velocity (Ξ”progress/min), ETA, deployer tier. **$0.01** |
178
+ | `tokenTopTraders(mint, params?)` | **New 1.21** Β· Wallets ranked by realized PnL (or ROI) on a token, enriched with KOL identity + alpha reputation. **$0.02** |
179
+ | `tokenCapTable(mint)` | **New 1.21** Β· Early-buyer cap table β€” first 10 non-deployer buyers with PnL, exit status, bundle/KOL/alpha flags + buyer-quality score. **$0.02** |
180
+ | `sniperRecent(params?)` | **New 1.21** Β· Deshred sniper deploy feed (elite/good deployers) with per-deploy snipe `footprint`. **$0.01** |
181
+ | `deployerTrajectory(wallet, params?)` | **New 1.21** Β· Deployer bond-rate trajectory β€” streaks, rolling bond rates, trend, cadence. `include: "daily_snapshots"` adds 90 days. **$0.01** |
182
+ | `discovery()` | Lists all 25 endpoints, prices, and parameter docs (free) |
183
+
184
+ ## REST API client
185
+
186
+ The `MadeOnSolREST` class exposes the full v1 API (alpha intelligence, token quality, copy-trade rules, wallet tracker, webhooks, streaming). Most endpoints require a Pro or Ultra subscription.
187
+
188
+ ```ts
189
+ import { MadeOnSolREST } from "madeonsol-x402";
190
+
191
+ const rest = new MadeOnSolREST({ apiKey: "msk_your_key" });
192
+ const { leaderboard } = await rest.alphaLeaderboard({ period: "30d", sort: "win_rate" });
193
+
194
+ // Rate-limit headers from the most recent response
195
+ console.log(rest.lastRateLimit); // { limit, remaining, reset, requestId }
196
+ ```
197
+
198
+ ### Alpha wallet intelligence
199
+
200
+ Scored from 1.5M+ early-buyer records (wallets seen in the first 20 buyers of Pump.fun tokens).
201
+
202
+ | Method | Tier | Description |
203
+ |---|---|---|
204
+ | `rest.alphaLeaderboard(params?)` | All | Top profitable wallets. Up to 100 on Free/Pro; ULTRA unlocks 500 + bot signals |
205
+ | `rest.alphaWallet(wallet)` | ULTRA | Full per-token breakdown + bot_signals array |
206
+ | `rest.alphaLinked(wallet)` | ULTRA | Wallets behaviorally linked (co-bought 3+ tokens within 2s) |
207
+
208
+ **alphaLeaderboard params** β€” `period` ("7d" \| "30d" \| "all"), `min_tokens` (1–20), `sort` ("win_rate" \| "pnl" \| "roi"), `exclude_bots` ("true" \| "false")
209
+
210
+ ### Token quality
211
+
212
+ | Method | Tier | Description |
213
+ |---|---|---|
214
+ | `rest.token(mint)` | All | **New 1.15** Β· Live token snapshot β€” price (USD/SOL), VWAP, market cap, FDV, liquidity, liq-to-MC ratio, primary DEX + pool, Token-2022 / transfer-fee flags, and `top_buyers[]`. Returns `{ token }` |
215
+ | `rest.tokenCapTable(mint)` | PRO+ | First non-deployer early buyers, enriched with PnL/KOL/bot flags. PRO=10, ULTRA=20 |
216
+ | `rest.tokenBuyerQuality(mint)` | All | 0–100 buyer-quality score + full breakdown (5-min cached). Live server-side |
217
+ | `rest.tokenRisk(mint)` | PRO+ | Transparent 0–100 rug-risk/safety score with `band`, explainable `factors[]`, and raw `inputs`. **1.22:** adds a top-level `dev` block (`TokenRiskDev \| null`) β€” deployer self-buy at create, sells rollup, live on-chain holdings, `wallet_empty`, `transferred_out`. Live server-side |
218
+ | `rest.tokenBundle(mint)` | All | **New 1.19** Β· Bundle-cohort holdings β€” `bundle` block (`wallet_count`, `bundle_kind`, `held_ratio`, headline `held_pct_of_supply`, `fully_exited`, `buy_volume`, `tokens_held`). BASIC = block only; PRO = top-10 `wallets` + flags; ULTRA = full cohort + identity fields |
219
+ | `rest.tokenPools(mint)` | PRO+ | **New 1.19.2** Β· Per-venue liquidity map β€” every DEX pool a token trades in (`pool_address`, `dex`, `liquidity_usd`, `last_price_sol`, `is_active`), plus a `summary` rollup (`pool_count`, `active_pool_count`, `dex_count`, `total_liquidity_usd`, `primary_pool`/`primary_dex`, `top_pool_share_pct`) |
220
+ | `rest.tokenDepth(mint, params?)` | PRO+ | **New 1.22** Β· Per-pool price impact / slippage β€” `quotes[]` per SOL buy size (`tokens_out`, `avg_price_sol`, `price_impact_pct`), `to_move_price` (SOL to move price 1%/5%/10%), `spot_price_sol`, `fee_pct`. Pools we can't price honestly land in `unsupported_pools[]` with a `reason`. `sizes` max 8, default `[0.5, 1, 5, 10]` |
221
+ | `rest.tokenHolders(mint)` | PRO+ | **New** Β· Live holder census + concentration β€” who holds NOW (vs `tokenCapTable` = who bought first). `concentration.holder_count` is EXACT (mint-scoped `getProgramAccounts` census, merged per owner; `null` only when the provider refuses a mega-cap β†’ top-20 `getTokenLargestAccounts` fallback with `source.census_fallback_reason` β€” never trade-estimated). Each disclosed owner labelled `deployer` / `kol` / `early_buyer` / `bundle` / `bot` / `dump_cluster` (empty = unknown, not clean). Pools / bonding curves / burns EXCLUDED from the circulating denominator and NAMED in `excluded[]` (`reason`: `pool` + `dex` + `pool_address`, `bonding_curve`, `burn`, `program_account`). `amount_raw` / `supply_raw` / `circulating_raw` are raw u64 STRINGS. Disclosure PRO 10 / ULTRA 50 / BUSINESS 100; maths tier-independent. Big tokens: first call may be HTTP 503 `holder_scan_in_progress` (`retry_after_seconds: 20`) β€” the scan continues and is cached, the retry is instant. Keyed only (no x402 route) |
222
+ | `rest.tokenLocks(mint, params?)` | PRO+ | **New 1.27** Β· Token locks & vesting on a mint β€” every Streamflow / Jupiter Lock / Bonfida vesting contract with a live-derived view (`locked_raw` still locked, `unlocked`, `withdrawn`, `claimable`, `status`, `next_unlock`, `cancelable_by_sender`) + `summary` (locked / deposited raw + ui + usd + % of supply, `unlocking_7d` / `unlocking_30d`, nearest `next_unlock`, `active_cancelable_by_sender`). Params `status`, `program`, `limit` (≀500). Base-unit amounts are STRINGS; ui/usd/pct null when unknown. **LP locks NOT included.** Keyed only (no x402 route) |
223
+ | `rest.tokenLocksFeed(params?)` | PRO+ | **New 1.27** Β· Cross-token feed of NEW lock / vesting contracts, newest first (same row + `token` facts). Cursor `since` = `pagination.next_since`, `before` = `next_before`; filters `mint`, `sender`, `recipient`, `program`, `kind`, `status`, `min_usd`, `min_pct_of_supply`, `include_estimated` (backfilled Jupiter rows). Pushed live on WS `token:locks`. Keyed only |
224
+ | `rest.tokenUnlocks(params?)` | PRO+ | **New 1.27** Β· Upcoming unlock EVENTS across all active contracts inside `within` (1h Β· 6h Β· 24h Β· 3d Β· 7d Β· 14d Β· 30d Β· 90d) β€” each contract's NEXT cliff / period / final / tranche with `amount_*` + `window_amount_*` (total over the window). `sort` soonest Β· largest_usd Β· largest_pct; filters `mint`, `program`, `kind`, `min_usd`, `min_pct_of_supply`. Keyed only |
225
+ | `rest.tokenFeeShares(mint)` | PRO+ | **New 1.27** Β· pump.fun creator-fee sharing on a coin β€” the on-chain `SharingConfig` (`admin`, `shareholders[]` with `share_bps` / `is_admin` / `is_social_pda` + `social` identity (platform 2 = X, `user_id` = numeric id, lifetime claimed), `redirected_bps`, `social_bps`, `is_default` = 100% to creator, `source` stream/chain) + `distributions` rollup per recipient, `past_recipients`, `history` (config changes / creator transfers), `recent_distributions`. Quote base units as STRINGS. **History starts 2026-08-17.** Keyed only |
226
+ | `rest.tokenFeeClaims(params?)` | PRO+ | **New 1.27** Β· pump.fun fee-event feed, newest first β€” `distribution` (with pro-rata `payouts[]`), `social_claim`, `shares_created` / `shares_updated` / `shares_reset`, `creator_transferred`, `creator_claim` (only when asked via `type`). Filters `type` (comma list), `mint`, `recipient`, `actor`, `social_platform`, `social_user_id`, `min_sol`; cursor `since` = `pagination.next_since`. Pushed live on WS `token:fee_claims`. **History starts 2026-08-17.** Keyed only |
227
+ | `rest.tokensSurges(params?)` | PRO+ | **New 1.28** Β· Token momentum fires, newest first β€” `kind` `surge` (token < 30 min old vs its LAUNCH MC; `tier` `early` ≀10 min β‰₯$12k β‰₯3Γ— Β· `strong` ≀30 min β‰₯$30k β‰₯6Γ— and β‰₯2Γ— the 3-min low Β· `breakout` ≀2 min β‰₯$45k β‰₯8Γ—; each once per mint, sustained β‰₯10 s) or `revival` (no trade candle β‰₯24 h, then β‰₯5 buys / β‰₯$500 buy volume / β‰₯1.5Γ— the pre-dormancy MC on the tape β€” never a price mark; `tier` null). Each row: burst `tape` (`unique_buyers` null outside trade coverage), `kol`, `early_buyers` (bundled / sold / sniper), `deployer`, `risk_flags[]`, and `outcome` (+1 h MC / peak / low) once β‰₯65 min old. `stats: true` = per-(kind, tier) hit-rates over `days`. Filters `kind`, `tier`, `mint`, `launchpad`, `deployer_tier`, `min_mc_usd` / `max_mc_usd`, `min_buys`, `exclude_flags` (comma list), `only_clean`; cursors `since` / `before`. Pushed live on WS `token:surges`. Retention 60 d. Keyed only |
228
+ | `rest.tokensBatchRisk(mints)` | PRO+ | **New 1.18** Β· Bulk risk scoring β€” up to 50 mints in one call (counts as 1 request). Each `tokens[]` entry is a full risk result or `{ mint, error: "not_tracked" }`; untracked mints don't fail the batch |
229
+ | `rest.tokenCandles(mint, params?)` | PRO+ | OHLC candles. PRO = OHLCV, last 30 days; ULTRA = + net flow (buy/sell volume, `net_volume_usd`, counts, MEV vol), liquidity delta, full history |
230
+ | `rest.tokenTrades(mint, params?)` | PRO+ | **New 1.21** Β· Mint-scoped trade tape β€” cursor-paginated raw trades (`price_sol`/`price_usd`, `early_buyer_rank`, `slot`), filter by `action`/`wallet`/`since`/`until`. Default window = **full history**; `coverage` block carries `history_start` (2026-04-12) + `scope` (pump.fun pipeline) |
231
+ | `rest.tokenTopTraders(mint, params?)` | PRO+ | **New 1.21** Β· Wallets ranked by realized PnL (or ROI) on a token β€” `sort` ("pnl" \| "roi"), `window_days` (1–180), `min_bought_sol`; enriched with KOL identity + alpha reputation (`bot_confidence`, historical win rate/PnL) |
232
+ | `rest.sniperRecent(params?)` | PRO+ | **New 1.21** Β· Deshred sniper deploy feed β€” PRO sees elite/good deployers, ULTRA all tiers. Each deploy carries a slot-window snipe `footprint` (`buys`/`buyers`/`sol`/`supply_pct`/`sniper_wallet_buys`; null until the ~10-min settle window) |
233
+
234
+ **tokenCandles params** β€” `tf` ("1m" \| "5m" \| "15m" \| "1h" \| "4h" \| "1d", default "1h"), `limit` (1–1000, default 200), `from` (ISO 8601), `to` (ISO 8601)
235
+
236
+ ```ts
237
+ // Score a basket in one request (counts as 1 against quota)
238
+ const { tokens, count } = await rest.tokensBatchRisk([mintA, mintB, mintC]);
239
+ for (const t of tokens) {
240
+ if ("error" in t) console.log(t.mint, t.error); // e.g. "not_tracked"
241
+ else console.log(t.mint, t.risk_score, t.band); // full risk result + as_of
242
+ }
243
+ ```
244
+
245
+ ### Signal Scorecard *(new in 1.15)*
246
+
247
+ Out-of-sample reliability for the scored early-buyer / coordination signals β€” every claim is backed by a hit-rate vs base-rate measurement so you can size positions on evidence, not vibes.
248
+
249
+ | Method | Tier | Description |
250
+ |---|---|---|
251
+ | `rest.signals()` | All (free) | Catalog of scored signals β€” name, methodology, and each signal's `performance_endpoint`. No payment required |
252
+ | `rest.signalPerformance(name, params?)` | All | Signal Scorecard for one signal β€” `buckets[]` (hit_rate, base_rate, lift, sample_n, window_days, test_from/test_to) + metric_type, outcome, methodology, as_of. Pass `{ history: true }` for a per-day `series[]` |
253
+
254
+ Valid signal names: `dump_cluster_count`, `runner_rate`, `recycled_early_buyer_count`, `coordination_count`.
255
+
256
+ ```ts
257
+ const { signals } = await rest.signals();
258
+ const scorecard = await rest.signalPerformance("dump_cluster_count", { history: true });
259
+ console.log(scorecard.buckets); // [{ bucket, hit_rate, base_rate, lift, sample_n, ... }]
260
+ ```
261
+
262
+ ### KOL coordination alerts (v1.1 β€” push signals)
263
+
264
+ Real-time push alerts when a cluster of KOLs co-buys the same token. Fires within ~1s of the triggering trade (pg_notify push, not polling). Delivered via WebSocket (`kol:coordination` channel, user-scoped) and/or HMAC-signed webhook. PRO=5 rules, ULTRA=20.
265
+
266
+ ```ts
267
+ // Create a rule
268
+ const { rule, webhook_secret } = await rest.coordinationAlertsCreate({
269
+ name: "fresh pump cluster",
270
+ min_kols: 4, // minimum distinct KOLs in window
271
+ window_minutes: 15, // peak-density window (1-60)
272
+ min_score: 70, // 0-100 composite score cutoff
273
+ include_majors: false, // filter WIF/BONK/POPCAT
274
+ cooldown_min: 60, // one fire per (rule,token) per 60min...
275
+ score_jump_break: 10, // ...unless score jumps +10 vs last fire
276
+ delivery_mode: "both",
277
+ webhook_url: "https://you.com/hooks/coord",
278
+ });
279
+ // β†’ store webhook_secret β€” shown ONCE
280
+ ```
281
+
282
+ `coordinationAlertsList`, `coordinationAlertsGet(id)`, `coordinationAlertsUpdate(id, params)`, `coordinationAlertsDelete(id)` round out the CRUD.
283
+
284
+ **Webhook signature:** `X-MadeOnSol-Signature: sha256=<hmac>` where `hmac = HMAC-SHA256(webhook_secret, timestamp + "." + rawBody)`, and `X-MadeOnSol-Timestamp` carries the unix seconds used.
285
+
286
+ **The `kolCoordination()` response** now includes v1.1 fields: `peak_window_start/end`, `peak_kols`, `peak_buys` (the busiest slice within the period), `exited_count` + per-KOL `exited` flag (net-flow-negative wallets), and `coordination_score` (0-100). Pass `min_score`, `window_minutes`, `include_majors` to filter.
287
+
288
+ ### KOL first-touch signal *(new in 1.3)*
289
+
290
+ Every "first KOL buy on a token mint" event β€” the moment a tracked KOL is the first of the cohort to touch a token. Filterable by **scout tier** (S/A/B/C from `mv_kol_scout_score`), KOL winrate, token age, mint suffix.
291
+
292
+ **Backtest:** top scouts attract β‰₯3 follow-on KOLs within 4h ~50% of the time vs ~14% baseline (38d / 491k buys / 72,549 events). Live leaderboard at [madeonsol.com/kol/scouts](https://madeonsol.com/kol/scouts).
293
+
294
+ ```ts
295
+ import { MadeOnSolREST } from "madeonsol-x402";
296
+ const rest = new MadeOnSolREST({ apiKey: process.env.MADEONSOL_API_KEY! });
297
+
298
+ // S-tier scouts on tokens younger than 1h
299
+ const { events } = await rest.firstTouches({ preset: "scout", min_scout_tier: "S" });
300
+
301
+ for (const e of events) {
302
+ console.log(e.first_kol.name, "scouted", e.token_symbol, `(scout_score=${e.first_kol.scout_score}%)`);
303
+ }
304
+ ```
305
+
306
+ Filter knobs: `since`, `before`, `limit`, `kol`, `min_kol_winrate_7d`, `min_scout_tier` (`"S"|"A"|"B"|"C"`), `min_n_touches`, `strategy`, `token_age_max_min`, `min_first_buy_sol`, `mint_suffix` (`"pump"`, `"bonk"`, …), `preset` (`"scout"`/`"fresh_launch"`), `include` (`"followers_4h"`).
307
+
308
+ > **Don't poll β€” push.** Median lead time before the second KOL is **12 seconds**. REST polling will miss the swarm. Subscribe to the `kol:first_touches` WebSocket channel (PRO+) or, on Ultra, create an HMAC-signed webhook subscription.
309
+
310
+ **Webhook subscriptions (Ultra)** β€” up to 10 active per user, mirrors `coordinationAlerts`:
311
+
312
+ ```ts
313
+ const { subscription, webhook_secret } = await rest.firstTouchSubscriptionsCreate({
314
+ name: "S-tier scouts on pump tokens",
315
+ filters: { min_scout_tier: "S", mint_suffix: "pump" },
316
+ delivery_mode: "webhook",
317
+ webhook_url: "https://my.bot/hooks/scout",
318
+ });
319
+ // β†’ store webhook_secret β€” shown ONCE
320
+ ```
321
+
322
+ `firstTouchSubscriptionsList`, `firstTouchSubscriptionsGet(id)`, `firstTouchSubscriptionsUpdate(id, params)`, `firstTouchSubscriptionsDelete(id)` round out the CRUD.
323
+
324
+ ### Price alerts *(new in 1.9)*
325
+
326
+ CRUD for token dip/recovery price alerts. Fires via WebSocket (`price_alert:events` channel) and/or HMAC-signed webhook when a token's market cap crosses your threshold. PRO=5 rules, ULTRA=25.
327
+
328
+ ```ts
329
+ const { alert, webhook_secret } = await rest.priceAlertsCreate({
330
+ name: "SOL dip buy",
331
+ token_mint: "So11111111111111111111111111111111111111112",
332
+ condition: "below", // "below" | "above"
333
+ threshold_mc_usd: 5_000_000_000,
334
+ cooldown_min: 120,
335
+ delivery_mode: "both",
336
+ webhook_url: "https://you.com/hooks/price",
337
+ });
338
+ // β†’ store webhook_secret β€” shown ONCE
339
+ ```
340
+
341
+ `priceAlertsList`, `priceAlertsGet(id)`, `priceAlertsUpdate(id, params)`, `priceAlertsDelete(id)` round out the CRUD.
342
+
343
+ ### Scout leaderboard & KOL consensus *(new in 1.9)*
344
+
345
+ | Method | Tier | Description |
346
+ |---|---|---|
347
+ | `rest.scoutLeaderboard(params?)` | PRO+ | Top scout-tier KOLs ranked by first-touch follow-on rate, win rate, and ROI |
348
+ | `rest.kolConsensus(params?)` | PRO+ | Tokens with the strongest KOL agreement signal β€” weighted by scout score and recent PnL |
349
+ | `rest.peakHistory(mint)` | PRO+ | Historical peak-density windows for a token β€” every coordination spike with KOL breakdown |
350
+ | `rest.coordinationHistory(params?)` | PRO+ | Global coordination event log with token, KOL count, score, and outcome |
351
+
352
+ ```ts
353
+ const { leaderboard } = await rest.scoutLeaderboard({ period: "30d", limit: 25 });
354
+ const { tokens } = await rest.kolConsensus({ min_kols: 5, period: "24h" });
355
+ ```
356
+
357
+ ### Wallet derived stats *(new in 1.9)*
358
+
359
+ `walletStats(address)` now includes a `stats` object with derived fields computed from the 90-day trade window:
360
+
361
+ ```ts
362
+ const { stats } = await rest.walletStats("WALLET_ADDRESS");
363
+ // stats.win_rate β€” fraction 0-1, tokens sold above cost basis
364
+ // stats.roi β€” aggregate return on invested SOL
365
+ // stats.verdict β€” "strong" | "profitable" | "neutral" | "losing"
366
+ // stats.biggest_miss β€” token with the highest post-exit gain the wallet missed
367
+ ```
368
+
369
+ ### Copy-trade rules
370
+
371
+ Server-side rules that fire signals when one of your watched source wallets trades. Delivered via webhook (HMAC-signed) and/or WebSocket. PRO=3 rules Γ— 5 source wallets each; ULTRA=20 Γ— 50.
372
+
373
+ | Method | Description |
374
+ |---|---|
375
+ | `rest.copyTradeList()` | List your rules |
376
+ | `rest.copyTradeCreate(params)` | Create a rule. Returns `webhook_secret` **once** β€” store it |
377
+ | `rest.copyTradeGet(id)` | Get one rule |
378
+ | `rest.copyTradeUpdate(id, params)` | Update fields or toggle `is_active` |
379
+ | `rest.copyTradeDelete(id)` | Delete permanently |
380
+ | `rest.copyTradeSignals(params?)` | Recent fired signals (up to 7 days). Filter by `subscription_id`, `since`, `limit` (1–500) |
381
+
382
+ ### Wallet tracker
383
+
384
+ Per-account watchlist with historical swap/transfer history.
385
+
386
+ | Method | Description |
387
+ |---|---|
388
+ | `rest.walletTrackerList()` | List tracked wallets + remaining capacity |
389
+ | `rest.walletTrackerAdd(wallet, label?)` | Add a wallet |
390
+ | `rest.walletTrackerRemove(wallet)` | Remove a wallet |
391
+ | `rest.walletTrackerUpdateLabel(wallet, label)` | Update label (pass `null` to clear) |
392
+ | `rest.walletTrackerTrades(params?)` | Historical events. Params: `wallet`, `action`, `event_type`, `limit` (1–200), `before` (cursor) |
393
+ | `rest.walletTrackerSummary(params?)` | Per-wallet stats. Params: `period` ("24h" \| "7d" \| "30d"), `wallet` |
394
+ | `rest.walletStats(address)` | **New 1.8** Β· Universal wallet stats (90d) + cross-product flags. PRO+. |
395
+ | `rest.walletPnl(address)` | **New 1.8** Β· Full FIFO PnL + curve + closed/open positions. PRO+. |
396
+ | `rest.walletPositions(address)` | **New 1.8** Β· Open positions only with live unrealized. PRO+. |
397
+ | `rest.walletTrades(address, params?)` | **New 1.8** Β· Cursor-paginated raw trades. Params: `limit` (1-500), `cursor`, `action`, `token_mint`, `since`, `until`. PRO+. |
398
+ | `rest.walletClassify(wallets)` | **New 1.21** Β· Bulk reputation flags for 1–100 wallets in one request β€” `is_sniper` / `is_bundler` (lifetime) / `is_dumper` (rolling 42d) / `is_kol` + `kol_name` / `bot_confidence` / `dump_cluster`. Pump.fun-pipeline scoped: `false` = not observed, NOT verified clean. PRO+. |
399
+
400
+ ### Webhooks
401
+
402
+ | Method | Description |
403
+ |---|---|
404
+ | `rest.createWebhook(params)` | Create webhook. Returns `secret` once β€” store it for HMAC verification |
405
+ | `rest.listWebhooks()` | List your webhooks |
406
+ | `rest.getWebhook(id)` | Get one + recent delivery log |
407
+ | `rest.updateWebhook(id, params)` | Update URL, events, filters, or re-enable |
408
+ | `rest.deleteWebhook(id)` | Delete |
409
+ | `rest.testWebhook(id)` | Send test payload |
410
+
411
+ ### KOL/deployer detail
412
+
413
+ | Method | Description |
414
+ |---|---|
415
+ | `rest.kolTiming(wallet, params?)` | Entry/exit timing β€” hold duration, exit speed, hour distribution |
416
+ | `rest.kolPnl(wallet, params?)` | Per-wallet PnL breakdown |
417
+ | `rest.deployerTrajectory(wallet)` | Deployer skill curve β€” streaks, rolling bond rate, trend |
418
+ | `rest.deployerHistory(wallet, opts?)` | **New 1.19.2** Β· PRO+ Β· Daily reputation time-series β€” backtest "was this deployer elite when it launched token X?" without look-ahead. `snapshots[]` carry per-day `tier`, `is_tracked`, `total_deployed`/`total_bonded`, `bonding_rate`, `recent_bond_rate`, `avg_peak_mc`, `best_token_peak_mc`. `opts.limit` (1–365, default 90) |
419
+
420
+ ### Streaming token
421
+
422
+ ```ts
423
+ const token = await rest.getStreamToken();
424
+ // token.ws_url β€” KOL/deployer streaming (Pro/Ultra)
425
+ // token.dex_ws_url β€” all-DEX trade stream (Ultra only)
426
+ // token.expires_at β€” always null: stream tokens never expire (1.27.1)
427
+ const fresh = await rest.getStreamToken({ rotate: true }); // replace it; old value works 60 s more
428
+ ```
429
+
430
+ Stream tokens **never expire**: the same token comes back on every call until your subscription lapses or you rotate it. Send it as `Authorization: Bearer <token>` on the WebSocket handshake (`?token=` still works). A `4001` close means "mint again", never a timer.
431
+
432
+ ### Managed streaming client *(new in 1.10)*
433
+
434
+ `rest.stream()` handles the token fetch (the token never expires β€” `getStreamToken()` is called on every (re)connect), auto-reconnect (backoff + jitter), heartbeat liveness, and typed events β€” just subscribe and listen.
435
+
436
+ ```ts
437
+ const stream = rest.stream();
438
+ stream.on("kol:trade", (t) => console.log(t.token_symbol, t.action));
439
+ stream.on("deployer:alert", (a) => console.log("new deploy", a.token_mint));
440
+ stream.subscribe(["kol:trades", "deployer:alerts"]);
441
+ // stream.unsubscribe([...]) / stream.close() when done
442
+ ```
443
+
444
+ Channels: `kol:trades`, `kol:coordination`, `kol:first_touches`, `deployer:alerts`, `wallet_tracker:events`, `copytrade:signals`, `price_alert:events`, `sniper:deploys`, `token:graduations`, `token:prices` (**new** in the channel list β€” event `token:price`, per-mint price/MC ticks; PRO+, REQUIRES `filters.mints` β€” PRO 25 / ULTRA 100 / BUSINESS 250 per connection; a state stream, so ticks carry no id/seq and are never replayed) (every pump.fun graduation in real time, tracked deployer or not β€” typed `GraduationEvent`), `token:locks` (**new 1.27** β€” event `token:lock` for every NEW Streamflow / Jupiter Lock / Bonfida lock or vesting contract, typed `TokenLockStreamEvent`; PRO+; updates are not pushed β€” poll `rest.tokenLocks()`), `token:fee_claims` (**new 1.27** β€” event `token:fee_claim` for every pump.fun fee event: distributions, social-handle claims, config changes, typed `TokenFeeClaimStreamEvent`; PRO+), `token:surges` (**new 1.28** β€” events `token:surge` (a token < 30 min old running β‰₯3Γ— / β‰₯6Γ— / β‰₯8Γ— its launch MC β€” `tier` early / strong / breakout, each once per mint, sustained) and `token:revival` (β‰₯24 h with no trade candle, then confirmed buys on the tape; `tier` null), typed `TokenSurgeStreamEvent` β€” the same row as `rest.tokensSurges()` minus `outcome`, `risk_flags[]` included; subscribe filters `kinds[]`, `tiers[]`, `launchpads[]`, `exclude_flags[]`, `min_mc_usd` / `max_mc_usd`, `deployer_tier[]`; PRO+). Lifecycle events: `open`, `close`, `reconnect`, `subscribed`, `heartbeat`, `warning`, `cursor`, `replay`, `gap`, `fatal`, `error`. Uses the global `WebSocket` on Node 22+; on Node < 22 also `npm i ws`.
445
+
446
+ #### Recovery: cursor, resume, de-duplication *(new in 2.2.0)*
447
+
448
+ The stream client keeps a **resume cursor** `{ instance, seq, ts }` β€” the position of the last frame your handlers finished β€” and on every reconnect asks the server to resume after it (`subscribe { …, resume }`).
449
+
450
+ - **"Processed"** means every handler for that frame returned, or the promise it returned settled. Return a promise from an async handler and the cursor waits for it (and for every earlier frame). A handler that throws or rejects still counts as processed; the error goes to `error`.
451
+ - **At-least-once, never exactly-once.** After a reconnect a frame can arrive again. The client drops ids it delivered recently (the last 10,000, option `dedupeSize`); anything you persist should still dedupe on `evt.id`. Replayed frames carry `evt.replayed === true`.
452
+ - **Persistence.** The cursor lives in memory. Save `stream.getCursor()` (or on every `cursor` event) and pass it back as `{ resume }` to continue after a process restart. Persist the committed cursor, never `getProgress()`.
453
+ - **Committed cursor vs progress.** `getCursor()` is the COMMITTED, safe cursor β€” persist and resume from this one. `getProgress()` is what has been received and handled (replayed frames included) and is not safe to resume from. Live frames commit as they are handled. Replayed frames never commit: the server replays channel by channel, so only a `replay_end` the server calls complete β€” or one whose gaps are all final β€” commits, at the server's `last_seq` / `last_ts`. If a recovery is incomplete, or the socket closes mid-replay, the committed cursor stays at the pre-resume point, and live frames after it are delivered but not committed until a later recovery completes (`isRecoveryIncomplete()`). **Trade-off:** the next reconnect re-requests the unrecovered range from the old cursor, and what arrives twice is dropped by id. Call `acceptGap()` once you have backfilled the range the `gap` event named, or decided to skip it. A gap the server calls final is handled by `onUnrecoverableGap` (below).
454
+ - **Gaps: what is known, and who decides.** A `gap` event says which channels the server could not rebuild, the **range that may be incomplete** (`skipped.from` β†’ `skipped.to`, plus `from`), the server's `reason`, whether it is `permanent`, and the bounds the server reported (`limits`, and per-channel `time_basis` / `truncated_at_ts` / `retry_after_ms` under `channels`). Events in that range **may** be missing β€” the number cannot be known, so it is never stated. Backfill the range from REST if you need certainty.
455
+ - **Transient** (`retryable: true` β€” `backpressure`, `closed`, `source_busy`, `source_error`, `late_ingest_possible`, `row_cap`): the committed cursor stays put, live frames do not commit, and the client resumes again after the server's `retryAfterMs` (for `row_cap`, from `resumeTsHint`), then on every reconnect. `resumeTsHint` is used only when every incomplete **retryable** channel is `row_cap` (a channel whose gap is final does not block it, and is still reported); otherwise the retry asks from the committed cursor again. The client asks again after the server’s `retryAfterMs` at most `maxResumeRetries` times per connection (default 5; the budget resets on every reconnect); when that budget is spent the gap event says `exhausted: true`, the cursor stays where it is, and the next reconnect resumes again.
456
+ - **Final** (`retryable: false` β€” `not_reconstructable`, `window_exceeded`, and an older server's `ring_truncated` / `instance_changed`): asking again can never fill it. **The SDK then decides to continue** β€” that is the client's decision, not your approval β€” and reports it on the same `gap` event with `advancedPastGap: true`, `source: "auto"` and the range being skipped, *before* the cursor moves. Set `onUnrecoverableGap: "stop"` to keep the cursor instead: the stream stops and emits `fatal` with the gap, and you decide (`acceptGap()` then `connect()` continues; `acceptGap()` reports the same gap with `source: "manual"`).
457
+ - **Older servers.** Against a server that does not understand `resume` yet, the client falls back to `replay_since_seq` (same server process) or `replay_since_ts` (the server restarted). That only covers the server's in-memory buffer (minutes), and a restart is reported as a `gap` with `instance_changed`.
458
+ - **Close codes.** `4001` β†’ the token is re-fetched and the client reconnects (`maxAuthRetries`, default 3, then `fatal`); `4002` connection limit β†’ `error` plus a wait of at least 60 s (`connectionLimitBackoffMs`) β€” free a ghost slot with the stream-sessions API; `4003` β†’ `fatal`, the client stops; `4008` slow consumer β†’ reconnect and resume. The backoff resets only when the server acks a subscribe, never on a bare socket open.
459
+ - **Warnings.** `warning` fires for every server warning frame, including `channels_rejected` and `channels_revoked` (revoked channels are removed from the subscription so reconnects do not re-request them) (a channel dropped after a plan change). A rejected or revoked channel is silent, so handle it.
460
+
461
+ ```ts
462
+ const stream = rest.stream({ resume: loadCursor() ?? undefined });
463
+ stream.on("*", async (data, evt) => {
464
+ await store.upsert(evt!.id, data); // the cursor advances once this resolves
465
+ });
466
+ stream.on("cursor", (c) => saveCursor(c)); // { instance, seq, ts }
467
+ stream.on("gap", (g) => console.warn("may be missing:", g.reasons, g.skipped)); // g.advancedPastGap: the SDK continued past it
468
+ stream.on("fatal", (f) => console.error("stream stopped:", f.code, f.reason));
469
+ ```
470
+
471
+ #### Named subscriptions *(new in 2.3.0)*
472
+
473
+ One socket can hold several independent subscriptions, each with its own channels and filters; the server caps the total per connection, the default one included (PRO 5, ULTRA 10, BUSINESS 20). `subscribe(channels, filters)` stays the connection's `"default"` subscription and its wire is unchanged; `subscribe({ subId, channels, filters })` opens a named one (`subId`: 1-64 characters of `A-Z a-z 0-9 _ . -`). A frame delivered under a named subscription carries `evt.sub_id`. **An event that matches several subscriptions is delivered once per matching subscription**, each copy stamped with its `sub_id`: the client dedupes per `(sub_id, id)`, so the same event can legitimately reach a handler twice, under two sub_ids. Filters of one subscription never affect another. `updateSubscription(subId, filters)` REPLACES that subscription's filters (`"default"` addresses the plain one), `unsubscribe(subId)` removes it, `getSubscriptions()` is the local view and `listSubscriptions()` asks the server (`list` / `subscriptions`). Server refusals arrive as `warning` frames carrying the `sub_id` and one of `invalid_sub_id`, `too_many_subscriptions`, `unknown_sub_id`, `invalid_filters`, `channels_rejected`, `channels_revoked`, `replay_in_progress`; a subscription refused as `too_many_subscriptions` or `invalid_sub_id` is dropped locally so reconnects stop re-requesting it. Lifecycle events `updated` and `unsubscribed` surface the server acks.
474
+
475
+ **Resume with several subscriptions** is per subscription: on every reconnect each subscription is re-sent with the same cursor, the server serves one replay per subscription, one after another (`replay_start` … `replay_end` each carry the `sub_id`; live frames are held until the last one ends), and the cursor commits once ALL of them have ended, at the smallest `last_seq` / `last_ts` across them. The `replay` event lists `subscriptions` and the raw `ends` per subscription; a gap's `channels` entries are keyed `sub_id/channel` for named subscriptions, and an incomplete retryable replay is retried for those subscriptions only. Against an older server that ignores `sub_id`, the client emits `warning` `named_subscriptions_unsupported` once.
476
+
477
+ ```ts
478
+ const stream = rest.stream();
479
+ stream.subscribe({ subId: "kol-buys", channels: ["kol:trades"], filters: { action: "buy", min_sol: 1 } });
480
+ stream.subscribe({ subId: "deploys", channels: ["deployer:alerts"], filters: { deployer_tier: ["elite"] } });
481
+ stream.on("kol:trade", (t, evt) => console.log(evt!.sub_id, t)); // "kol-buys"
482
+ stream.updateSubscription("kol-buys", { action: "buy", min_sol: 5 });
483
+ console.log(await stream.listSubscriptions()); // [{ subId, channels, filters }, …]
484
+ stream.unsubscribe("deploys");
485
+ ```
486
+
487
+ ### Live stream sessions *(new in 1.18)*
488
+
489
+ List and force-release the connection slots your key currently holds across both stream services (ws-streaming + dex-stream). Reflects in-memory state, so every listed slot is evictable β€” the self-serve fix when a deploy overlap leaves a ghost socket holding your slot and reconnects hit the 4002 connection limit. PRO/ULTRA only.
490
+
491
+ | Method | Tier | Description |
492
+ |---|---|---|
493
+ | `rest.streamSessions()` | PRO+ | List your live sessions β€” each with `id`, `service`, `tier`, `channels[]`, `connected_at`, `remote_ip`, `messages_sent`. Typed `StreamSessionsResponse` |
494
+ | `rest.streamSessionKill(id)` | PRO+ | Terminate one of your sessions by `id` and free its slot. Throws on a bad id (400) or no matching live session (404). Typed `StreamSessionEvictResponse` |
495
+
496
+ ```ts
497
+ const { sessions } = await rest.streamSessions();
498
+ for (const s of sessions) console.log(s.id, s.service, s.channels, s.messages_sent);
499
+
500
+ // Free a stuck slot after a deploy overlap
501
+ if (sessions.length) await rest.streamSessionKill(sessions[0].id); // { evicted: true, id }
502
+ ```
503
+
504
+ ## DEX Firehose (Ultra)
505
+
506
+ Connect to `dex_ws_url` and use the multi-subscription protocol β€” up to **10 named subs per connection**, each with its own `sub_id`, server-side filters, and optional replay (up to 500 most recent matching trades) from a server-side buffer holding ~5 minutes of firehose history β€” it backfills trades from before your connection existed. Replayed trades arrive newest-first flagged `"replay": true`, then a `replay_done` frame; sort by `block_time` client-side.
507
+
508
+ ```ts
509
+ import WebSocket from "ws";
510
+
511
+ const { token, dex_ws_url } = await rest.getStreamToken();
512
+ const ws = new WebSocket(`${dex_ws_url}?token=${token}`); // token MUST be in the query string
513
+
514
+ ws.on("open", () => {
515
+ ws.send(JSON.stringify({
516
+ type: "subscribe",
517
+ sub_id: "fresh-pumpfun",
518
+ replay: 50, // up to 500 from ring buffer
519
+ filters: {
520
+ dex: "pumpfun", // pumpfun | pumpamm | pumpswap | raydium | jupiter | orca | meteora | launchlab
521
+ token_age_max_seconds: 300,
522
+ min_sol: 0.5,
523
+ action: "buy",
524
+ },
525
+ }));
526
+ });
527
+
528
+ ws.on("message", (raw) => {
529
+ const msg = JSON.parse(raw.toString());
530
+ if (msg.channel === "dex:trades") {
531
+ // { sub_id, data: { wallet, mint, action, sol_amount, dex, ... }, replay, ts }
532
+ }
533
+ });
534
+ ```
535
+
536
+ **Operations** (all carry `sub_id`): `subscribe`, `update` (replace filters in place), `unsubscribe`, `list`, `ping`. **Filters:** `token_mint(s)` (≀50), `wallet(s)` (≀50), `dex`, `program`, `deployer_tier`, `token_age_max_seconds`, `market_cap_min/max_sol`, `min_sol`, `max_sol`, `action`. At least one targeting filter is required. Inbound rate limit: 5 messages/sec.
537
+
538
+ Full protocol reference: [madeonsol.com/api-docs#streaming](https://madeonsol.com/api-docs#streaming).
539
+
540
+ ## Rate-limit headers
541
+
542
+ Every successful REST response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `X-Request-Id`. The SDK exposes them via `rest.lastRateLimit`:
543
+
544
+ ```ts
545
+ await rest.alphaLeaderboard();
546
+ const { limit, remaining, reset, requestId } = rest.lastRateLimit;
547
+ if (remaining !== null && remaining < 5) {
548
+ console.warn(`Throttle warning β€” ${remaining}/${limit} requests left until ${reset}`);
549
+ }
550
+ ```
551
+
552
+ ## Discovery
553
+
554
+ ```ts
555
+ const info = await client.discovery();
556
+ console.log(info.endpoints); // all endpoints with prices and params
557
+ ```
558
+
559
+ Docs: [madeonsol.com/solana-api](https://madeonsol.com/solana-api)
560
+
561
+ ## Also Available
562
+
563
+ | Platform | Package |
564
+ |---|---|
565
+ | TypeScript SDK | [`madeonsol`](https://www.npmjs.com/package/madeonsol) on npm |
566
+ | Rust SDK | [`madeonsol`](https://crates.io/crates/madeonsol) on crates.io |
567
+ | Python (LangChain, CrewAI) | [`madeonsol-x402`](https://pypi.org/project/madeonsol-x402/) on PyPI |
568
+ | 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/madeonsol/mcp-server-madeonsol) |
569
+ | ElizaOS | [`@madeonsol/plugin-madeonsol`](https://www.npmjs.com/package/@madeonsol/plugin-madeonsol) |
570
+ | Solana Agent Kit | [`solana-agent-kit-plugin-madeonsol`](https://www.npmjs.com/package/solana-agent-kit-plugin-madeonsol) |