robinhood-chain-sdk 0.9.0 → 0.11.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 +35 -3
- package/dist/index.d.ts +252 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/stream.d.ts +130 -12
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +366 -54
- package/dist/stream.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,10 @@ 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.10.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.
|
|
19
|
+
|
|
20
|
+
> **Also in 0.10.0 — `rhc:token_prices` and enriched RHC trade payloads (WS Phase 2).** A tenth RHC channel, `rhc:token_prices` (PRO+, address-scoped): subscribe with `filters.addresses` (25 / 100 / 250 per connection on PRO / ULTRA / BUSINESS, rejected above the cap) and receive one `snapshot: true` frame per address, then ticks derived from the RHC trade feed at most once per address per 250 ms, each typed `RhcTokenPriceTick` with `quality` fresh | stale | unreliable and a `quality_reason`; a stale or unreliable price is never delivered as fresh. `rhc:dex_trade` / `rhc:dex_trade_unattributed` frames now carry additive enrichment, typed `RhcDexTradeEvent` / `RhcDexTradeUnattributedEvent`: exact `amount_in_raw` / `amount_out_raw` decimal strings (never floats), `token` + `quote` identity with decimals, `metadata_status`, `price_status` / `price_source` / `price_observed_at`, `mc_status` (why `mc_usd` is null) and `side` / `side_reason`. Every existing field keeps its name.
|
|
21
|
+
|
|
18
22
|
> **New in 0.9.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`). `StreamChannel` / `STREAM_CHANNELS` now list all nine RHC channels (adds `rhc:dex_trades_unattributed`, `rhc:new_tokens`, `rhc:token_locks`); `RhcPriceAlertEvaluation.mode` is `"event_driven" | "polled"` with the new optional `trigger` / `fallback_poll_seconds`. See the stream section's "Recovery" notes.
|
|
19
23
|
|
|
20
24
|
> **New in 0.8.2 — mutation calls are no longer retried automatically (security fix, SDK-02).** A lost response or transient network error after a `POST`/`PATCH`/`DELETE` (rule create, watchlist change, `stream.rotate()`) used to retry automatically — which could duplicate a rule or rotate a token twice. Mutating calls (including the batch-read POST endpoints) now make exactly one attempt; `GET` retries/backoff (`maxRetries`) are unchanged. If a mutating call fails, check current state before deciding whether to retry by hand. No public API/type changes.
|
|
@@ -247,9 +251,11 @@ const { events, has_more, next_before, coverage } = await client.lpEvents({ toke
|
|
|
247
251
|
for (const ev of events) {
|
|
248
252
|
if (ev.provider_is_token_deployer) console.warn("deployer pulled liquidity:", ev.tx_hash, ev.dex, ev.token_amount_raw);
|
|
249
253
|
}
|
|
250
|
-
console.log(coverage.
|
|
254
|
+
console.log(coverage.events); // ["remove"] — the default request is removals only
|
|
251
255
|
```
|
|
252
256
|
|
|
257
|
+
Since 2026-09-23 `action: "add" | "pool_created" | "all"` opts into liquidity adds (kept 7 days) and pool creations, and every row carries the depth fields `tick_lower` / `tick_upper`, `liquidity_delta`, `in_range`, `active_liquidity_delta`, `active_share` (v3/v4), `share_of_reserves` (v2) and `material` — null where the pool state was unknown and on older rows.
|
|
258
|
+
|
|
253
259
|
> **Removals ONLY.** Liquidity adds are not persisted (v4 adds share the topic and are dropped at decode; v2/v3 `Mint` is not subscribed), so every row is `event: "remove"` and an empty page means "no removals seen", never "no liquidity activity" — the `coverage` block spells this out. Amounts are **raw uint256 decimal strings** (`liquidity`, `amount0` / `amount1`, plus pre-resolved `token_amount_raw` / `quote_token` / `quote_amount_raw`) — do not `Number()` them; v4 rows carry `liquidity` only because the pool manager emits no token amounts. Filters: `token`, `pool` (v2/v3 address or v4 bytes32 poolId), `provider`, `dex`. Data since 2026-08-05.
|
|
254
260
|
|
|
255
261
|
## Token intelligence — `client.tokens`
|
|
@@ -579,7 +585,7 @@ await client.kol.firstTouchSubscriptions.update(subscription.id, { filters: {} }
|
|
|
579
585
|
|
|
580
586
|
## Streaming — `client.stream` (PRO+)
|
|
581
587
|
|
|
582
|
-
Managed WebSocket with token fetch on every (re)connect, auto-reconnect with backoff, heartbeat liveness, and typed events. Stream tokens **never expire** (since 2026-08-27) — there is no refresh timer; `client.stream.getToken()` returns the same token every call (`expires_at` / `next_refresh_at` are always `null`), and `getToken({ rotate: true })` replaces it (the old one keeps working for 60 s).
|
|
588
|
+
Managed WebSocket with token fetch on every (re)connect, auto-reconnect with backoff, heartbeat liveness, and typed events. Stream tokens **never expire** (since 2026-08-27) — there is no refresh timer; `client.stream.getToken()` returns the same token every call (`expires_at` / `next_refresh_at` are always `null`), and `getToken({ rotate: true })` replaces it (the old one keeps working for 60 s). Eleven RHC channels:
|
|
583
589
|
|
|
584
590
|
| Channel | Emits | Tier | Scope |
|
|
585
591
|
|---|---|---|---|
|
|
@@ -591,7 +597,17 @@ Managed WebSocket with token fetch on every (re)connect, auto-reconnect with bac
|
|
|
591
597
|
| `rhc:price_alert:events` | `rhc:price_alert:dip`, `rhc:price_alert:recovery` | PRO+ | user-scoped; event-driven off each trade (a few seconds), not sub-second |
|
|
592
598
|
| `rhc:kol:coordination` | `rhc:kol:coordination` | PRO+ | user-scoped — only **your** rules' fires |
|
|
593
599
|
| `rhc:kol:first_touches` | `rhc:kol:first_touch` | PRO+ | broadcast — ULTRA gates only the first-touch *subscription CRUD*, not this channel |
|
|
594
|
-
| `rhc:token_locks` | `rhc:token_lock` | PRO+ | broadcast — a token lock / vesting contract created on chain |
|
|
600
|
+
| `rhc:token_locks` | `rhc:token_lock` (+ `rhc:token_unlock_upcoming`, `rhc:token_unlock_available` with `filters.lifecycle: true`) | PRO+ | broadcast — a token lock / vesting contract created on chain; opt-in unlock-schedule events (claims / cancels are not observable on RHC) |
|
|
601
|
+
| `rhc:token_prices` | `rhc:token_price` | PRO+ | **address-scoped** — `filters.addresses` required (25 / 100 / 250 per connection); one `snapshot: true` frame per address, then ≤ 1 tick per address per 250 ms with `quality` fresh / stale / unreliable + reason (`RhcTokenPriceTick`); no `seq` / `id` |
|
|
602
|
+
| `rhc:lp_events` | `rhc:lp_event` | **ULTRA+** | broadcast — liquidity `add` / `remove` / `pool_created` on tracked Uniswap v2/v3/v4 pools with `in_range`, `active_share`, `share_of_reserves`, `material` (`RhcLpStreamEvent`); filters `RhcLpEventsFilters`; durable resume |
|
|
603
|
+
|
|
604
|
+
**Liquidity events and lock schedule (server 2026-09-23).** `rhc:lp_events` frames carry raw `amount0` / `amount1` strings (null on v4), the position range and, for v3/v4, `in_range` / `active_liquidity_delta` / `active_share` — a share of liquidity at the current price, not of TVL (an out-of-range removal is `active_share: 0`); v2 carries `share_of_reserves`; `material: true` = a removal of ≥ 25 %. When the pool's tick was not known the active fields are `null` with `active_share_reason: "pool_state_unknown"` — never guessed. `provider` is usually the router / position manager. No USD field. On `rhc:token_locks`, `filters.lifecycle: true` adds `rhc:token_unlock_upcoming` (within 24 h) and `rhc:token_unlock_available` (passed within 30 min — **claimable per the schedule, not claimed**; `withdrawals_tracked: false`).
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
const lp = client.stream.connect();
|
|
608
|
+
lp.subscribe({ subId: "rugs", channels: ["rhc:lp_events"], filters: { actions: ["remove"], material_only: true } });
|
|
609
|
+
lp.on("rhc:lp_event", (e: RhcLpStreamEvent) => console.log(e.dex, e.pool, e.action, e.active_share ?? e.share_of_reserves));
|
|
610
|
+
```
|
|
595
611
|
|
|
596
612
|
> **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`.
|
|
597
613
|
|
|
@@ -639,6 +655,22 @@ stream.on("gap", (g) => console.warn("may be missing:", g.reasons, g.skipped));
|
|
|
639
655
|
stream.on("fatal", (f) => console.error("stream stopped:", f.code, f.reason));
|
|
640
656
|
```
|
|
641
657
|
|
|
658
|
+
### Named subscriptions *(new in 0.10.0)*
|
|
659
|
+
|
|
660
|
+
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.
|
|
661
|
+
|
|
662
|
+
**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.
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
const stream = client.stream.connect();
|
|
666
|
+
stream.subscribe({ subId: "kol-buys", channels: ["rhc:kol_trades"], filters: { action: "buy" } });
|
|
667
|
+
stream.subscribe({ subId: "firehose", channels: ["rhc:dex_trades"], filters: {} });
|
|
668
|
+
stream.on("rhc:kol_trade", (t, evt) => console.log(evt!.sub_id, t)); // "kol-buys"
|
|
669
|
+
stream.updateSubscription("kol-buys", { action: "buy" });
|
|
670
|
+
console.log(await stream.listSubscriptions()); // [{ subId, channels, filters }, …]
|
|
671
|
+
stream.unsubscribe("firehose");
|
|
672
|
+
```
|
|
673
|
+
|
|
642
674
|
## Error handling
|
|
643
675
|
|
|
644
676
|
Every method throws `RobinhoodError` on a non-2xx response, with `.status`, `.body`, `.message`, and `.requestId` (the API's `_rid` — include it when reporting issues). GET requests retry network failures, rate-limits (`429`) and transient server errors (`5xx`) up to `maxRetries`, with exponential backoff and `Retry-After` / `X-RateLimit-Reset` hints.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RobinhoodStream } from "./stream.js";
|
|
2
2
|
import type { StreamClientOptions } from "./stream.js";
|
|
3
3
|
export { RobinhoodStream, STREAM_CHANNELS } from "./stream.js";
|
|
4
|
-
export type { StreamClientOptions, StreamChannel, StreamEventName, StreamEvent, StreamLifecycleEvent, StreamWarning, StreamCursor, StreamReplayResult, StreamGap, StreamFatal, StreamTokenLike, } from "./stream.js";
|
|
4
|
+
export type { StreamClientOptions, StreamChannel, StreamEventName, StreamEvent, StreamLifecycleEvent, StreamWarning, StreamCursor, StreamReplayResult, StreamGap, StreamFatal, StreamSubscription, StreamSubscribeOptions, StreamTokenLike, } from "./stream.js";
|
|
5
5
|
/** Robinhood Chain — Arbitrum Orbit L2, chain id 4663. */
|
|
6
6
|
export declare const CHAIN_ID = 4663;
|
|
7
7
|
/** Thrown for any non-2xx API response (and exhausted network retries). */
|
|
@@ -369,17 +369,26 @@ export interface RhcLpEventsParams {
|
|
|
369
369
|
provider?: string;
|
|
370
370
|
/** Filter by DEX version. */
|
|
371
371
|
dex?: UniswapVersion;
|
|
372
|
+
/**
|
|
373
|
+
* Which actions to return (2026-09-23). Default `"remove"` = the historical
|
|
374
|
+
* removals-only feed, unchanged. `"add"` (kept 7 days), `"pool_created"` or
|
|
375
|
+
* `"all"` opt into rows persisted since the WS Phase 3 producer.
|
|
376
|
+
*/
|
|
377
|
+
action?: RhcLpAction | "all";
|
|
372
378
|
/** Opaque cursor from `next_before` (same (block_time,id) keyset as /rhc/trades). */
|
|
373
379
|
before?: string;
|
|
374
380
|
}
|
|
381
|
+
/** A liquidity action on Robinhood Chain. */
|
|
382
|
+
export type RhcLpAction = "add" | "remove" | "pool_created";
|
|
375
383
|
/**
|
|
376
|
-
* One liquidity
|
|
377
|
-
*
|
|
378
|
-
* STRINGS; v4 rows carry `liquidity` only
|
|
379
|
-
* amounts), so `amount0` / `amount1` /
|
|
384
|
+
* One liquidity event from `/rhc/lp-events`. Without `action` every row is
|
|
385
|
+
* `event: "remove"` (the default feed is removals only). Amounts are raw
|
|
386
|
+
* on-chain uint256 integers as decimal STRINGS; v4 rows carry `liquidity` only
|
|
387
|
+
* (the pool manager emits no token amounts), so `amount0` / `amount1` /
|
|
388
|
+
* `token_amount_raw` are null there.
|
|
380
389
|
*/
|
|
381
390
|
export interface RhcLpEvent {
|
|
382
|
-
event:
|
|
391
|
+
event: RhcLpAction;
|
|
383
392
|
pool: string;
|
|
384
393
|
dex: UniswapVersion;
|
|
385
394
|
fee_tier: number | null;
|
|
@@ -411,6 +420,116 @@ export interface RhcLpEvent {
|
|
|
411
420
|
block_time: string;
|
|
412
421
|
tx_hash: string;
|
|
413
422
|
log_index: number;
|
|
423
|
+
tick_lower?: number | null;
|
|
424
|
+
tick_upper?: number | null;
|
|
425
|
+
/** Signed liquidity change (v3/v4), int256 as string. */
|
|
426
|
+
liquidity_delta?: string | null;
|
|
427
|
+
/** tick_lower <= current tick < tick_upper just before the event; null = tick unknown. */
|
|
428
|
+
in_range?: boolean | null;
|
|
429
|
+
/** liquidity_delta when in range, "0" out of range. */
|
|
430
|
+
active_liquidity_delta?: string | null;
|
|
431
|
+
/** |active delta| / active liquidity before (v3/v4); 0 out of range; adds can exceed 1. */
|
|
432
|
+
active_share?: number | null;
|
|
433
|
+
/** v2 only: amount / reserve before (from the pair's Sync). */
|
|
434
|
+
share_of_reserves?: number | null;
|
|
435
|
+
/** true = a removal of ≥ 25 % of reserves / active liquidity; null for adds, creations and unknown shares. */
|
|
436
|
+
material?: boolean | null;
|
|
437
|
+
}
|
|
438
|
+
/** Why `active_share` is null / not the relevant share. */
|
|
439
|
+
export type RhcLpActiveShareReason = "pool_created" | "not_concentrated" | "pool_state_unknown" | "no_active_liquidity";
|
|
440
|
+
/**
|
|
441
|
+
* `rhc:lp_event` frame `data` (WS channel `rhc:lp_events`, ULTRA / BUSINESS,
|
|
442
|
+
* 2026-09-23). Frame id = `rhc:lp_event:<tx_hash>:<log_index>`. Every key is
|
|
443
|
+
* always present (null when unknown). `in_range` / `active_*` are null with
|
|
444
|
+
* `active_share_reason: "pool_state_unknown"` when the pool's tick was not
|
|
445
|
+
* known — never guessed. `provider` is usually a router / position manager,
|
|
446
|
+
* not the beneficial owner. No USD field.
|
|
447
|
+
*/
|
|
448
|
+
export interface RhcLpStreamEvent {
|
|
449
|
+
chain: Chain;
|
|
450
|
+
action: RhcLpAction;
|
|
451
|
+
dex: UniswapVersion;
|
|
452
|
+
pool: string;
|
|
453
|
+
token_address: string | null;
|
|
454
|
+
token: {
|
|
455
|
+
address: string | null;
|
|
456
|
+
symbol: string | null;
|
|
457
|
+
decimals: number | null;
|
|
458
|
+
};
|
|
459
|
+
token0: string | null;
|
|
460
|
+
token1: string | null;
|
|
461
|
+
provider: string | null;
|
|
462
|
+
liquidity: string | null;
|
|
463
|
+
/** NULL on v4 — ModifyLiquidity reports no token amounts. */
|
|
464
|
+
amount0: string | null;
|
|
465
|
+
amount1: string | null;
|
|
466
|
+
tick_lower: number | null;
|
|
467
|
+
tick_upper: number | null;
|
|
468
|
+
liquidity_delta: string | null;
|
|
469
|
+
in_range: boolean | null;
|
|
470
|
+
active_liquidity_delta: string | null;
|
|
471
|
+
active_share: number | null;
|
|
472
|
+
active_share_reason: RhcLpActiveShareReason | null;
|
|
473
|
+
share_of_reserves: number | null;
|
|
474
|
+
material: boolean | null;
|
|
475
|
+
block_number: number | null;
|
|
476
|
+
block_time: string | null;
|
|
477
|
+
tx_hash: string;
|
|
478
|
+
log_index: number | null;
|
|
479
|
+
}
|
|
480
|
+
/** Per-subscription filters for `rhc:lp_events` (all optional, AND; invalid values reject the channel). */
|
|
481
|
+
export interface RhcLpEventsFilters {
|
|
482
|
+
addresses?: string[];
|
|
483
|
+
pools?: string[];
|
|
484
|
+
dexes?: UniswapVersion[];
|
|
485
|
+
actions?: RhcLpAction[];
|
|
486
|
+
material_only?: boolean;
|
|
487
|
+
/** 0..1000 — only events whose relevant share (reserves on v2, active on v3/v4) is known and ≥ this. */
|
|
488
|
+
min_share?: number;
|
|
489
|
+
}
|
|
490
|
+
/** Unlock-schedule event names on `rhc:token_locks` (only with `filters.lifecycle: true`). */
|
|
491
|
+
export type RhcTokenLockLifecycleEventName = "rhc:token_unlock_upcoming" | "rhc:token_unlock_available";
|
|
492
|
+
/**
|
|
493
|
+
* Filters that switch `rhc:token_locks` to lifecycle mode. Claims, extensions
|
|
494
|
+
* and cancels are NOT observable on Robinhood Chain — only the unlock schedule.
|
|
495
|
+
*/
|
|
496
|
+
export interface RhcTokenLockLifecycleFilters {
|
|
497
|
+
lifecycle: true;
|
|
498
|
+
events?: RhcTokenLockLifecycleEventName[];
|
|
499
|
+
unlock_kinds?: Array<"cliff" | "final" | "tranche">;
|
|
500
|
+
/** Token scope for creates AND lifecycle (≤ 500); applied only with `lifecycle: true`. */
|
|
501
|
+
addresses?: string[];
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* `rhc:token_unlock_upcoming` (the lock's NEXT unlock, when it is within 24 h) /
|
|
505
|
+
* `rhc:token_unlock_available` (passed within the last 30 min — claimable PER
|
|
506
|
+
* THE SCHEDULE, NOT claimed). Frame id = `<event>:<event_key>`.
|
|
507
|
+
*/
|
|
508
|
+
export interface RhcTokenUnlockScheduleEvent {
|
|
509
|
+
event_key: string;
|
|
510
|
+
lock_id: string;
|
|
511
|
+
token_address: string;
|
|
512
|
+
family: string;
|
|
513
|
+
observed_at: string | null;
|
|
514
|
+
unlock_at: string;
|
|
515
|
+
unlock_kind: "cliff" | "final" | "tranche";
|
|
516
|
+
amount_raw: string | null;
|
|
517
|
+
amount_reason: string | null;
|
|
518
|
+
unlocked_total_raw: string | null;
|
|
519
|
+
release_model: "at_end" | "linear" | "tranched";
|
|
520
|
+
claimable?: true;
|
|
521
|
+
chain: Chain;
|
|
522
|
+
locker: string | null;
|
|
523
|
+
kind: string | null;
|
|
524
|
+
subject: string | null;
|
|
525
|
+
lp_kind: string | null;
|
|
526
|
+
lp_pool: string | null;
|
|
527
|
+
sender: string | null;
|
|
528
|
+
recipient: string | null;
|
|
529
|
+
locked_amount_raw: string | null;
|
|
530
|
+
amount_unit: string | null;
|
|
531
|
+
decimals: number | null;
|
|
532
|
+
withdrawals_tracked: false;
|
|
414
533
|
}
|
|
415
534
|
export interface RhcLpEventsResponse {
|
|
416
535
|
chain: Chain;
|
|
@@ -419,10 +538,11 @@ export interface RhcLpEventsResponse {
|
|
|
419
538
|
has_more: boolean;
|
|
420
539
|
/** Opaque pagination cursor; pass back as `before`. */
|
|
421
540
|
next_before: string | null;
|
|
422
|
-
/** Honesty block:
|
|
541
|
+
/** Honesty block: the actions this response covers (["remove"] by default), adds_persisted, note, since. */
|
|
423
542
|
coverage: {
|
|
424
543
|
events: string[];
|
|
425
|
-
adds_persisted: boolean;
|
|
544
|
+
adds_persisted: boolean | null;
|
|
545
|
+
adds_retention_days?: number;
|
|
426
546
|
note: string;
|
|
427
547
|
since: string;
|
|
428
548
|
};
|
|
@@ -1995,6 +2115,130 @@ export interface RhcPriceAlertEvent {
|
|
|
1995
2115
|
delivered: boolean;
|
|
1996
2116
|
delivered_at: string | null;
|
|
1997
2117
|
}
|
|
2118
|
+
/** One side of a swap as the server resolved it. `decimals` null = not yet enriched. */
|
|
2119
|
+
export interface RhcSideIdentity {
|
|
2120
|
+
address: string | null;
|
|
2121
|
+
symbol: string | null;
|
|
2122
|
+
decimals: number | null;
|
|
2123
|
+
}
|
|
2124
|
+
/** known: decimals AND symbol resolved · pending: token seen, enrichment queued · unknown: never seen. */
|
|
2125
|
+
export type RhcMetadataStatus = "known" | "pending" | "unknown";
|
|
2126
|
+
/** fresh: swap-derived quote inside the producer's 60-min freshness rule · stale: priced through the v4 pool-state fallback · none: unpriced. */
|
|
2127
|
+
export type RhcPriceStatus = "fresh" | "stale" | "none";
|
|
2128
|
+
export type RhcPriceSource = "swap_quote" | "pool_state_quote";
|
|
2129
|
+
/** Why `mc_usd` is null (or "ok"). `liquidity_gate` = MC exceeded pool TVL x 50,000, the same gate that nulls the stored MC. */
|
|
2130
|
+
export type RhcMcStatus = "ok" | "no_price" | "no_supply" | "dust" | "ceiling" | "liquidity_gate" | "no_liquidity";
|
|
2131
|
+
export type RhcSideReason = "both_sides_quote" | "no_recognized_quote";
|
|
2132
|
+
/**
|
|
2133
|
+
* `rhc:dex_trade` frame `data` (channel `rhc:dex_trades`, ULTRA+). Every field
|
|
2134
|
+
* that existed before 2026-09-22 keeps its name; the block from
|
|
2135
|
+
* `amount_in_raw` down is the additive enrichment. `*_raw` amounts are uint256
|
|
2136
|
+
* as EXACT decimal strings — never parse them into a float. A KOL trade also
|
|
2137
|
+
* appears on `rhc:kol_trades` with the same `tx_hash` + `log_index` (different
|
|
2138
|
+
* event id). The durable backfill rebuilds every key it can and lists the rest
|
|
2139
|
+
* in the frame's `missing` (liquidity_usd, quote.usd, quote.observed_at).
|
|
2140
|
+
*/
|
|
2141
|
+
export interface RhcDexTradeEvent {
|
|
2142
|
+
chain: "robinhood";
|
|
2143
|
+
token_address: string;
|
|
2144
|
+
token_symbol: string | null;
|
|
2145
|
+
action: "buy" | "sell";
|
|
2146
|
+
dex: string;
|
|
2147
|
+
pool: string;
|
|
2148
|
+
eth_amount: number | null;
|
|
2149
|
+
price_usd: number | null;
|
|
2150
|
+
mc_usd: number | null;
|
|
2151
|
+
/** Pool TVL at broadcast (null = not yet tracked, never "zero"). */
|
|
2152
|
+
liquidity_usd: number | null;
|
|
2153
|
+
trader: string | null;
|
|
2154
|
+
tx_hash: string;
|
|
2155
|
+
log_index: number;
|
|
2156
|
+
block_number: number;
|
|
2157
|
+
traded_at: string;
|
|
2158
|
+
amount_in_raw: string | null;
|
|
2159
|
+
amount_out_raw: string | null;
|
|
2160
|
+
token_amount_raw: string | null;
|
|
2161
|
+
quote_amount_raw: string | null;
|
|
2162
|
+
token: RhcSideIdentity;
|
|
2163
|
+
quote: RhcSideIdentity & {
|
|
2164
|
+
usd: number | null;
|
|
2165
|
+
observed_at: string | null;
|
|
2166
|
+
};
|
|
2167
|
+
token_decimals: number | null;
|
|
2168
|
+
quote_decimals: number | null;
|
|
2169
|
+
metadata_status: RhcMetadataStatus;
|
|
2170
|
+
price_status: RhcPriceStatus;
|
|
2171
|
+
price_source: RhcPriceSource | null;
|
|
2172
|
+
/** ISO block time of the print the price was observed at; null when unpriced. */
|
|
2173
|
+
price_observed_at: string | null;
|
|
2174
|
+
mc_status: RhcMcStatus | null;
|
|
2175
|
+
/** `action` when a canonical quote (WETH / ETH / USDG / VIRTUAL or an equity quote) defines "the token", else null. */
|
|
2176
|
+
side: "buy" | "sell" | null;
|
|
2177
|
+
side_reason: RhcSideReason | null;
|
|
2178
|
+
launchpad: string | null;
|
|
2179
|
+
}
|
|
2180
|
+
/** `rhc:dex_trade_unattributed` frame `data` (channel `rhc:dex_trades_unattributed`, ULTRA+). Raw legs plus per-leg identity; never a buy/sell side. */
|
|
2181
|
+
export interface RhcDexTradeUnattributedEvent {
|
|
2182
|
+
chain: "robinhood";
|
|
2183
|
+
dex: string;
|
|
2184
|
+
pool: string;
|
|
2185
|
+
token_in: string;
|
|
2186
|
+
token_out: string;
|
|
2187
|
+
amount_in: string | number | null;
|
|
2188
|
+
amount_out: string | number | null;
|
|
2189
|
+
liquidity_usd: number | null;
|
|
2190
|
+
trader: string | null;
|
|
2191
|
+
tx_hash: string;
|
|
2192
|
+
log_index: number;
|
|
2193
|
+
block_number: number;
|
|
2194
|
+
traded_at: string;
|
|
2195
|
+
amount_in_raw: string | null;
|
|
2196
|
+
amount_out_raw: string | null;
|
|
2197
|
+
token_in_meta: RhcSideIdentity;
|
|
2198
|
+
token_out_meta: RhcSideIdentity;
|
|
2199
|
+
metadata_status: RhcMetadataStatus;
|
|
2200
|
+
price_status: "none";
|
|
2201
|
+
price_source: null;
|
|
2202
|
+
price_observed_at: null;
|
|
2203
|
+
side: null;
|
|
2204
|
+
side_reason: RhcSideReason;
|
|
2205
|
+
}
|
|
2206
|
+
/** Why a tick is not `fresh`. Every rule reuses a server-side pricing guard; none is client-tunable. */
|
|
2207
|
+
export type RhcTickQualityReason = "no_price" | "dust_trade" | "curve_live" | "state_quote_fallback" | "low_liquidity" | "price_age" | "mc_ceiling";
|
|
2208
|
+
/**
|
|
2209
|
+
* `rhc:token_price` frame `data` (channel `rhc:token_prices`, PRO+, address-
|
|
2210
|
+
* scoped). On subscribe every address first gets ONE frame with
|
|
2211
|
+
* `snapshot: true` (the current price row; `source: "rhc_token_prices"`), then
|
|
2212
|
+
* ticks derived from the RHC trade feed (`source: "rhc-dex-stream:trade"`),
|
|
2213
|
+
* at most one per address per 250 ms with the latest value. A `stale` or
|
|
2214
|
+
* `unreliable` price is only ever delivered with its flag — treat `quality`
|
|
2215
|
+
* as part of the price. State stream: no `seq` / `id`, not replayed; a resume
|
|
2216
|
+
* sends a fresh snapshot.
|
|
2217
|
+
*/
|
|
2218
|
+
export interface RhcTokenPriceTick {
|
|
2219
|
+
chain: "robinhood";
|
|
2220
|
+
address: string;
|
|
2221
|
+
symbol: string | null;
|
|
2222
|
+
price_usd: number | null;
|
|
2223
|
+
/** ETH-denominated price; present on snapshots, null on trade-derived ticks. */
|
|
2224
|
+
price_native: number | null;
|
|
2225
|
+
market_cap_usd: number | null;
|
|
2226
|
+
liquidity_usd: number | null;
|
|
2227
|
+
source_pool: string | null;
|
|
2228
|
+
source_dex: string | null;
|
|
2229
|
+
/** ISO block time of the print (tick) or the last trade (snapshot). */
|
|
2230
|
+
observed_at: string | null;
|
|
2231
|
+
/** Age of `observed_at` when the frame was built (ms). */
|
|
2232
|
+
price_age_ms: number | null;
|
|
2233
|
+
quality: "fresh" | "stale" | "unreliable";
|
|
2234
|
+
quality_reason: RhcTickQualityReason | null;
|
|
2235
|
+
price_source: RhcPriceSource | null;
|
|
2236
|
+
/** The print this tick came from; null on a snapshot. */
|
|
2237
|
+
tx_hash: string | null;
|
|
2238
|
+
log_index: number | null;
|
|
2239
|
+
block_number: number | null;
|
|
2240
|
+
source: "rhc_token_prices" | "rhc-dex-stream:trade";
|
|
2241
|
+
}
|
|
1998
2242
|
export interface RhcPriceAlertEventsResponse {
|
|
1999
2243
|
chain: Chain;
|
|
2000
2244
|
events: RhcPriceAlertEvent[];
|