robinhood-chain-sdk 0.3.1 → 0.5.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
@@ -7,7 +7,7 @@
7
7
  [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen?style=flat-square)](package.json)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](LICENSE)
9
9
 
10
- > **Robinhood Chain API / SDK — EVM-native on-chain trading intelligence for Robinhood Chain (chain id 4663).** The official, fully-typed, zero-dependency TypeScript client for all 30 endpoints: live KOL trades and coordination, token discovery, batch reads & launch-bundle detection, the Uniswap DEX trade tape, 1-minute OHLC candles, deployer reputation with alerts and trajectories, and smart-money wallet rankings — served from a self-hosted Robinhood Chain node.
10
+ > **Robinhood Chain API / SDK — EVM-native on-chain trading intelligence for Robinhood Chain (chain id 4663).** The official, fully-typed, zero-dependency TypeScript client for all 52 endpoints: live KOL trades and coordination, token discovery, batch reads & launch-bundle detection, the Uniswap DEX trade tape, 1-minute OHLC candles, deployer reputation with alerts and trajectories, smart-money wallet rankings, and four **push rule engines** (copy-trade, price alerts, KOL coordination, first touches) — served from a self-hosted Robinhood Chain node.
11
11
 
12
12
  > ⭐ **[Star on GitHub](https://github.com/madeonsol/robinhood-chain-sdk)** · 📂 **[Examples](./examples/)** · 🌐 **[Robinhood Chain](https://madeonsol.com/robinhood)** · 📚 **[API docs](https://madeonsol.com/api-docs)**
13
13
 
@@ -15,6 +15,14 @@ Robinhood Chain (RHC) is an **Arbitrum Orbit L2, chain id 4663**. This SDK wraps
15
15
 
16
16
  The KOL→EVM mapping is unique to MadeOnSol: each tracked Solana KOL's Robinhood-Chain wallet is recovered by tracing their Solana→EVM bridge deposits (deBridge / Relay / Mayan / Wormhole), then attributed on-chain to the effective trading account (`tx.from`, or the ERC-4337 userOp sender when the trade was bundled). Robinhood Chain coverage is **bundled into every MadeOnSol tier at no extra cost — same `msk_` API key, same base URL** as the Solana product.
17
17
 
18
+ ## New in 0.5.0 — stream fixes
19
+
20
+ No REST changes; everything below is about `client.stream`.
21
+
22
+ - **Channel names corrected.** `StreamChannel` now lists the six real RHC channels — `rhc:kol_trades`, `rhc:dex_trades` (the DEX firehose, ULTRA+), `rhc:copytrade:signals`, `rhc:price_alert:events`, `rhc:kol:coordination`, `rhc:kol:first_touches`. 0.4.0's `rhc:trades` never existed server-side; the server now accepts it as a deprecated alias of `rhc:dex_trades`, and the literal stays in the union marked `@deprecated` so 0.4.0 code keeps compiling.
23
+ - **Event names corrected.** The firehose broadcasts `rhc:dex_trade` — a 0.4.0 `on("rhc:trade", …)` handler never fired, and is now a **compile error** so you find it. `StreamEventName` covers all six channels: `rhc:kol_trade`, `rhc:dex_trade`, `rhc:copytrade:signal`, `rhc:price_alert:dip` / `rhc:price_alert:recovery`, `rhc:kol:coordination`, `rhc:kol:first_touch`.
24
+ - **Server warnings surfaced.** The server answers a bad subscribe (typo'd or tier-gated channel) with a `channels_rejected` warning frame — 0.4.0 silently dropped it, so the stream just looked healthy-but-quiet. It now emits a typed `"warning"` lifecycle event (`StreamWarning`: `code`, `rejected`, `valid_channels`, `message`).
25
+
18
26
  ## Quick start (10 seconds)
19
27
 
20
28
  ```bash
@@ -46,7 +54,7 @@ const client = new RobinhoodClient({
46
54
 
47
55
  ## Every endpoint → SDK method
48
56
 
49
- All 30 Robinhood Chain endpoints live under `https://madeonsol.com/api/v1`. Bearer `msk_` auth on every call.
57
+ All 52 Robinhood Chain operations live under `https://madeonsol.com/api/v1`. Bearer `msk_` auth on every call. Everything is a `GET` except the two batch reads and the four rule engines at the bottom, which are full CRUD.
50
58
 
51
59
  | # | Endpoint | SDK method | Tier |
52
60
  |---|---|---|---|
@@ -63,23 +71,51 @@ All 30 Robinhood Chain endpoints live under `https://madeonsol.com/api/v1`. Bear
63
71
  | 11 | `GET /rhc/tokens/{address}/kol-consensus` | `client.tokens.kolConsensus(address)` | PRO+ |
64
72
  | 12 | `GET /rhc/tokens/{address}/buyer-quality` | `client.tokens.buyerQuality(address)` | BASIC |
65
73
  | 13 | `GET /rhc/tokens/{address}/bundle` | `client.tokens.bundle(address)` | BASIC |
66
- | 14 | `POST /rhc/token/batch` | `client.tokens.batch(addresses)` — max 50 | BASIC |
67
- | 15 | `POST /rhc/tokens/batch/buyer-quality` | `client.tokens.batchBuyerQuality(addresses)` — **max 20** | BASIC |
68
- | 16 | `GET /rhc/deployer-hunter/leaderboard` | `client.deployerHunter.leaderboard(params?)` | BASIC |
69
- | 17 | `GET /rhc/deployer-hunter/best-tokens` | `client.deployerHunter.bestTokens(params?)` | BASIC |
70
- | 18 | `GET /rhc/deployer-hunter/stats` | `client.deployerHunter.stats()` | BASIC |
71
- | 19 | `GET /rhc/deployer-hunter/alerts` | `client.deployerHunter.alerts(params?)` | BASIC |
72
- | 20 | `GET /rhc/deployer-hunter/recent-bonds` | `client.deployerHunter.recentBonds(params?)` | BASIC |
73
- | 21 | `GET /rhc/deployer-hunter/{address}` | `client.deployerHunter.profile(address)` | BASIC |
74
- | 22 | `GET /rhc/deployer-hunter/{address}/trajectory` | `client.deployerHunter.trajectory(address)` | BASIC |
75
- | 23 | `GET /rhc/deployer-hunter/{address}/tokens` | `client.deployerHunter.tokens(address, params?)` | BASIC |
76
- | 24 | `GET /rhc/deployer-hunter/{address}/history` | `client.deployerHunter.history(address, params?)` | PRO+ |
77
- | 25 | `GET /rhc/alpha-wallets` | `client.alphaWallets(params?)` | PRO+ |
74
+ | 14 | `GET /rhc/tokens/{address}/top-traders` | `client.tokens.topTraders(address, params?)` | PRO+ |
75
+ | 15 | `GET /rhc/tokens/{address}/flow` | `client.tokens.flow(address, window?)` | PRO+ |
76
+ | 16 | `GET /rhc/tokens/{address}/peak-history` | `client.tokens.peakHistory(address, params?)` | PRO+ |
77
+ | 17 | `GET /rhc/tokens/{address}/risk` | `client.tokens.risk(address)` | PRO+ |
78
+ | 18 | `GET /rhc/tokens/{address}/holders` | `client.tokens.holders(address, params?)` | PRO+ |
79
+ | 19 | `POST /rhc/token/batch` | `client.tokens.batch(addresses)` — max 50 | BASIC |
80
+ | 20 | `POST /rhc/tokens/batch/buyer-quality` | `client.tokens.batchBuyerQuality(addresses)` — **max 20** | BASIC |
81
+ | 21 | `GET /rhc/deployer-hunter/leaderboard` | `client.deployerHunter.leaderboard(params?)` | BASIC |
82
+ | 22 | `GET /rhc/deployer-hunter/best-tokens` | `client.deployerHunter.bestTokens(params?)` | BASIC |
83
+ | 23 | `GET /rhc/deployer-hunter/stats` | `client.deployerHunter.stats()` | BASIC |
84
+ | 24 | `GET /rhc/deployer-hunter/alerts` | `client.deployerHunter.alerts(params?)` | BASIC |
85
+ | 25 | `GET /rhc/deployer-hunter/recent-bonds` | `client.deployerHunter.recentBonds(params?)` | BASIC |
86
+ | 26 | `GET /rhc/deployer-hunter/{address}` | `client.deployerHunter.profile(address)` | BASIC |
87
+ | 27 | `GET /rhc/deployer-hunter/{address}/trajectory` | `client.deployerHunter.trajectory(address)` | BASIC |
88
+ | 28 | `GET /rhc/deployer-hunter/{address}/tokens` | `client.deployerHunter.tokens(address, params?)` | BASIC |
89
+ | 29 | `GET /rhc/deployer-hunter/{address}/history` | `client.deployerHunter.history(address, params?)` | PRO+ |
90
+ | 30 | `GET /rhc/alpha-wallets` | `client.alphaWallets(params?)` | PRO+ |
91
+ | 31 | `GET /rhc/copytrade/subscriptions` | `client.copyTrade.list()` | PRO+ |
92
+ | 32 | `POST /rhc/copytrade/subscriptions` | `client.copyTrade.create(params)` | PRO+ |
93
+ | 33 | `GET /rhc/copytrade/subscriptions/{id}` | `client.copyTrade.get(id)` | PRO+ |
94
+ | 34 | `PATCH /rhc/copytrade/subscriptions/{id}` | `client.copyTrade.update(id, params)` | PRO+ |
95
+ | 35 | `DELETE /rhc/copytrade/subscriptions/{id}` | `client.copyTrade.delete(id)` | PRO+ |
96
+ | 36 | `GET /rhc/copytrade/signals` | `client.copyTrade.signals(params?)` | PRO+ |
97
+ | 37 | `GET /rhc/price-alerts` | `client.priceAlerts.list()` | PRO+ |
98
+ | 38 | `POST /rhc/price-alerts` | `client.priceAlerts.create(params)` | PRO+ |
99
+ | 39 | `GET /rhc/price-alerts/{id}` | `client.priceAlerts.get(id)` | PRO+ |
100
+ | 40 | `PATCH /rhc/price-alerts/{id}` | `client.priceAlerts.update(id, params)` | PRO+ |
101
+ | 41 | `DELETE /rhc/price-alerts/{id}` | `client.priceAlerts.delete(id)` | PRO+ |
102
+ | 42 | `GET /rhc/price-alerts/events` | `client.priceAlerts.events(params?)` | PRO+ |
103
+ | 43 | `GET /rhc/kol/coordination/alerts` | `client.kol.coordinationAlerts.list()` | PRO+ |
104
+ | 44 | `POST /rhc/kol/coordination/alerts` | `client.kol.coordinationAlerts.create(params)` | PRO+ |
105
+ | 45 | `GET /rhc/kol/coordination/alerts/{id}` | `client.kol.coordinationAlerts.get(id)` | PRO+ |
106
+ | 46 | `PATCH /rhc/kol/coordination/alerts/{id}` | `client.kol.coordinationAlerts.update(id, params)` | PRO+ |
107
+ | 47 | `DELETE /rhc/kol/coordination/alerts/{id}` | `client.kol.coordinationAlerts.delete(id)` | PRO+ |
108
+ | 48 | `GET /rhc/kol/first-touches/subscriptions` | `client.kol.firstTouchSubscriptions.list()` | ULTRA+ |
109
+ | 49 | `POST /rhc/kol/first-touches/subscriptions` | `client.kol.firstTouchSubscriptions.create(params)` | ULTRA+ |
110
+ | 50 | `GET /rhc/kol/first-touches/subscriptions/{id}` | `client.kol.firstTouchSubscriptions.get(id)` | ULTRA+ |
111
+ | 51 | `PATCH /rhc/kol/first-touches/subscriptions/{id}` | `client.kol.firstTouchSubscriptions.update(id, params)` | ULTRA+ |
112
+ | 52 | `DELETE /rhc/kol/first-touches/subscriptions/{id}` | `client.kol.firstTouchSubscriptions.delete(id)` | ULTRA+ |
78
113
  | + | `POST /stream/token` → WebSocket | `client.stream.connect()` | PRO+ |
79
114
 
80
115
  ## What you can build
81
116
 
82
- - **KOL copy-trading on Robinhood Chain** — stream `client.kol.feed()` / the `rhc:kol_trades` channel and mirror verified-KOL buys, EVM-native.
117
+ - **KOL copy-trading on Robinhood Chain** — stream `client.kol.feed()` / the `rhc:kol_trades` channel and mirror verified-KOL buys, EVM-native. Or stop polling entirely: `client.copyTrade.create()` has the server watch the tape and push you a signal.
118
+ - **Push instead of poll** — four rule engines (`client.copyTrade`, `client.priceAlerts`, `client.kol.coordinationAlerts`, `client.kol.firstTouchSubscriptions`) deliver over webhook or WebSocket. **Quotas are per chain** — RHC rules never eat your Solana allowance.
83
119
  - **Consensus scanner** — `client.kol.hotTokens()` surfaces tokens 2+ KOLs are accumulating; `client.kol.coordination()` adds the cohort composition behind it (per-KOL legs, accumulating vs distributing, exit state).
84
120
  - **Discovery bot** — `client.kol.firstTouches()` gives the globally earliest KOL buy per token, filterable to tokens minutes old.
85
121
  - **Launch-bundle / rug gate** — `client.tokens.bundle()` flags a same-block early-buyer bundle and how much of supply it still holds; `client.tokens.buyerQuality()` scores the first-20 cohort 0–100 with a dump-cluster ensemble.
@@ -414,9 +450,99 @@ const { wallets } = await client.alphaWallets({
414
450
  });
415
451
  ```
416
452
 
453
+ ## Rule engines — push, not polling
454
+
455
+ Four server-side rule engines watch the Robinhood Chain tape for you and deliver over **webhook**, **WebSocket**, or both. **Every quota is per chain** — configuring RHC rules never consumes your Solana budget, and a full set of Solana rules leaves your RHC capacity untouched. A `webhook_secret` is returned **exactly once** on create (null when `delivery_mode` is `"websocket"`); payloads are signed HMAC-SHA256 over `` `<timestamp>.<body>` `` in the `X-MadeOnSol-Signature` header.
456
+
457
+ ### Copy-trade — `client.copyTrade` (PRO+)
458
+
459
+ ```ts
460
+ const { subscription, webhook_secret } = await client.copyTrade.create({
461
+ name: "degen desk",
462
+ source_wallets: ["0xaaa…", "0xbbb…", "0xccc…"], // 1–250, per-tier cap enforced server-side
463
+ min_trade_eth: 0.01,
464
+ only_action: "buy", // buy | sell | both
465
+ sizing_mode: "fixed", // fixed | proportional | percent_source
466
+ sizing_amount: 0.05, // ETH when sizing_mode is "fixed"
467
+ delivery_mode: "websocket",
468
+ });
469
+
470
+ await client.copyTrade.update(subscription.id, { is_active: false });
471
+ await client.copyTrade.delete(subscription.id); // fired signals cascade
472
+
473
+ // Catch-up path for a missed webhook / dropped WS — fires retained 7 days
474
+ const since = new Date(Date.now() - 3_600_000).toISOString();
475
+ const { signals } = await client.copyTrade.signals({ subscription_id: subscription.id, since });
476
+ ```
477
+
478
+ Sizes are **ETH, not SOL**, and there is deliberately **no market-cap band** — the RHC trade event carries no market cap, so a band could only be a per-event DB lookup in the hot path of a ~3.3M-trades/day chain. `update()` re-checks the per-tier wallet cap, so a rule cannot be PATCHed past its limit.
479
+
480
+ ### Price alerts — `client.priceAlerts` (PRO+)
481
+
482
+ ```ts
483
+ const { alert, evaluation } = await client.priceAlerts.create({
484
+ token_address: "0xdef…", // must already be tracked on RHC with a market cap
485
+ drop_pct: 30, // 0.01–99.99, measured from the MC captured RIGHT NOW
486
+ recovery_pct: 15, // omit for a dip-only, terminal alert
487
+ webhook_url: "https://example.com/hook",
488
+ });
489
+ console.log(evaluation.mode, evaluation.interval_seconds); // "polled", ~15
490
+
491
+ const { events } = await client.priceAlerts.events({ alert_id: alert.id, event_type: "dip" });
492
+ ```
493
+
494
+ > **RHC price alerts are polled (~15s), not sub-second like the Solana ones.** `rhc_token_prices` is written by the RHC ingester on a separate box and emits no `pg_notify`, so there is nothing to react to — effective latency is that interval plus the token's own price-update cadence. Every create response spells this out in its `evaluation` block. The baseline MC is captured at creation, so an alert is a delta from the moment you set it; alerts self-expire after 30 days, and only `name`, `delivery_mode`, `webhook_url` and `is_active` are mutable (retuning a threshold mid-flight would make the recorded events uninterpretable).
495
+
496
+ ### KOL coordination rules — `client.kol.coordinationAlerts` (PRO+)
497
+
498
+ ```ts
499
+ const { rule, scoring } = await client.kol.coordinationAlerts.create({
500
+ min_kols: 3, // 2–50 distinct tracked KOL buyers
501
+ window_minutes: 15, // 1–60 rolling window
502
+ min_score: 40, // 0–100
503
+ cooldown_min: 30, // 1–1440 before the same token can fire again
504
+ score_jump_break: 20, // score jump that breaks the cooldown early
505
+ delivery_mode: "websocket",
506
+ });
507
+ await client.kol.coordinationAlerts.update(rule.id, { min_kols: 4 }); // UUID id
508
+ ```
509
+
510
+ > **Coordination scoring is comparable to Solana, but not identical.** The shared v1 scorer runs, `quality` is a real KOL win-rate, and `earliness` is **defaulted** — RHC has no early-entry equivalent. The create response's `scoring` block records which components are real, and every fired signal repeats it in `score_inputs`.
511
+
512
+ ### KOL first-touch subscriptions — `client.kol.firstTouchSubscriptions` (ULTRA+)
513
+
514
+ ```ts
515
+ const { subscription } = await client.kol.firstTouchSubscriptions.create({
516
+ name: "early hands",
517
+ filters: {
518
+ min_first_buy_eth: 0.05,
519
+ min_kol_winrate: 0.5, // win-rate on CLOSED positions
520
+ strategy: "swing", // scalper | day_trader | swing | inactive | unscored
521
+ min_mc_usd: 10_000,
522
+ },
523
+ delivery_mode: "websocket",
524
+ });
525
+
526
+ // `filters` is a whole-object REPLACE, not a merge — {} clears every filter
527
+ await client.kol.firstTouchSubscriptions.update(subscription.id, { filters: {} });
528
+ ```
529
+
530
+ > **First-touch filters are not the Solana set.** RHC has no scout score, so `min_scout_tier` and `min_n_touches` do not exist here rather than silently matching nothing; `min_kol_winrate` and `strategy` are the quality gates. Unknown filter keys are rejected with a **400**, not ignored.
531
+
417
532
  ## Streaming — `client.stream` (PRO+)
418
533
 
419
- Managed WebSocket with token fetch + 24h refresh, auto-reconnect with backoff, heartbeat liveness, and typed events. Channels: **`rhc:kol_trades`** and **`rhc:trades`**.
534
+ Managed WebSocket with token fetch + 24h refresh, auto-reconnect with backoff, heartbeat liveness, and typed events. Six RHC channels:
535
+
536
+ | Channel | Emits | Tier | Scope |
537
+ |---|---|---|---|
538
+ | `rhc:kol_trades` | `rhc:kol_trade` | PRO+ | broadcast — the live KOL tape |
539
+ | `rhc:dex_trades` | `rhc:dex_trade` | **ULTRA+** | broadcast — the full DEX firehose |
540
+ | `rhc:copytrade:signals` | `rhc:copytrade:signal` | PRO+ | user-scoped — only **your** rules' fires |
541
+ | `rhc:price_alert:events` | `rhc:price_alert:dip`, `rhc:price_alert:recovery` | PRO+ | user-scoped; ~15s polled, not sub-second |
542
+ | `rhc:kol:coordination` | `rhc:kol:coordination` | PRO+ | user-scoped — only **your** rules' fires |
543
+ | `rhc:kol:first_touches` | `rhc:kol:first_touch` | PRO+ | broadcast — ULTRA gates only the first-touch *subscription CRUD*, not this channel |
544
+
545
+ > **Deprecated:** `rhc:trades` was never a real channel — 0.4.0 subscribers got a `channels_rejected` warning and silence. The server now accepts it as an alias of `rhc:dex_trades` (and acks it under the canonical name), and the SDK keeps the literal marked `@deprecated` so 0.4.0 code compiles. Use `rhc:dex_trades`.
420
546
 
421
547
  ```ts
422
548
  const stream = client.stream.connect();
@@ -424,10 +550,13 @@ const stream = client.stream.connect();
424
550
  stream
425
551
  .on("open", () => console.log("connected"))
426
552
  .on("rhc:kol_trade", (trade) => console.log("KOL trade", trade))
427
- .on("rhc:trade", (trade) => console.log("DEX trade", trade))
553
+ .on("rhc:dex_trade", (trade) => console.log("DEX trade", trade))
554
+ // New in 0.5.0 — the server tells you when a channel was refused (typo or
555
+ // tier gate); 0.4.0 dropped this frame and the stream just stayed silent.
556
+ .on("warning", (w) => console.warn("rejected:", w.code, w.rejected, w.valid_channels))
428
557
  .on("error", (err) => console.error(err));
429
558
 
430
- stream.subscribe(["rhc:kol_trades", "rhc:trades"]);
559
+ stream.subscribe(["rhc:kol_trades", "rhc:dex_trades"]);
431
560
  // …later
432
561
  stream.close(); // clean shutdown — short-lived scripts exit promptly
433
562
  ```
@@ -453,7 +582,7 @@ try {
453
582
 
454
583
  ## Types & constants
455
584
 
456
- Fully-typed responses and params for all 30 endpoints are exported (`RhcKolFeedResponse`, `RhcKolCoordinationResponse`, `RhcKolFirstTouchesResponse`, `RhcTradesResponse`, `RhcTokenSnapshot`, `RhcTokenBatchResponse`, `RhcBatchBuyerQualityResponse`, `RhcBundleResponse`, `RhcDeployerTrajectoryResponse`, `RhcDeployerTokensResponse`, `RhcDeployerHistoryResponse`, `RhcBestTokensResponse`, `RhcDeployerStatsResponse`, `RhcDeployerAlertsResponse`, `RhcRecentBondsResponse`, `RhcAlphaWalletsResponse`, …), plus shared types (`DeployerTier`, `TradeAction`, `UniswapVersion`, `RhcBundleKind`, `RhcAlertType`, `RhcAlertPriority`, `RhcCoordinationSignal`) and the `CHAIN_ID` constant (`4663`).
585
+ Fully-typed responses and params for all 52 endpoints are exported (`RhcKolFeedResponse`, `RhcKolCoordinationResponse`, `RhcKolFirstTouchesResponse`, `RhcTradesResponse`, `RhcTokenSnapshot`, `RhcTokenBatchResponse`, `RhcBatchBuyerQualityResponse`, `RhcBundleResponse`, `RhcTopTradersResponse`, `RhcFlowResponse`, `RhcPeakHistoryResponse`, `RhcRiskResponse`, `RhcHoldersResponse`, `RhcDeployerTrajectoryResponse`, `RhcDeployerTokensResponse`, `RhcDeployerHistoryResponse`, `RhcBestTokensResponse`, `RhcDeployerStatsResponse`, `RhcDeployerAlertsResponse`, `RhcRecentBondsResponse`, `RhcAlphaWalletsResponse`, plus the rule engines: `RhcCopyTradeSubscription`, `RhcCopyTradeCreateParams`, `RhcCopyTradeSignal`, `RhcPriceAlert`, `RhcPriceAlertEvaluation`, `RhcPriceAlertEvent`, `RhcCoordinationAlertRule`, `RhcCoordinationAlertScoring`, `RhcFirstTouchSubscription`, `RhcFirstTouchFilters`, `RhcDeletedResponse`, …), plus shared types (`DeployerTier`, `TradeAction`, `UniswapVersion`, `DeliveryMode`, `RhcBundleKind`, `RhcAlertType`, `RhcAlertPriority`, `RhcCoordinationSignal`) and the `CHAIN_ID` constant (`4663`).
457
586
 
458
587
  ## Links
459
588