# Robinhood Chain Alert Bot

> Real-time alert bot for Robinhood Chain - Telegram, Discord, and X (Twitter). Tracks new launches, graduations, whale trades, Stock Token premium/discount arbitrage, holder milestones, and liquidity-pull rug warnings. Free tier, x402 USDG premium.

- **Type:** MCP server
- **Install:** `agentstack add mcp-nirholas-robinhood-chain-alert-bot`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [nirholas](https://agentstack.voostack.com/s/nirholas)
- **Installs:** 0
- **Category:** [Communication](https://agentstack.voostack.com/c/communication)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [nirholas](https://github.com/nirholas)
- **Source:** https://github.com/nirholas/robinhood-chain-alert-bot
- **Website:** https://github.com/nirholas/robinhood-chain-alert-bot

## Install

```sh
agentstack add mcp-nirholas-robinhood-chain-alert-bot
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# hood-alerts

[](./LICENSE)
[](https://deploy.cloud.run?git_repo=https://github.com/nirholas/robinhood-chain-alert-bot)
[](https://deploy.workers.cloudflare.com/?url=https://github.com/nirholas/robinhood-chain-alert-bot)
[](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)
          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`](../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).

### Local sibling packages

`hood402`, `hoodkit`, and `hoodchain` are consumed as `file:` dependencies of the other Robinhood
Chain repos in this workspace (`../hood402`, `../hoodkit`, `../robinhood-chain-sdk`) rather than
their published npm versions, so a change to any of them is picked up on the next `npm install`
here. A `file:` link keeps each sibling's own `viem` copy, so a patch-level skew (e.g. `2.55.2`
here vs `2.55.1` in `hood402`) makes viem's structural `Chain` / `WalletClient` types diverge across
the two copies. `src/premium/paywall.ts` bridges that boundary explicitly (casting the local viem
clients to `hood402`'s `HoodBroadcaster` / `HoodConfirmer`), so the build stays green across a patch
skew; the runtime objects are the same correct viem clients. Once the siblings are on npm and a
single `viem` resolves, the cast is a no-op.

## 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** (default, canonical home): `Settings → Pages → Deploy from a branch → main →
  /docs` → .
- **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

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [nirholas](https://github.com/nirholas)
- **Source:** [nirholas/robinhood-chain-alert-bot](https://github.com/nirholas/robinhood-chain-alert-bot)
- **License:** MIT
- **Homepage:** https://github.com/nirholas/robinhood-chain-alert-bot

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-nirholas-robinhood-chain-alert-bot
- Seller: https://agentstack.voostack.com/s/nirholas
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
