hood-alerts 0.1.0 → 0.2.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/.env.example +90 -0
- package/Dockerfile +65 -0
- package/LICENSE +15 -14
- package/README.md +253 -309
- package/dist/scripts/capture-fixtures.d.ts +2 -0
- package/dist/scripts/capture-fixtures.js +143 -0
- package/dist/scripts/capture-fixtures.js.map +1 -0
- package/dist/scripts/grant-premium.d.ts +2 -0
- package/dist/scripts/grant-premium.js +70 -0
- package/dist/scripts/grant-premium.js.map +1 -0
- package/dist/scripts/live-probe.d.ts +2 -0
- package/dist/scripts/live-probe.js +51 -0
- package/dist/scripts/live-probe.js.map +1 -0
- package/dist/src/commands/commands.d.ts +42 -0
- package/dist/src/commands/commands.js +246 -0
- package/dist/src/commands/commands.js.map +1 -0
- package/dist/src/config.d.ts +69 -0
- package/dist/src/config.js +72 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/db/deliveries.d.ts +17 -0
- package/dist/src/db/deliveries.js +41 -0
- package/dist/src/db/deliveries.js.map +1 -0
- package/dist/src/db/entitlements.d.ts +41 -0
- package/dist/src/db/entitlements.js +73 -0
- package/dist/src/db/entitlements.js.map +1 -0
- package/dist/src/db/index.d.ts +4 -0
- package/dist/src/db/index.js +63 -0
- package/dist/src/db/index.js.map +1 -0
- package/dist/src/db/subscribers.d.ts +44 -0
- package/dist/src/db/subscribers.js +124 -0
- package/dist/src/db/subscribers.js.map +1 -0
- package/dist/src/engine/detectors/holders.d.ts +16 -0
- package/dist/src/engine/detectors/holders.js +33 -0
- package/dist/src/engine/detectors/holders.js.map +1 -0
- package/dist/src/engine/detectors/liquidity.d.ts +24 -0
- package/dist/src/engine/detectors/liquidity.js +62 -0
- package/dist/src/engine/detectors/liquidity.js.map +1 -0
- package/dist/src/engine/detectors/live.d.ts +52 -0
- package/dist/src/engine/detectors/live.js +420 -0
- package/dist/src/engine/detectors/live.js.map +1 -0
- package/dist/src/engine/detectors/premium-ladder.d.ts +14 -0
- package/dist/src/engine/detectors/premium-ladder.js +61 -0
- package/dist/src/engine/detectors/premium-ladder.js.map +1 -0
- package/dist/src/engine/detectors/prices.d.ts +15 -0
- package/dist/src/engine/detectors/prices.js +45 -0
- package/dist/src/engine/detectors/prices.js.map +1 -0
- package/dist/src/engine/detectors/whale-classify.d.ts +18 -0
- package/dist/src/engine/detectors/whale-classify.js +69 -0
- package/dist/src/engine/detectors/whale-classify.js.map +1 -0
- package/dist/src/engine/digest.d.ts +31 -0
- package/dist/src/engine/digest.js +48 -0
- package/dist/src/engine/digest.js.map +1 -0
- package/dist/src/engine/engine.d.ts +40 -0
- package/dist/src/engine/engine.js +175 -0
- package/dist/src/engine/engine.js.map +1 -0
- package/dist/src/engine/eth-price.d.ts +18 -0
- package/dist/src/engine/eth-price.js +85 -0
- package/dist/src/engine/eth-price.js.map +1 -0
- package/dist/src/engine/events.d.ts +94 -0
- package/dist/src/engine/events.js +37 -0
- package/dist/src/engine/events.js.map +1 -0
- package/dist/src/engine/gate.d.ts +35 -0
- package/dist/src/engine/gate.js +35 -0
- package/dist/src/engine/gate.js.map +1 -0
- package/dist/src/engine/token-meta.d.ts +17 -0
- package/dist/src/engine/token-meta.js +44 -0
- package/dist/src/engine/token-meta.js.map +1 -0
- package/dist/src/engine/topics.d.ts +60 -0
- package/dist/src/engine/topics.js +120 -0
- package/dist/src/engine/topics.js.map +1 -0
- package/dist/src/format/cards.d.ts +46 -0
- package/dist/src/format/cards.js +193 -0
- package/dist/src/format/cards.js.map +1 -0
- package/dist/{service/main.d.cts → src/index.d.ts} +1 -0
- package/dist/src/index.js +133 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/logger.d.ts +5 -0
- package/dist/src/logger.js +16 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/premium/paywall.d.ts +40 -0
- package/dist/src/premium/paywall.js +105 -0
- package/dist/src/premium/paywall.js.map +1 -0
- package/dist/src/server.d.ts +14 -0
- package/dist/src/server.js +57 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/transports/console.d.ts +14 -0
- package/dist/src/transports/console.js +24 -0
- package/dist/src/transports/console.js.map +1 -0
- package/dist/src/transports/discord.d.ts +28 -0
- package/dist/src/transports/discord.js +145 -0
- package/dist/src/transports/discord.js.map +1 -0
- package/dist/src/transports/telegram.d.ts +22 -0
- package/dist/src/transports/telegram.js +101 -0
- package/dist/src/transports/telegram.js.map +1 -0
- package/dist/src/transports/types.d.ts +14 -0
- package/dist/src/transports/types.js +2 -0
- package/dist/src/transports/types.js.map +1 -0
- package/dist/src/transports/x.d.ts +58 -0
- package/dist/src/transports/x.js +155 -0
- package/dist/src/transports/x.js.map +1 -0
- package/package.json +80 -112
- package/dist/bot/index.cjs +0 -1008
- package/dist/bot/index.cjs.map +0 -1
- package/dist/bot/index.d.cts +0 -241
- package/dist/bot/index.d.ts +0 -241
- package/dist/bot/index.js +0 -30
- package/dist/bot/index.js.map +0 -1
- package/dist/chunk-32GS6XVE.js +0 -732
- package/dist/chunk-32GS6XVE.js.map +0 -1
- package/dist/chunk-3LHCQA4Z.js +0 -76
- package/dist/chunk-3LHCQA4Z.js.map +0 -1
- package/dist/chunk-3NHLSKZE.js +0 -1
- package/dist/chunk-3NHLSKZE.js.map +0 -1
- package/dist/chunk-6JQK5G3Z.js +0 -72
- package/dist/chunk-6JQK5G3Z.js.map +0 -1
- package/dist/chunk-CBNZ6AZT.js +0 -598
- package/dist/chunk-CBNZ6AZT.js.map +0 -1
- package/dist/chunk-CIURPPOW.js +0 -1
- package/dist/chunk-CIURPPOW.js.map +0 -1
- package/dist/chunk-IUOEGBF6.js +0 -49
- package/dist/chunk-IUOEGBF6.js.map +0 -1
- package/dist/chunk-NBMINJ5E.js +0 -230
- package/dist/chunk-NBMINJ5E.js.map +0 -1
- package/dist/chunk-O2RY2SWN.js +0 -1
- package/dist/chunk-O2RY2SWN.js.map +0 -1
- package/dist/chunk-PLMVKTQM.js +0 -1
- package/dist/chunk-PLMVKTQM.js.map +0 -1
- package/dist/chunk-RSTLW7XH.js +0 -130
- package/dist/chunk-RSTLW7XH.js.map +0 -1
- package/dist/chunk-SOBZ2FPP.js +0 -355
- package/dist/chunk-SOBZ2FPP.js.map +0 -1
- package/dist/chunk-VC5MKEY2.js +0 -170
- package/dist/chunk-VC5MKEY2.js.map +0 -1
- package/dist/chunk-W2GUAWUR.js +0 -147
- package/dist/chunk-W2GUAWUR.js.map +0 -1
- package/dist/chunk-Y2M3USWZ.js +0 -355
- package/dist/chunk-Y2M3USWZ.js.map +0 -1
- package/dist/chunk-Z4DJPESQ.js +0 -1216
- package/dist/chunk-Z4DJPESQ.js.map +0 -1
- package/dist/chunk-ZDM5VNWB.js +0 -1
- package/dist/chunk-ZDM5VNWB.js.map +0 -1
- package/dist/events/index.cjs +0 -820
- package/dist/events/index.cjs.map +0 -1
- package/dist/events/index.d.cts +0 -58
- package/dist/events/index.d.ts +0 -58
- package/dist/events/index.js +0 -69
- package/dist/events/index.js.map +0 -1
- package/dist/format-6RG4OcyE.d.ts +0 -57
- package/dist/format-B7d40f65.d.cts +0 -57
- package/dist/index.cjs +0 -4122
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -21
- package/dist/index.d.ts +0 -21
- package/dist/index.js +0 -274
- package/dist/index.js.map +0 -1
- package/dist/logger-3phvV_fl.d.cts +0 -185
- package/dist/logger-CYza_0eU.d.ts +0 -185
- package/dist/logs-D7sYBvSy.d.cts +0 -66
- package/dist/logs-D7sYBvSy.d.ts +0 -66
- package/dist/notifiers/index.cjs +0 -620
- package/dist/notifiers/index.cjs.map +0 -1
- package/dist/notifiers/index.d.cts +0 -186
- package/dist/notifiers/index.d.ts +0 -186
- package/dist/notifiers/index.js +0 -70
- package/dist/notifiers/index.js.map +0 -1
- package/dist/pricing-BumgLpvB.d.cts +0 -79
- package/dist/pricing-BumgLpvB.d.ts +0 -79
- package/dist/ratelimit-BKVQG2oY.d.cts +0 -42
- package/dist/ratelimit-CGLlXNIS.d.ts +0 -42
- package/dist/registry-D3We8Lg6.d.ts +0 -50
- package/dist/registry-DePA3IyS.d.cts +0 -50
- package/dist/rules/index.cjs +0 -614
- package/dist/rules/index.cjs.map +0 -1
- package/dist/rules/index.d.cts +0 -166
- package/dist/rules/index.d.ts +0 -166
- package/dist/rules/index.js +0 -60
- package/dist/rules/index.js.map +0 -1
- package/dist/schema-DuDMPqpq.d.ts +0 -183
- package/dist/schema-NnIdKFl2.d.cts +0 -183
- package/dist/service/index.cjs +0 -3774
- package/dist/service/index.cjs.map +0 -1
- package/dist/service/index.d.cts +0 -262
- package/dist/service/index.d.ts +0 -262
- package/dist/service/index.js +0 -45
- package/dist/service/index.js.map +0 -1
- package/dist/service/main.cjs +0 -3759
- package/dist/service/main.cjs.map +0 -1
- package/dist/service/main.d.ts +0 -1
- package/dist/service/main.js +0 -60
- package/dist/service/main.js.map +0 -1
- package/dist/sources-CAaQR6-N.d.ts +0 -159
- package/dist/sources-CpDOUlzx.d.cts +0 -159
- package/dist/telegram-CpodYJrP.d.cts +0 -64
- package/dist/telegram-pvphmBwn.d.ts +0 -64
- package/dist/tiers/index.cjs +0 -471
- package/dist/tiers/index.cjs.map +0 -1
- package/dist/tiers/index.d.cts +0 -231
- package/dist/tiers/index.d.ts +0 -231
- package/dist/tiers/index.js +0 -34
- package/dist/tiers/index.js.map +0 -1
- package/dist/types-C0ETog04.d.ts +0 -109
- package/dist/types-CcoKpStA.d.cts +0 -109
- package/dist/types-DFPpTMWo.d.cts +0 -186
- package/dist/types-DFPpTMWo.d.ts +0 -186
package/README.md
CHANGED
|
@@ -1,369 +1,313 @@
|
|
|
1
1
|
# hood-alerts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](./LICENSE)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The alert layer for **Robinhood Chain** (chain ID 4663): Telegram and Discord bots, plus optional
|
|
6
|
+
X (Twitter) auto-posting, backed by one shared detection engine that watches launches, graduations,
|
|
7
|
+
whale trades, Stock Token price moves, on-chain premium/discount arbitrage, holder milestones, and
|
|
8
|
+
liquidity-pull rug warnings. Free tier plus x402 USDG premium.
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
- a schema-validated **rule engine** whose rules are data a subscriber can edit from chat,
|
|
9
|
-
- **delivery adapters** that get escaping and rate limiting right on both platforms,
|
|
10
|
-
- the **bot** command surface,
|
|
11
|
-
- an enforced **tier policy**,
|
|
12
|
-
- and a **hosted service** that survives a restart mid-stream without double-sending or silently skipping a block.
|
|
10
|
+
## What it detects
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
| Detector | Topic | Source |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| New token launch | `launches` (`launches:noxa`, `launches:odyssey`) | NOXA (instant Uniswap v3 pool) and The Odyssey (bonding curve) |
|
|
15
|
+
| Bonding-curve graduation | `graduations` | Odyssey curve fills and migrates to a locked Uniswap v3 pool |
|
|
16
|
+
| Whale trades | `whales`, or per-token `token:0x…` | Odyssey curve trades and Uniswap v3 swaps, chain-wide, threshold in USD |
|
|
17
|
+
| Stock Token price moves | `stock:SYMBOL` | Chainlink feed, rolling window, signed percent change |
|
|
18
|
+
| On-chain premium/discount (the arb signal) | `premiums` (premium tier) | DEX mid vs Chainlink feed, hysteresis ladder at 1/2/3/5/10/20/50% |
|
|
19
|
+
| Holder milestones | per-token `token:0x…` | Blockscout `token_holders_count`, crossing 10 through 100,000 |
|
|
20
|
+
| Liquidity-pull rug warning | `rugs` (premium tier), or per-token `token:0x…` | tracked pool's quote-side (USDG/WETH) reserves dropping sharply |
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
Every event is deduped per fingerprint before it reaches a subscriber, routed through a
|
|
23
|
+
premium/quiet-hours/digest gate, and rendered as a platform-native card (Telegram HTML, Discord
|
|
24
|
+
embed, a compact X post, or a structured console log line) with links back to the chain explorer
|
|
25
|
+
and `three.ws/markets/robinhood`.
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
## Quickstart (self-host)
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
| At launch | Deploys the ERC-20, creates a Uniswap v3 pool, seeds single-sided liquidity and locks the LP NFT, all in one transaction | Opens a curve. No pool exists yet |
|
|
24
|
-
| Trading | Normal Uniswap v3 swaps from block one | On the curve until it fills |
|
|
25
|
-
| Graduation | **Never happens. There is no curve to fill** | `PoolCompleted` + `PoolMigrated` when the curve fills and liquidity moves to a locked Uniswap v3 pool |
|
|
26
|
-
| Emits | `launch`, `whale_trade` | `launch`, `curve_trade`, `graduation`, `whale_trade` |
|
|
27
|
-
|
|
28
|
-
A "NOXA graduation" alert would describe an event that does not exist on chain. hood-alerts encodes that asymmetry in the types and in the rule schema, so an impossible rule is **rejected at validation time** instead of being accepted and never firing:
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
import { safeParseRule } from 'hood-alerts/rules'
|
|
32
|
-
|
|
33
|
-
const result = safeParseRule({ id: 'nope', kinds: ['graduation'], launchpads: ['noxa'] })
|
|
34
|
-
console.log(result.success)
|
|
35
|
-
// false: "this combination can never fire: NOXA is an instant launcher with no
|
|
36
|
-
// bonding curve, so it emits no curve_trade and no graduation"
|
|
29
|
+
```sh
|
|
30
|
+
npm install
|
|
31
|
+
npm run dev # tsx watch, console transport only if no bot tokens are set
|
|
37
32
|
```
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
With zero environment variables the engine still runs: detectors connect to the public Robinhood
|
|
35
|
+
Chain RPC, alerts print as structured JSON log lines (the console transport), and `/healthz` comes
|
|
36
|
+
up on `:8080`. That is the self-host smoke-test mode. Copy `.env.example` to `.env` and fill in a
|
|
37
|
+
Telegram and/or Discord bot token to go live on those platforms; see `.env.example` for every
|
|
38
|
+
variable, defaults, and what each detector threshold controls.
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| `graduation` | Odyssey `PoolMigrated` | Odyssey only | the quote side used to seed the migrated pool | `pool`, `positionId`, `liquidity`, `tokenUsed`, `quoteUsed` |
|
|
48
|
-
| `whale_trade` | Uniswap v3 `Swap` on a tracked memecoin pool | both | the swap's quote leg | `pool`, `quoteToken`, `quoteSymbol`, `side`, `tokenAmount`, `quoteAmount`, `price`, `feeTier` |
|
|
49
|
-
|
|
50
|
-
### USD values are measured, never assumed
|
|
51
|
-
|
|
52
|
-
There is no hardcoded price anywhere in this package.
|
|
53
|
-
|
|
54
|
-
- **USDG legs are the unit of account.** USDG is the chain's fully reserved dollar stablecoin at 6 decimals, so a USDG leg is its own USD value.
|
|
55
|
-
- **ETH legs are priced through the chain's own liquidity.** `hoodchain`'s `quoteSwap` is asked for the real output of selling 1 WETH into USDG across every fee tier and two-hop route, cached for 30 seconds and coalesced so a burst of events makes one quote.
|
|
56
|
-
- **Anything else is `null`.** A memecoin/memecoin pool has no honest USD value from chain data alone, so the event says so and a `minUsd` rule does not match it. Unknown is never treated as zero (which would satisfy every `maxUsd` filter) or as infinity.
|
|
57
|
-
|
|
58
|
-
### Evidence: every source decoded against mainnet
|
|
40
|
+
```sh
|
|
41
|
+
npm run build # tsc -> dist/
|
|
42
|
+
npm start # node dist/src/index.js
|
|
43
|
+
```
|
|
59
44
|
|
|
60
|
-
|
|
45
|
+
### Creating the bots
|
|
46
|
+
|
|
47
|
+
- **Telegram**: message [@BotFather](https://t.me/BotFather), `/newbot`, paste the token into
|
|
48
|
+
`HOOD_ALERTS_TELEGRAM_TOKEN`. The bot registers its command list on startup.
|
|
49
|
+
- **Discord**: create an application at the
|
|
50
|
+
[Developer Portal](https://discord.com/developers/applications), add a bot, copy the bot token
|
|
51
|
+
into `HOOD_ALERTS_DISCORD_TOKEN` and the application id into `HOOD_ALERTS_DISCORD_APP_ID`. Slash
|
|
52
|
+
commands register (guild-visible immediately) on startup.
|
|
53
|
+
|
|
54
|
+
Leaving either token unset disables that transport; the engine and the console transport keep
|
|
55
|
+
running regardless.
|
|
56
|
+
|
|
57
|
+
### X (Twitter)
|
|
58
|
+
|
|
59
|
+
X is a broadcast-only transport: there is no inbound bot, so you cannot DM the account and get a
|
|
60
|
+
reply the way you can with Telegram or Discord. What it auto-posts is fixed at startup by
|
|
61
|
+
`HOOD_ALERTS_X_TOPICS` (default `launches,graduations,whales`) instead of `watch`/`unwatch`
|
|
62
|
+
commands, parsed through the same topic resolver the bots use. Pick exactly **one** of two modes
|
|
63
|
+
via `HOOD_ALERTS_X_MODE`; see `.env.example` for the full variable list.
|
|
64
|
+
|
|
65
|
+
**`official`**: the real X API v2, `POST /2/tweets`, signed with your own developer app's OAuth1
|
|
66
|
+
user-context credentials. This is the normal, ToS-compliant path, and it costs money: X gates
|
|
67
|
+
posting behind a paid API tier. Check current pricing at
|
|
68
|
+
[developer.x.com/en/portal/products/buy](https://developer.x.com/en/portal/products/buy) before
|
|
69
|
+
relying on it; it has changed before and can change again.
|
|
70
|
+
|
|
71
|
+
Setup: create an app at the [developer portal](https://developer.x.com/en/portal), generate a
|
|
72
|
+
consumer key/secret (`HOOD_ALERTS_X_API_KEY` / `HOOD_ALERTS_X_API_SECRET`), then generate an
|
|
73
|
+
access token/secret with **read and write** permissions (`HOOD_ALERTS_X_ACCESS_TOKEN` /
|
|
74
|
+
`HOOD_ALERTS_X_ACCESS_SECRET`). All four are required; hood-alerts signs every request itself with
|
|
75
|
+
Node's built-in `crypto` (HMAC-SHA1 per RFC 5849), no extra dependency.
|
|
76
|
+
|
|
77
|
+
**`xactions`**: posts through a self-hosted [XActions](https://github.com/nirholas/XActions)
|
|
78
|
+
instance instead of the official API: browser-automation driving your own X session cookie. Free,
|
|
79
|
+
no developer account, no paid tier. This is **not** the official API and carries real ToS risk;
|
|
80
|
+
run it on an account you are prepared to lose if X acts on that. Point `HOOD_ALERTS_XACTIONS_URL`
|
|
81
|
+
at your running instance and `HOOD_ALERTS_XACTIONS_TOKEN` at its bearer token. Posting is
|
|
82
|
+
async/job-queued: hood-alerts treats a 2xx from `POST {url}/api/posting/tweet` as "accepted", not
|
|
83
|
+
"delivered", and logs that delivery is not confirmed synchronously (the real xactions API responds
|
|
84
|
+
`{ operationId, status: "queued" }`, not a tweet id).
|
|
85
|
+
|
|
86
|
+
Both modes fail loudly: a configured mode missing any required credential logs a warning and
|
|
87
|
+
disables the X transport (Telegram, Discord, and the console transport keep running) instead of
|
|
88
|
+
crashing the process. Every X post is compacted to fit X's 280-character limit: title, the single
|
|
89
|
+
most important line, and the first link; only the text portion truncates, never the URL. Digests
|
|
90
|
+
(too many alerts to post individually) post the single most significant event plus a `(+N more)`
|
|
91
|
+
count, and log the full summarized batch, so nothing is silently dropped from the record even
|
|
92
|
+
though it is dropped from the post itself.
|
|
93
|
+
|
|
94
|
+
## Bot commands
|
|
95
|
+
|
|
96
|
+
Both bots share one command router (`src/commands/commands.ts`), so the UX is identical modulo
|
|
97
|
+
native slash-command vs `/command arg` syntax:
|
|
61
98
|
|
|
62
99
|
```
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
graduation ROBIN 0xfB4729659eeF22Bfc1c2B680F6F873f8147aaaab $7618.47 block 1048638
|
|
80
|
-
Whale trades 4045 events 83886ms
|
|
81
|
-
whale_trade CHEEMS 0xdaA213A0Bd8B048D6022e2c46df877E8A204072b $190.46 block 1439024
|
|
82
|
-
|
|
83
|
-
pools discovered and registered for whale watching: 2622
|
|
100
|
+
watch <what> [threshold]
|
|
101
|
+
launches every new token (add noxa/odyssey to filter)
|
|
102
|
+
graduations Odyssey curves migrating to Uniswap v3
|
|
103
|
+
whales [usd] trades >= usd, chain-wide (default 5000)
|
|
104
|
+
premiums [pct] Stock Token DEX premium/discount vs Chainlink (premium tier)
|
|
105
|
+
rugs [pct] liquidity-pull rug warnings (premium tier)
|
|
106
|
+
<TICKER> a Stock Token: price moves + premium crossings (e.g. TSLA)
|
|
107
|
+
<0x address> a memecoin: whale trades, graduation, holders, liquidity
|
|
108
|
+
|
|
109
|
+
unwatch <what|all> stop watching
|
|
110
|
+
list your subscriptions
|
|
111
|
+
threshold <what> <n> change a subscription's threshold
|
|
112
|
+
digest on|off [min] batch alerts into digests (default 60 min)
|
|
113
|
+
quiet <from> <to> quiet hours in UTC (e.g. quiet 22 7); quiet off
|
|
114
|
+
premium premium status + how to upgrade
|
|
115
|
+
help this message
|
|
84
116
|
```
|
|
85
117
|
|
|
86
|
-
|
|
118
|
+
In a group chat or server, `watch`, `unwatch`, `threshold`, `digest`, and `quiet` are restricted to
|
|
119
|
+
admins; `list`, `premium`, and `help` are open to everyone.
|
|
87
120
|
|
|
88
|
-
|
|
121
|
+
## Free vs premium
|
|
89
122
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
123
|
+
| | Free | Premium |
|
|
124
|
+
| --- | --- | --- |
|
|
125
|
+
| Subscriptions | 3 | Unlimited |
|
|
126
|
+
| Delivery | Batched to at most one immediate alert per 60s; extras fold into a digest | Real-time, subject only to quiet hours / digest mode you set |
|
|
127
|
+
| `premiums` (Stock Token arb signal) | Not available | Included |
|
|
128
|
+
| `rugs` (liquidity-pull warning) | Not available | Included |
|
|
129
|
+
| Price | Free | `HOOD_ALERTS_PREMIUM_PRICE_USDG` (default 5) USDG for `HOOD_ALERTS_PREMIUM_DAYS` (default 30) days |
|
|
95
130
|
|
|
96
|
-
|
|
131
|
+
Premium is paid on Robinhood Chain via **x402** using [`hood402`](../hood402) (USDG,
|
|
132
|
+
EIP-3009 `transferWithAuthorization`, chain 4663). The bot's `premium` command hands back a
|
|
133
|
+
`POST /premium/activate?platform=...&chat=...` URL: the first request gets a 402 challenge with
|
|
134
|
+
payment instructions, and any x402 client (`hood402`, `x402-fetch`) that can sign a USDG
|
|
135
|
+
authorization completes the purchase. The entitlement never activates before the transfer settles
|
|
136
|
+
on-chain, so a signature alone buys nothing.
|
|
97
137
|
|
|
98
|
-
|
|
138
|
+
Purchases stay disabled (`503` + setup instructions) until the instance sets `HOOD402_PAY_TO` and
|
|
139
|
+
either `HOOD402_FACILITATOR_URL` (delegates verification and settlement, no gas key on this box) or
|
|
140
|
+
`HOOD402_SETTLER_KEY` (this instance broadcasts the settlement itself). See `.env.example` for both
|
|
141
|
+
modes.
|
|
99
142
|
|
|
100
|
-
|
|
101
|
-
import { createHoodClient } from 'hoodchain'
|
|
102
|
-
import {
|
|
103
|
-
createEventSources,
|
|
104
|
-
createMemoryPoolRegistry,
|
|
105
|
-
createPriceOracle,
|
|
106
|
-
createTokenMetaReader,
|
|
107
|
-
} from 'hood-alerts/events'
|
|
108
|
-
|
|
109
|
-
const hood = createHoodClient()
|
|
110
|
-
const sources = createEventSources({
|
|
111
|
-
client: hood,
|
|
112
|
-
oracle: createPriceOracle(hood),
|
|
113
|
-
tokens: createTokenMetaReader(hood),
|
|
114
|
-
registry: createMemoryPoolRegistry(),
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
const head = await hood.public.getBlockNumber()
|
|
118
|
-
for (const source of sources) {
|
|
119
|
-
for (const event of await source.poll(head - 5_000n, head - 2n)) {
|
|
120
|
-
console.log(event.kind, event.symbol, event.usdValue, event.explorer.tx)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
```
|
|
143
|
+
## HTTP API
|
|
124
144
|
|
|
125
|
-
|
|
145
|
+
The same process that runs the bots serves a small Hono app on `PORT` (default 8080):
|
|
126
146
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
147
|
+
| Endpoint | Method | Returns |
|
|
148
|
+
| --- | --- | --- |
|
|
149
|
+
| `/` | GET | service metadata and endpoint list |
|
|
150
|
+
| `/healthz` | GET | uptime, last detector event time, per-event-type counts, whether premium purchases are enabled |
|
|
151
|
+
| `/premium/status?platform=telegram\|discord\|console\|x&chat=<id>` | GET | `{ tier: 'free' \| 'premium', expiresAt }` for that chat |
|
|
152
|
+
| `/premium/activate?platform=...&chat=...` | POST | the x402 purchase flow: 402 challenge, then 200 + entitlement once payment settles |
|
|
130
153
|
|
|
131
|
-
|
|
132
|
-
parseRule({ id: 'whales', name: 'Whale buys over $25k', kinds: ['whale_trade'], minUsd: 25_000, side: 'buy' }),
|
|
133
|
-
parseRule({ id: 'grads', kinds: ['graduation'], launchpads: ['odyssey'] }),
|
|
134
|
-
]
|
|
154
|
+
## Example: reading the alert engine's pure logic directly
|
|
135
155
|
|
|
136
|
-
|
|
156
|
+
`src/engine/gate.ts` and `src/engine/topics.ts` have no I/O and are safe to import standalone, for
|
|
157
|
+
example to preview how a threshold will gate before wiring a subscription:
|
|
137
158
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
159
|
+
```ts
|
|
160
|
+
import { gate } from './src/engine/gate.js'
|
|
161
|
+
import { resolveWatchTarget, parseTopic } from './src/engine/topics.js'
|
|
162
|
+
|
|
163
|
+
const target = resolveWatchTarget('whales') // { ok: true, topic: 'whales' }
|
|
164
|
+
if (target.ok) {
|
|
165
|
+
const topic = parseTopic(target.topic) // { kind: 'whales' }
|
|
166
|
+
const decision = gate(
|
|
167
|
+
{ premium: false, digest: false, quietStart: null, quietEnd: null, lastDeliveredAt: null, deliveredLastMinute: 0 },
|
|
168
|
+
Math.floor(Date.now() / 1000),
|
|
169
|
+
)
|
|
170
|
+
console.log(topic, decision) // { kind: 'whales' } { action: 'deliver' }
|
|
141
171
|
}
|
|
142
172
|
```
|
|
143
173
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
npm run demo:notify
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Rules are data
|
|
151
|
-
|
|
152
|
-
A rule is a JSON document validated by a [Zod](https://zod.dev) schema, so it can be stored per subscriber, edited from a chat command, exported and diffed. The schema is the single source of truth: the bot, the service and the tier policy all validate through it.
|
|
153
|
-
|
|
154
|
-
| Field | Type | Filter |
|
|
155
|
-
|---|---|---|
|
|
156
|
-
| `id` | slug | Stable id, unique per subscription |
|
|
157
|
-
| `name` | string | Label shown in `/rules` |
|
|
158
|
-
| `enabled` | boolean | Off without deleting |
|
|
159
|
-
| `kinds` | `launch` / `curve_trade` / `graduation` / `whale_trade` | Which events |
|
|
160
|
-
| `launchpads` | `noxa` / `odyssey` | Which launchpad |
|
|
161
|
-
| `minUsd`, `maxUsd` | number | USD value band. An unpriced event never matches |
|
|
162
|
-
| `minLiquidityUsd`, `maxLiquidityUsd` | number | Deepest known pool's USD reserves |
|
|
163
|
-
| `tokens` | address[] | Watchlist. Non-empty means only these tokens |
|
|
164
|
-
| `deployers` | address[] | Only these actors |
|
|
165
|
-
| `excludeDeployers` | address[] | Never these actors |
|
|
166
|
-
| `side` | `buy` / `sell` / `any` | Trade direction (trades only) |
|
|
167
|
-
| `reputation.minPriorLaunches` | int | Deployer's prior launches as of the event's block |
|
|
168
|
-
| `reputation.maxPriorLaunches` | int | Rejects serial deployers |
|
|
169
|
-
| `reputation.maxRuggedLaunches` | int | Rejects deployers with drained pools |
|
|
170
|
-
| `reputation.requireLpLocked` | boolean | LP NFT held by the launchpad locker |
|
|
171
|
-
| `rateLimit.maxPerHour` | int | Cap per rolling hour |
|
|
172
|
-
| `rateLimit.minIntervalSeconds` | int | Minimum gap between alerts |
|
|
173
|
-
|
|
174
|
-
Evaluation is lazy and ordered: cheap in-memory filters run before anything that costs an RPC call, so a rule that rejects an event on its kind never triggers a pool balance read. With thousands of subscriptions that is the difference between keeping up with the chain and not.
|
|
175
|
-
|
|
176
|
-
### Deployer reputation is derived on chain
|
|
177
|
-
|
|
178
|
-
Nothing is scraped, self-reported or scored by a model.
|
|
179
|
-
|
|
180
|
-
- **Prior launches**: `TokenLaunched` logs on the NOXA factory indexed by `deployer`, plus `TokenCreated` on the three Odyssey factories indexed by `creator`. Both queries are topic-selective, so the RPC serves the full chain history in one call each. Counted relative to the event's own block, so replaying a historical range gives the same answer twice.
|
|
181
|
-
- **LP locked**: `NonfungiblePositionManager.ownerOf(positionId)` equals the NOXA locker contract. That is the launchpad's own permanent-lock mechanism, read directly rather than trusted from a UI badge.
|
|
182
|
-
- **Drained**: a prior launch whose pool quote reserve is now below the rug threshold (default $50). This is a **heuristic** and is labelled as one everywhere it appears: a token that never traded and a token whose liquidity was pulled both end up with an empty pool. Pair it with `minPriorLaunches` for a meaningful signal.
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
npx tsx examples/deployer-reputation.ts 0xYourDeployerAddress
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## Escaping, because that is how alert bots die
|
|
189
|
-
|
|
190
|
-
Memecoin names are adversarial input by nature. A token called `WHO_LET_THE` italicises half a MarkdownV2 message; a token called `<b>RUG` injects markup under HTML parse mode. Either way Telegram answers `400 Bad Request: can't parse entities` and the alert is lost.
|
|
191
|
-
|
|
192
|
-
Each context gets its own escaper and its own tests, because the rules are not symmetric even within one platform:
|
|
193
|
-
|
|
194
|
-
| Context | Escaped |
|
|
195
|
-
|---|---|
|
|
196
|
-
| Telegram MarkdownV2 text | all 18 of ``_*[]()~`>#+-=\|{}.!`` |
|
|
197
|
-
| MarkdownV2 link URL | only `)` and `\` (escaping the full set corrupts query strings) |
|
|
198
|
-
| MarkdownV2 code span | only `` ` `` and `\` |
|
|
199
|
-
| Telegram HTML text | `&`, `<`, `>` |
|
|
200
|
-
| Telegram HTML attribute | the above plus `"` |
|
|
201
|
-
| Discord embeds | ``\*_~`\|>[]()`` |
|
|
174
|
+
A threshold typed after the target (`watch whales 10000`) is parsed separately by
|
|
175
|
+
`Commands.watch()`, not by `resolveWatchTarget` itself.
|
|
202
176
|
|
|
203
|
-
|
|
177
|
+
The package does not ship a public library entry point today (`main`/`bin` both point at the CLI
|
|
178
|
+
process in `src/index.ts`, which starts the bots and the HTTP server and does not export anything);
|
|
179
|
+
importing from `src/` as above works in this checkout but is not a published API contract.
|
|
204
180
|
|
|
205
|
-
##
|
|
181
|
+
## Configuration
|
|
206
182
|
|
|
207
|
-
|
|
183
|
+
Every variable, its default, and what it controls: [`.env.example`](.env.example). Highlights:
|
|
208
184
|
|
|
209
|
-
-
|
|
210
|
-
-
|
|
185
|
+
- `HOOD_ALERTS_RPC_URL` — custom RPC; defaults to viem's public `robinhood` chain RPC.
|
|
186
|
+
- `HOOD_ALERTS_DB` — SQLite path (subscriptions, entitlements, dedup, delivery log). `:memory:`
|
|
187
|
+
works for tests.
|
|
188
|
+
- `HOOD_ALERTS_WHALE_FLOOR_USD` / `HOOD_ALERTS_WHALE_DEFAULT_USD` — engine-side emission floor vs.
|
|
189
|
+
the default per-subscription threshold.
|
|
190
|
+
- `HOOD_ALERTS_PRICE_WINDOW_S` / `HOOD_ALERTS_PRICE_DEFAULT_PCT` — Chainlink rolling-window move
|
|
191
|
+
detector.
|
|
192
|
+
- `HOOD_ALERTS_PREMIUM_POLL_S` / `HOOD_ALERTS_PREMIUM_DEFAULT_PCT` — Stock Token arb poll cadence
|
|
193
|
+
and default ladder entry.
|
|
194
|
+
- `HOOD_ALERTS_RUG_DEFAULT_PCT` — default liquidity-pull threshold (percent of quote reserves).
|
|
211
195
|
|
|
212
|
-
##
|
|
213
|
-
|
|
214
|
-
One process: poll the chain, match rules, queue deliveries, drain the queue, serve health and metrics.
|
|
196
|
+
## Development
|
|
215
197
|
|
|
216
|
-
```
|
|
217
|
-
cp .env.example .env # nothing is required for a dry run
|
|
198
|
+
```sh
|
|
218
199
|
npm install
|
|
219
|
-
npm run
|
|
220
|
-
|
|
200
|
+
npm run typecheck # tsc --noEmit, strict + exactOptionalPropertyTypes
|
|
201
|
+
npm test # vitest: dedup/rate-limit gate, digest batching, topic parsing and
|
|
202
|
+
# matching, event fingerprinting/TTL, alert card rendering, config
|
|
203
|
+
# loading, the command router, and the subscriber/entitlement/delivery
|
|
204
|
+
# repositories — all offline, no chain or bot credentials required
|
|
205
|
+
npm run build # tsc -> dist/
|
|
221
206
|
```
|
|
222
207
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
-e TELEGRAM_BOT_TOKEN=123456789:AA... hood-alerts
|
|
227
|
-
```
|
|
208
|
+
The `whale-classify` and `detectors` suites run against **real captured mainnet event streams** in
|
|
209
|
+
`tests/fixtures/` (real Uniswap v3 swaps, Odyssey bonding-curve trades, and Chainlink prices).
|
|
210
|
+
Refresh them any time with `npm run capture-fixtures`, which re-reads live chain 4663.
|
|
228
211
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
| `GET /metrics` | Prometheus text: subscriptions, outbox by status, deliveries per hour, tracked pools, per-source cursor and lag |
|
|
234
|
-
| `POST /discord/interactions` | Discord slash commands (Ed25519 verified) |
|
|
212
|
+
`npm run test:live` and `npm run probe` exercise the live detector set against real Robinhood Chain
|
|
213
|
+
RPC and, for `probe`, the console transport; they need real network access and are not part of the
|
|
214
|
+
offline unit suite above. `npm run grant-premium` comps or inspects a premium entitlement directly
|
|
215
|
+
in the database (handy for testing premium delivery before a payment rail is wired).
|
|
235
216
|
|
|
236
|
-
###
|
|
217
|
+
### Local sibling packages
|
|
237
218
|
|
|
238
|
-
|
|
219
|
+
`hood402`, `hoodkit`, and `hoodchain` are consumed as `file:` dependencies of the other Robinhood
|
|
220
|
+
Chain repos in this workspace (`../hood402`, `../hoodkit`, `../robinhood-chain-sdk`) rather than
|
|
221
|
+
their published npm versions, so a change to any of them is picked up on the next `npm install`
|
|
222
|
+
here. A `file:` link keeps each sibling's own `viem` copy, so a patch-level skew (e.g. `2.55.2`
|
|
223
|
+
here vs `2.55.1` in `hood402`) makes viem's structural `Chain` / `WalletClient` types diverge across
|
|
224
|
+
the two copies. `src/premium/paywall.ts` bridges that boundary explicitly (casting the local viem
|
|
225
|
+
clients to `hood402`'s `HoodBroadcaster` / `HoodConfirmer`), so the build stays green across a patch
|
|
226
|
+
skew; the runtime objects are the same correct viem clients. Once the siblings are on npm and a
|
|
227
|
+
single `viem` resolves, the cast is a no-op.
|
|
239
228
|
|
|
240
|
-
|
|
241
|
-
2. **Never silently skip.** The block cursor advances only after every event in a chunk has been enqueued and committed. A crash mid-chunk leaves the cursor at the start of that chunk, so the range is re-read. A failed RPC call leaves it untouched for the same reason.
|
|
242
|
-
3. **Never lose an enqueued alert.** Queued rows live in an outbox until they are delivered or dead-lettered. On startup, rows left in `sending` (a crash mid-flight) return to `pending` and are retried.
|
|
229
|
+
## Deploy
|
|
243
230
|
|
|
244
|
-
|
|
231
|
+
hood-alerts is one long-running process (engine + HTTP server + both bots), so run it as a service
|
|
232
|
+
with a warm instance, not a job.
|
|
245
233
|
|
|
246
|
-
|
|
234
|
+
Because the sibling packages are `file:` links, the Docker build context is the **parent
|
|
235
|
+
`robinhood/` directory** so they resolve:
|
|
247
236
|
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
✓ crash recovery > re-processes only the unfinished range after a crash mid-batch
|
|
252
|
-
✓ crash recovery > returns rows abandoned in flight to the queue on restart
|
|
253
|
-
```
|
|
237
|
+
```sh
|
|
238
|
+
# from robinhood/ (the parent of hood-alerts/)
|
|
239
|
+
docker build -f hood-alerts/Dockerfile -t hood-alerts .
|
|
254
240
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
| Command | What it does |
|
|
260
|
-
|---|---|
|
|
261
|
-
| `/start`, `/help` | Every command, plus the launchpad lifecycle explanation |
|
|
262
|
-
| `/subscribe [target]` | Send alerts here with a starter rule set. On Discord the target may be a webhook URL or a channel id; it defaults to the current channel |
|
|
263
|
-
| `/unsubscribe [id]` | Stop a subscription. Lists them when there is more than one |
|
|
264
|
-
| `/rules` | Every rule on every subscription, with its filters |
|
|
265
|
-
| `/rule add {json}` | Add a rule. Validation errors come back as the field that failed |
|
|
266
|
-
| `/rule rm\|on\|off <id>` | Remove, enable or disable a rule |
|
|
267
|
-
| `/threshold <rule> <usd>` | Set a rule's minimum USD value |
|
|
268
|
-
| `/watch <token> [rule]` | Add a token to a rule's watchlist |
|
|
269
|
-
| `/unwatch <token> [rule]` | Remove one |
|
|
270
|
-
| `/tier` | Your tier, its limits, and where the entitlement came from |
|
|
271
|
-
| `/status` | Subscriptions, rule counts, alerts used this hour |
|
|
272
|
-
| `/link [address] [signature]` | Link the wallet that pays. Send it bare to get the message to sign |
|
|
273
|
-
| `/upgrade` | How to go premium on this deployment |
|
|
274
|
-
|
|
275
|
-
Register the Discord slash commands with:
|
|
276
|
-
|
|
277
|
-
```bash
|
|
278
|
-
DISCORD_APPLICATION_ID=... DISCORD_BOT_TOKEN=... npm run register:discord
|
|
241
|
+
gcloud run deploy hood-alerts \
|
|
242
|
+
--image=REGION-docker.pkg.dev/PROJECT/repo/hood-alerts:latest \
|
|
243
|
+
--min-instances=1 --no-cpu-throttling --port=8080 \
|
|
244
|
+
--set-env-vars=HOOD_ALERTS_TELEGRAM_TOKEN=…,HOOD_ALERTS_DISCORD_TOKEN=…,HOOD_ALERTS_DISCORD_APP_ID=…
|
|
279
245
|
```
|
|
280
246
|
|
|
281
|
-
|
|
247
|
+
Mount a volume at `/data` (the default `HOOD_ALERTS_DB` path) so subscriptions, entitlements, and
|
|
248
|
+
the delivery log survive restarts. The container answers `GET /healthz`; `SIGTERM` flushes pending
|
|
249
|
+
digests before exit. Once the siblings are published to npm, switch the `file:` specs in
|
|
250
|
+
`package.json` and the image builds from the `hood-alerts/` directory alone. Full self-host and
|
|
251
|
+
premium-wiring guide: the `docs/` site.
|
|
282
252
|
|
|
283
|
-
##
|
|
253
|
+
## Docs site (GitHub Pages)
|
|
284
254
|
|
|
285
|
-
|
|
255
|
+
`docs/` is a static, hand-built site (no framework, no build step): open `docs/index.html` locally,
|
|
256
|
+
or serve it from GitHub Pages. Its landing page renders a **live** premium/discount feed client-side
|
|
257
|
+
straight from the public RPC (real Chainlink feeds vs real DEX pools, no server). One-time setup:
|
|
258
|
+
Settings -> Pages -> Deploy from a branch -> `main` -> `/docs`.
|
|
286
259
|
|
|
287
|
-
|
|
288
|
-
|---|---|---|
|
|
289
|
-
| Subscriptions | 1 | 10 |
|
|
290
|
-
| Rules per subscription | 3 | 50 |
|
|
291
|
-
| Filters per rule | 3 | 24 |
|
|
292
|
-
| Watchlist size | 5 | 500 |
|
|
293
|
-
| Alerts per hour | 30 | 2,000 |
|
|
294
|
-
| Delivery delay | 60s | none |
|
|
295
|
-
| Liquidity and reputation filters | no | yes |
|
|
296
|
-
| Event kinds | `launch`, `graduation`, `whale_trade` | all four, including the `curve_trade` firehose |
|
|
297
|
-
|
|
298
|
-
The delivery delay is enforced in the outbox (the row carries `not_before`), so it survives a restart instead of being a `setTimeout` a crash erases.
|
|
299
|
-
|
|
300
|
-
### Entitlements: what actually ships
|
|
260
|
+
## Architecture notes
|
|
301
261
|
|
|
302
|
-
|
|
262
|
+
- **Console transport is not a fallback, it's the smoke-test mode.** With no bot tokens configured
|
|
263
|
+
the engine still ingests real chain events and logs every alert/digest as structured JSON; that
|
|
264
|
+
is how a fresh self-host proves the pipeline works before touching Telegram or Discord.
|
|
265
|
+
- **The entitlement never activates before settlement.** `PremiumPaywall.activate()` calls
|
|
266
|
+
`hood402`'s verify-then-settle flow and only writes the entitlement row after the on-chain
|
|
267
|
+
transfer succeeds.
|
|
268
|
+
- **Digest buffering is in-memory by design.** A restart loses at most one pending batch, never a
|
|
269
|
+
subscription (subscriptions and the delivery log are in SQLite); `AlertEngine.stop()` flushes
|
|
270
|
+
every buffered digest before shutdown.
|
|
271
|
+
- **Auto-tracked pools expire; watched pools do not.** A NOXA/Odyssey launch or graduation starts a
|
|
272
|
+
24h rug-watch on its pool automatically; a user `watch 0x…` keeps that pool's liquidity monitor
|
|
273
|
+
running indefinitely.
|
|
303
274
|
|
|
304
|
-
|
|
305
|
-
2. **`createUsdgEntitlementProvider` is a working on-chain rail.** It reads USDG `Transfer` logs from a subscriber's linked wallet to the operator's `USDG_RECEIVER` and accrues subscription time from those real payments: each payment buys `floor(amount / price)` periods, starting from whenever the current entitlement would have lapsed, so renewing early extends rather than overwrites. No facilitator, no card processor, no third-party service. USDG is the chain's own dollar and the ledger is the chain.
|
|
275
|
+
## Repo layout
|
|
306
276
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
277
|
+
```
|
|
278
|
+
src/index.ts CLI entry: wires config, db, engine, detectors, transports, HTTP server
|
|
279
|
+
src/config.ts environment -> validated Config
|
|
280
|
+
src/server.ts Hono app: /, /healthz, /premium/status, /premium/activate
|
|
281
|
+
src/engine/ event types, dedup fingerprinting, topic parsing/matching, the
|
|
282
|
+
digest buffer, the delivery gate, and the live detector set
|
|
283
|
+
src/engine/detectors/ pure per-signal detectors (price move, premium ladder, holder
|
|
284
|
+
milestones, liquidity pull, whale classification) plus live.ts,
|
|
285
|
+
which wires them to hoodchain/hoodkit streams
|
|
286
|
+
src/db/ better-sqlite3 repositories: subscribers, entitlements, deliveries
|
|
287
|
+
src/premium/paywall.ts the hood402 x402 purchase flow
|
|
288
|
+
src/transports/ Telegram (grammY), Discord (discord.js), X (OAuth1 API v2 or self-
|
|
289
|
+
hosted xactions), and console transports
|
|
290
|
+
src/commands/commands.ts the shared, platform-neutral command router
|
|
291
|
+
src/format/cards.ts alert -> platform-neutral card -> per-platform rendering
|
|
292
|
+
tests/ vitest unit suite + tests/fixtures (real captured chain events) +
|
|
293
|
+
tests/live (opt-in on-chain reads)
|
|
294
|
+
scripts/ capture-fixtures, live-probe, grant-premium (see Development above)
|
|
295
|
+
docs/ static GitHub Pages site with a live client-side premium feed
|
|
296
|
+
Dockerfile one-container build (engine + both bots), build from robinhood/
|
|
315
297
|
```
|
|
316
298
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
## API
|
|
320
|
-
|
|
321
|
-
| Export | From | What it is |
|
|
322
|
-
|---|---|---|
|
|
323
|
-
| `createEventSources` | `hood-alerts/events` | Every source for a mainnet client. Throws on testnet, where neither launchpad exists |
|
|
324
|
-
| `createNoxaLaunchSource`, `createOdysseyLaunchSource`, `createOdysseyCurveTradeSource`, `createOdysseyGraduationSource`, `createWhaleTradeSource` | `hood-alerts/events` | The sources individually |
|
|
325
|
-
| `createPriceOracle`, `createStaticPriceOracle` | `hood-alerts/events` | USD valuation from live liquidity, or a fixed rate |
|
|
326
|
-
| `createLiquidityReader` | `hood-alerts/events` | Pool reserves in USD, cached |
|
|
327
|
-
| `createMemoryPoolRegistry` | `hood-alerts/events` | The tracked memecoin pool set |
|
|
328
|
-
| `fetchLogRange` | `hood-alerts/events` | `eth_getLogs` that bisects on the result cap and retries rate limits |
|
|
329
|
-
| `parseRule`, `safeParseRule`, `ruleSchema`, `subscriptionSchema` | `hood-alerts/rules` | Rule validation |
|
|
330
|
-
| `evaluateRule`, `matchRules` | `hood-alerts/rules` | The engine |
|
|
331
|
-
| `checkRateLimit`, `createMemoryRateLimitStore` | `hood-alerts/rules` | Per-rule rate limiting |
|
|
332
|
-
| `createRpcReputationProvider` | `hood-alerts/rules` | On-chain deployer history |
|
|
333
|
-
| `renderAlert` | `hood-alerts/notifiers` | One event rendered for both platforms |
|
|
334
|
-
| `createTelegramNotifier`, `createTelegramClient` | `hood-alerts/notifiers` | Telegram Bot API |
|
|
335
|
-
| `createDiscordWebhookNotifier`, `createDiscordBotNotifier` | `hood-alerts/notifiers` | Discord, both delivery paths |
|
|
336
|
-
| `createCaptureNotifier` | `hood-alerts/notifiers` | Records instead of sending. Backs `DRY_RUN=1` |
|
|
337
|
-
| `escapeMarkdownV2`, `escapeHtml`, `escapeDiscordMarkdown` | `hood-alerts/notifiers` | The escapers, individually testable |
|
|
338
|
-
| `createCommandRouter`, `COMMANDS` | `hood-alerts/bot` | The shared command surface |
|
|
339
|
-
| `createTelegramBot` | `hood-alerts/bot` | Long-polling front end |
|
|
340
|
-
| `createDiscordInteractionHandler`, `registerDiscordCommands`, `verifyDiscordSignature` | `hood-alerts/bot` | Slash commands and Ed25519 verification |
|
|
341
|
-
| `TIER_POLICIES`, `createEntitlementGate` | `hood-alerts/tiers` | The policy table and the one chokepoint |
|
|
342
|
-
| `createStaticEntitlementProvider`, `createUsdgEntitlementProvider` | `hood-alerts/tiers` | Who is premium |
|
|
343
|
-
| `AlertStore` | `hood-alerts/service` | SQLite state: subscriptions, outbox, cursors, pools, links |
|
|
344
|
-
| `createDispatcher` | `hood-alerts/service` | `pollOnce` and `flushOnce` |
|
|
345
|
-
| `createService`, `loadConfig` | `hood-alerts/service` | The whole thing, wired |
|
|
346
|
-
|
|
347
|
-
## Limits and caveats
|
|
348
|
-
|
|
349
|
-
- **Mainnet only.** NOXA and The Odyssey are deployed on chain 4663. Building sources against testnet throws rather than reporting an empty chain as "no launches".
|
|
350
|
-
- **Whale trades cover a tracked pool set, not every pool on the chain.** A topic-only Uniswap v3 `Swap` query overflows the public RPC's 10,000-log result cap in under 2,000 blocks, so "watch everything" is not something the endpoint can serve. The pool set is assembled from launchpad activity (NOXA pools at launch, Odyssey pools at graduation), capped by `WHALE_POOL_LIMIT` newest-first, with every subscriber's watchlist tokens pinned on top of the cap. A pool the service has never seen a launch for is not watched.
|
|
351
|
-
- **A fresh database starts `INITIAL_LOOKBACK_BLOCKS` behind the head.** Deleting the SQLite file loses subscriptions and replays that window. Mount it on a volume.
|
|
352
|
-
- **Liquidity is total pool reserves, not tradeable depth.** For concentrated Uniswap v3 positions the reserve inside the active tick can be far smaller. Read `minLiquidityUsd` as "how much is in there", not "how much I can sell into".
|
|
353
|
-
- **The rug figure is a heuristic**, defined precisely above. It is evidence, not a verdict.
|
|
354
|
-
- **Rate-limit quota is consumed at enqueue, not at send.** An alert that is queued and then dead-lettered still counted against the hour. Counting at send time would let one block's events blow through every cap before the first delivery.
|
|
355
|
-
- **The public RPC throttles bursts.** Above a few hundred tracked pools, set `ROBINHOOD_RPC_URL` to a dedicated endpoint.
|
|
356
|
-
|
|
357
|
-
## Development
|
|
299
|
+
## Notes
|
|
358
300
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
301
|
+
- **Stock Tokens.** Stock Tokens are tokenized debt securities (issuer: Robinhood Assets (Jersey)
|
|
302
|
+
Ltd) and may not be offered, sold, or delivered to US persons (additional limits: Canada, UK,
|
|
303
|
+
Switzerland). hood-alerts only ever displays price/premium data derived from public Chainlink
|
|
304
|
+
feeds and DEX pools; it never facilitates acquiring a Stock Token.
|
|
305
|
+
- Liquidity-pull alerts are an early warning, not proof of a rug: always check the pool before
|
|
306
|
+
acting on one.
|
|
307
|
+
- Not affiliated with Robinhood Markets, Inc.
|
|
366
308
|
|
|
367
309
|
## License
|
|
368
310
|
|
|
369
|
-
|
|
311
|
+
MIT. See [LICENSE](./LICENSE).
|
|
312
|
+
|
|
313
|
+
Built by [nirholas](https://x.com/nichxbt) · [three.ws](https://three.ws)
|