Install
$ agentstack add mcp-nirholas-robinhood-chain-alert-bot ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
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)
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.
npm run build # tsc -> dist/
npm start # node dist/src/index.js
Creating the bots
- Telegram: message @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, 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 before relying on it; it has changed before and can change again.
Setup: create an app at the developer 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 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:
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 publicrobinhoodchain 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
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:
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.jsonat 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.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.