# hood-alerts [![license](https://img.shields.io/badge/license-MIT-93a1af?labelColor=101418)](./LICENSE) [![Run on Google Cloud](https://deploy.cloud.run/button.svg)](https://deploy.cloud.run?git_repo=https://github.com/nirholas/robinhood-chain-alert-bot) [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/nirholas/robinhood-chain-alert-bot) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/nirholas/robinhood-chain-alert-bot) The alert layer for **Robinhood Chain** (chain ID 4663): Telegram and Discord bots, plus optional X (Twitter) auto-posting, backed by one shared detection engine that watches launches, graduations, whale trades, Stock Token price moves, on-chain premium/discount arbitrage, holder milestones, and liquidity-pull rug warnings. Free tier plus x402 USDG premium. ## What it detects | Detector | Topic | Source | | --- | --- | --- | | New token launch | `launches` (`launches:noxa`, `launches:odyssey`) | NOXA (instant Uniswap v3 pool) and The Odyssey (bonding curve) | | Bonding-curve graduation | `graduations` | Odyssey curve fills and migrates to a locked Uniswap v3 pool | | Whale trades | `whales`, or per-token `token:0x…` | Odyssey curve trades and Uniswap v3 swaps, chain-wide, threshold in USD | | Stock Token price moves | `stock:SYMBOL` | Chainlink feed, rolling window, signed percent change | | 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% | | Holder milestones | per-token `token:0x…` | Blockscout `token_holders_count`, crossing 10 through 100,000 | | Liquidity-pull rug warning | `rugs` (premium tier), or per-token `token:0x…` | tracked pool's quote-side (USDG/WETH) reserves dropping sharply | Every event is deduped per fingerprint before it reaches a subscriber, routed through a premium/quiet-hours/digest gate, and rendered as a platform-native card (Telegram HTML, Discord embed, a compact X post, or a structured console log line) with links back to the chain explorer and `three.ws/markets/robinhood`. ## Quickstart (self-host) ```sh npm install npm run dev # tsx watch, console transport only if no bot tokens are set ``` With zero environment variables the engine still runs: detectors connect to the public Robinhood Chain RPC, alerts print as structured JSON log lines (the console transport), and `/healthz` comes up on `:8080`. That is the self-host smoke-test mode. Copy `.env.example` to `.env` and fill in a Telegram and/or Discord bot token to go live on those platforms; see `.env.example` for every variable, defaults, and what each detector threshold controls. ```sh npm run build # tsc -> dist/ npm start # node dist/src/index.js ``` ### Creating the bots - **Telegram**: message [@BotFather](https://t.me/BotFather), `/newbot`, paste the token into `HOOD_ALERTS_TELEGRAM_TOKEN`. The bot registers its command list on startup. - **Discord**: create an application at the [Developer Portal](https://discord.com/developers/applications), add a bot, copy the bot token into `HOOD_ALERTS_DISCORD_TOKEN` and the application id into `HOOD_ALERTS_DISCORD_APP_ID`. Slash commands register (guild-visible immediately) on startup. Leaving either token unset disables that transport; the engine and the console transport keep running regardless. ### X (Twitter) X is a broadcast-only transport: there is no inbound bot, so you cannot DM the account and get a reply the way you can with Telegram or Discord. What it auto-posts is fixed at startup by `HOOD_ALERTS_X_TOPICS` (default `launches,graduations,whales`) instead of `watch`/`unwatch` commands, parsed through the same topic resolver the bots use. Pick exactly **one** of two modes via `HOOD_ALERTS_X_MODE`; see `.env.example` for the full variable list. **`official`**: the real X API v2, `POST /2/tweets`, signed with your own developer app's OAuth1 user-context credentials. This is the normal, ToS-compliant path, and it costs money: X gates posting behind a paid API tier. Check current pricing at [developer.x.com/en/portal/products/buy](https://developer.x.com/en/portal/products/buy) before relying on it; it has changed before and can change again. Setup: create an app at the [developer portal](https://developer.x.com/en/portal), generate a consumer key/secret (`HOOD_ALERTS_X_API_KEY` / `HOOD_ALERTS_X_API_SECRET`), then generate an access token/secret with **read and write** permissions (`HOOD_ALERTS_X_ACCESS_TOKEN` / `HOOD_ALERTS_X_ACCESS_SECRET`). All four are required; hood-alerts signs every request itself with Node's built-in `crypto` (HMAC-SHA1 per RFC 5849), no extra dependency. **`xactions`**: posts through a self-hosted [XActions](https://github.com/nirholas/XActions) instance instead of the official API: browser-automation driving your own X session cookie. Free, no developer account, no paid tier. This is **not** the official API and carries real ToS risk; run it on an account you are prepared to lose if X acts on that. Point `HOOD_ALERTS_XACTIONS_URL` at your running instance and `HOOD_ALERTS_XACTIONS_TOKEN` at its bearer token. Posting is async/job-queued: hood-alerts treats a 2xx from `POST {url}/api/posting/tweet` as "accepted", not "delivered", and logs that delivery is not confirmed synchronously (the real xactions API responds `{ operationId, status: "queued" }`, not a tweet id). Both modes fail loudly: a configured mode missing any required credential logs a warning and disables the X transport (Telegram, Discord, and the console transport keep running) instead of crashing the process. Every X post is compacted to fit X's 280-character limit: title, the single most important line, and the first link; only the text portion truncates, never the URL. Digests (too many alerts to post individually) post the single most significant event plus a `(+N more)` count, and log the full summarized batch, so nothing is silently dropped from the record even though it is dropped from the post itself. ## Bot commands Both bots share one command router (`src/commands/commands.ts`), so the UX is identical modulo native slash-command vs `/command arg` syntax: ``` watch [threshold] launches every new token (add noxa/odyssey to filter) graduations Odyssey curves migrating to Uniswap v3 whales [usd] trades >= usd, chain-wide (default 5000) premiums [pct] Stock Token DEX premium/discount vs Chainlink (premium tier) rugs [pct] liquidity-pull rug warnings (premium tier) a Stock Token: price moves + premium crossings (e.g. TSLA) <0x address> a memecoin: whale trades, graduation, holders, liquidity unwatch stop watching list your subscriptions threshold change a subscription's threshold digest on|off [min] batch alerts into digests (default 60 min) quiet quiet hours in UTC (e.g. quiet 22 7); quiet off premium premium status + how to upgrade help this message ``` In a group chat or server, `watch`, `unwatch`, `threshold`, `digest`, and `quiet` are restricted to admins; `list`, `premium`, and `help` are open to everyone. ## Free vs premium | | Free | Premium | | --- | --- | --- | | Subscriptions | 3 | Unlimited | | 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 | | `premiums` (Stock Token arb signal) | Not available | Included | | `rugs` (liquidity-pull warning) | Not available | Included | | Price | Free | `HOOD_ALERTS_PREMIUM_PRICE_USDG` (default 5) USDG for `HOOD_ALERTS_PREMIUM_DAYS` (default 30) days | Premium is paid on Robinhood Chain via **x402** using `hood402` (USDG, EIP-3009 `transferWithAuthorization`, chain 4663). The bot's `premium` command hands back a `POST /premium/activate?platform=...&chat=...` URL: the first request gets a 402 challenge with payment instructions, and any x402 client (`hood402`, `x402-fetch`) that can sign a USDG authorization completes the purchase. The entitlement never activates before the transfer settles on-chain, so a signature alone buys nothing. Purchases stay disabled (`503` + setup instructions) until the instance sets `HOOD402_PAY_TO` and either `HOOD402_FACILITATOR_URL` (delegates verification and settlement, no gas key on this box) or `HOOD402_SETTLER_KEY` (this instance broadcasts the settlement itself). See `.env.example` for both modes. ## HTTP API The same process that runs the bots serves a small Hono app on `PORT` (default 8080): | Endpoint | Method | Returns | | --- | --- | --- | | `/` | GET | service metadata and endpoint list | | `/healthz` | GET | uptime, last detector event time, per-event-type counts, whether premium purchases are enabled | | `/premium/status?platform=telegram\|discord\|console\|x&chat=` | GET | `{ tier: 'free' \| 'premium', expiresAt }` for that chat | | `/premium/activate?platform=...&chat=...` | POST | the x402 purchase flow: 402 challenge, then 200 + entitlement once payment settles | ## Example: reading the alert engine's pure logic directly `src/engine/gate.ts` and `src/engine/topics.ts` have no I/O and are safe to import standalone, for example to preview how a threshold will gate before wiring a subscription: ```ts import { gate } from './src/engine/gate.js' import { resolveWatchTarget, parseTopic } from './src/engine/topics.js' const target = resolveWatchTarget('whales') // { ok: true, topic: 'whales' } if (target.ok) { const topic = parseTopic(target.topic) // { kind: 'whales' } const decision = gate( { premium: false, digest: false, quietStart: null, quietEnd: null, lastDeliveredAt: null, deliveredLastMinute: 0 }, Math.floor(Date.now() / 1000), ) console.log(topic, decision) // { kind: 'whales' } { action: 'deliver' } } ``` A threshold typed after the target (`watch whales 10000`) is parsed separately by `Commands.watch()`, not by `resolveWatchTarget` itself. The package does not ship a public library entry point today (`main`/`bin` both point at the CLI process in `src/index.ts`, which starts the bots and the HTTP server and does not export anything); importing from `src/` as above works in this checkout but is not a published API contract. ## Configuration Every variable, its default, and what it controls: [`.env.example`](.env.example). Highlights: - `HOOD_ALERTS_RPC_URL` — custom RPC; defaults to viem's public `robinhood` chain RPC. - `HOOD_ALERTS_DB` — SQLite path (subscriptions, entitlements, dedup, delivery log). `:memory:` works for tests. - `HOOD_ALERTS_WHALE_FLOOR_USD` / `HOOD_ALERTS_WHALE_DEFAULT_USD` — engine-side emission floor vs. the default per-subscription threshold. - `HOOD_ALERTS_PRICE_WINDOW_S` / `HOOD_ALERTS_PRICE_DEFAULT_PCT` — Chainlink rolling-window move detector. - `HOOD_ALERTS_PREMIUM_POLL_S` / `HOOD_ALERTS_PREMIUM_DEFAULT_PCT` — Stock Token arb poll cadence and default ladder entry. - `HOOD_ALERTS_RUG_DEFAULT_PCT` — default liquidity-pull threshold (percent of quote reserves). ## Development ```sh npm install npm run typecheck # tsc --noEmit, strict + exactOptionalPropertyTypes npm test # vitest: dedup/rate-limit gate, digest batching, topic parsing and # matching, event fingerprinting/TTL, alert card rendering, config # loading, the command router, and the subscriber/entitlement/delivery # repositories — all offline, no chain or bot credentials required npm run build # tsc -> dist/ ``` The `whale-classify` and `detectors` suites run against **real captured mainnet event streams** in `tests/fixtures/` (real Uniswap v3 swaps, Odyssey bonding-curve trades, and Chainlink prices). Refresh them any time with `npm run capture-fixtures`, which re-reads live chain 4663. `npm run test:live` and `npm run probe` exercise the live detector set against real Robinhood Chain RPC and, for `probe`, the console transport; they need real network access and are not part of the offline unit suite above. `npm run grant-premium` comps or inspects a premium entitlement directly in the database (handy for testing premium delivery before a payment rail is wired). ### Sibling packages `hoodchain`, `hoodkit`, and `hood402` are ordinary published npm dependencies, pinned by `package.json` and the lockfile, so `npm ci` from a clean clone is all this repo needs. Their sources are [`robinhood-chain-sdk`](https://github.com/nirholas/robinhood-chain-sdk), [`robinhood-chain-kit`](https://github.com/nirholas/robinhood-chain-kit), and [`robinhood-chain-x402`](https://github.com/nirholas/robinhood-chain-x402). All three declare `viem` as a peer dependency, and this repo pins `viem` to one exact version, so a single `viem` copy resolves for the whole tree and the client types line up on their own. `src/premium/paywall.ts` hands real `createWalletClient` / `createPublicClient` instances straight to `hood402`'s `PaywallEngine` with no cast, which is what makes a breaking change in that interface show up as a build failure instead of being swallowed. To develop against a local checkout of one of them, override it for your working copy only: ```sh npm install ../robinhood-chain-x402 # or ../robinhood-chain-kit, ../robinhood-chain-sdk ``` Revert with `npm ci` before committing, so the lockfile keeps pointing at the published version. ## Deploy hood-alerts is one long-running process (engine + HTTP server + both bots), so run it as a service with a warm instance, not a job. `hoodchain`/`hoodkit`/`hood402` are ordinary published npm dependencies (see `package.json`) — the Docker build needs no special context, just this directory: ```sh docker build -t hood-alerts . docker run -p 8080:8080 -v hood-alerts-data:/data hood-alerts gcloud run deploy hood-alerts \ --source . \ --region us-central1 \ --min-instances=1 --no-cpu-throttling --port=8080 \ --set-env-vars=HOOD_ALERTS_TELEGRAM_TOKEN=…,HOOD_ALERTS_DISCORD_TOKEN=…,HOOD_ALERTS_DISCORD_APP_ID=… ``` The **Run on Google Cloud** button above does this for you. Mount a volume at `/data` (the default `HOOD_ALERTS_DB` path) so subscriptions, entitlements, and the delivery log survive restarts. The container answers `GET /healthz` (confirmed: real on-chain pool discovery finishes and the server starts serving within ~10s of boot); `SIGTERM` flushes pending digests before exit. Full self-host and premium-wiring guide: the `docs/` site. ## Docs site (GitHub Pages, Cloudflare, or Vercel) `docs/` is a static, hand-built site (no framework, no build step): open `docs/index.html` locally, or serve it from any static host. Its landing page renders a **live** premium/discount feed client-side straight from the public RPC (real Chainlink feeds vs real DEX pools, no server). - **GitHub Pages** (the intended home): `Settings → Pages → Deploy from a branch → main → /docs` publishes it at . Pages is not enabled on this repo yet, so that URL is not live; until it is, read the pages from [`docs/`](./docs/index.html) in the repo or open them locally. - **Cloudflare** / **Vercel**: click the buttons above — `wrangler.json`/`vercel.json` at the repo root point both at `docs/`, no build command needed. ## Architecture notes - **Console transport is not a fallback, it's the smoke-test mode.** With no bot tokens configured the engine still ingests real chain events and logs every alert/digest as structured JSON; that is how a fresh self-host proves the pipeline works before touching Telegram or Discord. - **The entitlement never activates before settlement.** `PremiumPaywall.activate()` calls `hood402`'s verify-then-settle flow and only writes the entitlement row after the on-chain transfer succeeds. - **Digest buffering is in-memory by design.** A restart loses at most one pending batch, never a subscription (subscriptions and the delivery log are in SQLite); `AlertEngine.stop()` flushes every buffered digest before shutdown. - **Auto-tracked pools expire; watched pools do not.** A NOXA/Odyssey launch or graduation starts a 24h rug-watch on its pool automatically; a user `watch 0x…` keeps that pool's liquidity monitor running indefinitely. ## Repo layout ``` src/index.ts CLI entry: wires config, db, engine, detectors, transports, HTTP server src/config.ts environment -> validated Config src/server.ts Hono app: /, /healthz, /premium/status, /premium/activate src/engine/ event types, dedup fingerprinting, topic parsing/matching, the digest buffer, the delivery gate, and the live detector set src/engine/detectors/ pure per-signal detectors (price move, premium ladder, holder milestones, liquidity pull, whale classification) plus live.ts, which wires them to hoodchain/hoodkit streams src/db/ better-sqlite3 repositories: subscribers, entitlements, deliveries src/premium/paywall.ts the hood402 x402 purchase flow src/transports/ Telegram (grammY), Discord (discord.js), X (OAuth1 API v2 or self- hosted xactions), and console transports src/commands/commands.ts the shared, platform-neutral command router src/format/cards.ts alert -> platform-neutral card -> per-platform rendering tests/ vitest unit suite + tests/fixtures (real captured chain events) + tests/live (opt-in on-chain reads) scripts/ capture-fixtures, live-probe, grant-premium (see Development above) docs/ static GitHub Pages site with a live client-side premium feed Dockerfile one-container build (engine + both bots), build from robinhood/ ``` ## Notes - **Stock Tokens.** Stock Tokens are tokenized debt securities (issuer: Robinhood Assets (Jersey) Ltd) and may not be offered, sold, or delivered to US persons (additional limits: Canada, UK, Switzerland). hood-alerts only ever displays price/premium data derived from public Chainlink feeds and DEX pools; it never facilitates acquiring a Stock Token. - Liquidity-pull alerts are an early warning, not proof of a rug: always check the pool before acting on one. - Not affiliated with Robinhood Markets, Inc. ## License MIT. See [LICENSE](./LICENSE). Built by [nirholas](https://x.com/nichxbt) · [three.ws](https://three.ws) ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=nirholas/robinhood-chain-alert-bot&type=Date)](https://www.star-history.com/#nirholas/robinhood-chain-alert-bot&Date) ## Support the project If robinhood-chain-alert-bot saves you time, **[star it on GitHub](https://github.com/nirholas/robinhood-chain-alert-bot)**. Stars are how other developers and AI agents find the repositories worth trusting, and they cost you one click. Know someone who would use it? [Post on X](https://twitter.com/intent/tweet?text=robinhood-chain-alert-bot%3A%20Real-time%20alert%20bot%20for%20Robinhood%20Chain%20-%20Telegram%2C%20Discord%2C%20and%20X%20(Twitter)&url=https%3A%2F%2Fgithub.com%2Fnirholas%2Frobinhood-chain-alert-bot) · [Share on Bluesky](https://bsky.app/intent/compose?text=robinhood-chain-alert-bot%3A%20Real-time%20alert%20bot%20for%20Robinhood%20Chain%20-%20Telegram%2C%20Discord%2C%20and%20X%20(Twitter)%20https%3A%2F%2Fgithub.com%2Fnirholas%2Frobinhood-chain-alert-bot) · [Share on LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fgithub.com%2Fnirholas%2Frobinhood-chain-alert-bot) · [Submit to Hacker News](https://news.ycombinator.com/submitlink?u=https%3A%2F%2Fgithub.com%2Fnirholas%2Frobinhood-chain-alert-bot&t=robinhood-chain-alert-bot%3A%20Real-time%20alert%20bot%20for%20Robinhood%20Chain%20-%20Telegram%2C%20Discord%2C%20and%20X%20(Twitter)) · [Share on Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fgithub.com%2Fnirholas%2Frobinhood-chain-alert-bot&title=robinhood-chain-alert-bot%3A%20Real-time%20alert%20bot%20for%20Robinhood%20Chain%20-%20Telegram%2C%20Discord%2C%20and%20X%20(Twitter)) ## Built for AI agents too Coding agents and LLM tooling can read this repo directly: [AGENTS.md](./AGENTS.md), [llms.txt](./llms.txt), [llms-full.txt](./llms-full.txt). Point an agent at `https://github.com/nirholas/robinhood-chain-alert-bot` and it has the context it needs. ## More from the same author - [All repositories by nirholas](https://github.com/nirholas/nirholas#readme): the full catalog, grouped by topic - [three.ws](https://three.ws): the platform for 3D AI agents with Solana wallets, a skill marketplace and x402 payments - Questions or ideas: [open an issue](https://github.com/nirholas/robinhood-chain-alert-bot/issues) or [start a discussion](https://github.com/nirholas/robinhood-chain-alert-bot/discussions) ## Contributors [![Contributors](https://contrib.rocks/image?repo=nirholas/robinhood-chain-alert-bot)](https://github.com/nirholas/robinhood-chain-alert-bot/graphs/contributors)