Bellwatch AI — The Market Bell
Your agent for the moments that matter.
Bellwatch monitors supported assets on Robinhood Chain, tracks market sessions, price freshness and liquidity conditions, and explains transactions before you sign. Set your rules. Follow the evidence. Stay in control.
Bellwatch AI is an independent project. It is not affiliated with, endorsed by or an official product of Robinhood. It is not a trading strategy, not an audited security product, does not predict outcomes and does not guarantee that every risk is detected. Rules are evaluated by code; the assistant explains retrieved data and never acts on your behalf.
What it does
- Market watch — reference prices from Chainlink feeds with observation time, block and freshness; underlying market session from the NYSE calendar (time-zone and holiday aware); issuer tokenization window;
uiMultiplier for Stock Tokens; onchain pool availability (Uniswap v3 and hookless v4) and executable quotes (KyberSwap, Uniswap v3) with price impact against the reference.
- Alert rules — price above/below, feed age, market session transition and quote price impact, evaluated by a deterministic engine on a schedule, with cooldowns, de-duplication and an in-app inbox.
- Execution guardian — review a transaction hash (recorded events) or an unsigned request (decoding, identity, gas,
eth_simulateV1 balance changes) with four finding labels and an explicit list of what was checked and what was unavailable.
- Evidence-based agent — a tool-backed assistant that answers only from retrieved data and cites sources and observation times; deterministic template answers when no AI key is configured.
- Landing page — a particle bell (React Three Fiber) swinging from its top pivot with a separately moving clapper, pointer influence, a "Preview alert" built from a live observation and a static SVG fallback. The rule cards and the review card are live: three example rules evaluated right now by the real engine, and the latest successful router transaction on the chain run through the real review pipeline (
/api/showcase, cached five minutes).
- $BELLW token info — symbol, network and contract address (shown as "Soon" until published) in the Token section;
pnpm token:ca 0x… verifies the contract on chain, writes src/config/token.ts and deploys. Informational only: no sale, staking or tokenomics.
See docs/CAPABILITIES.md for the full capability matrix, docs/DATA-SOURCES.md for sources, docs/VERIFICATION.md for the latest verification run and docs/DEPLOYMENT.md for hosting.
| Feature |
Works out of the box |
Needs |
| Landing page, bell animation, docs |
yes |
— |
| Market watch, quotes, pools, wallet reads (read-only address) |
yes (public RPC, key-free APIs) |
optional RPC_UPSTREAM_URL for a dedicated provider |
| Transaction review (hash + unsigned preview) |
yes |
— |
| Guest watchlist |
yes (browser storage, labelled) |
— |
| Sign-in, persisted watchlist, rules, inbox, saved reviews, conversations |
locally yes (embedded PGlite by default) |
DATABASE_URL (PostgreSQL) + SESSION_SECRET in production |
| Background monitoring (worker active) |
no until scheduled |
CRON_SECRET + a cron hitting /api/cron/evaluate, or pnpm worker |
| AI explanations |
no (template explanations instead) |
ANTHROPIC_API_KEY |
Verified providers and networks (2026-11-09 UTC): Robinhood Chain mainnet 4663 (official RPC, publicnode, dRPC — chain id checked), Chainlink feed directory (62 feeds), issuer registry (203 Stock Tokens), Uniswap deployments registry v1.0.0 (2026-11-04), KyberSwap aggregator (chain slug robinhood), Blockscout API, Sourcify v2, DefiLlama, NYSE calendar 2026–2028. Testnet 46630 answers its chain id but publishes no feeds, venues or Stock Tokens; a testnet build exposes gas balances only. Details and the raw check table: docs/VERIFICATION.md.
Not available: transaction execution of any kind, push/e-mail delivery, RFQ venues, Rialto, Lighter, exhaustive pool enumeration, testnet prices, audits or "safe" verdicts.
Local setup
pnpm install
cp .env.example .env.local # defaults: pglite database, dev secrets, DoH pin for *.robinhood.com
pnpm dev # http://127.0.0.1:27500
Optional: pnpm worker in a second terminal runs the evaluator every 60 s against the dev server (HTTP mode, uses CRON_SECRET from .env.local); pnpm worker -- --once runs one pass.
Checks:
pnpm typecheck && pnpm lint && pnpm test # tsc, eslint (React Compiler rules), vitest
pnpm verify:integrations # live registry verification → docs/VERIFICATION.md
pnpm build && pnpm start # production build on http://127.0.0.1:27510
node scripts/smoke.mjs # HTTP smoke + auth/ownership checks (SMOKE_BASE=…, CRON_SECRET=…)
node scripts/flow.mjs --base=http://127.0.0.1:27510 # browser flow with a stub wallet (nothing is broadcast)
node scripts/shot.mjs http://127.0.0.1:27510/ shots/landing.png --bell # bell motion check + screenshot
Results of the checks performed for this release are in docs/TEST-RESULTS.md.
Layout
src/config network, asset registry (generated from the sources), integrations, site copy
src/lib/util decimal (bigint) arithmetic, errors, retry, caches, allow-listed fetch, time zones
src/lib/chain RPC client with chain-id gate and Multicall3 waves
src/lib/market prices (Chainlink), sessions (NYSE calendar + tokenization window), pools, quotes, snapshot
src/lib/rules rule types + deterministic engine
src/lib/monitor evaluator tick + worker status
src/lib/review calldata decoders, findings, identity (Sourcify), preview (eth_simulateV1), history
src/lib/agent tools, Anthropic runner, template answers
src/lib/auth SIWE + sessions
src/lib/db Drizzle schema + client (postgres / pglite / memory); migrations in drizzle/
src/app/api route handlers (health, status, rpc relay, registry, market, quote, wallet, auth, watchlist,
rules, notifications, review, agent, cron)
src/components landing (bell, sections), app (shell, tables, review view), ui primitives
scripts brand, sync-docs, worker, migrate, verify-integrations, shot, smoke, flow, stub wallet
tests vitest: decimal, engine, sessions, decode/findings, tool schemas, evaluator (PGlite)
docs capabilities, data sources, deployment, privacy, verification, test results
Security notes
- Server fetches go only to allow-listed providers; user-supplied URLs are never fetched.
- Sign-in is SIWE with single-use nonces, domain + chain binding, expiry and server-side sessions; every private table is owner-scoped.
- The RPC relay allows read methods only; the app never signs, sends or schedules transactions; seed phrases and private keys are never requested.
- Public routes are rate-limited per IP; the cron endpoint requires
CRON_SECRET.
- Tool results given to the assistant are data, never instructions; the assistant cannot modify rules.
Scripts
| Script |
Purpose |
pnpm dev / pnpm build / pnpm start |
Next.js (27500 dev, 27510 prod) |
pnpm check |
typecheck + lint + tests |
pnpm worker [-- --once] [-- --mode=direct] |
background evaluator |
pnpm db:migrate |
apply migrations to DATABASE_URL |
pnpm db:generate |
regenerate SQL from the schema (drizzle-kit) |
pnpm verify:integrations |
live verification → docs/VERIFICATION.md |
pnpm brand |
rebuild icons/OG image from assets/bellwatch-logo.png |
pnpm smoke / pnpm flow / pnpm shot |
HTTP smoke, browser flow, screenshots |
pnpm token:ca 0x… [--no-deploy] [--check] / pnpm token:ca --clear |
verify the $BELLW contract on chain 4663, write the config, deploy |
pnpm ship |
production deploy (vercel --prod, team flow3515) |
Supported capabilities
What Bellwatch does today, by network, and what each capability needs. "Unavailable" means the feature is shown as unavailable in the product rather than replaced by placeholder data.
Networks
| Network |
Chain id |
Status |
Notes |
| Robinhood Chain |
4663 |
Supported |
Reference prices, sessions, pools, quotes, review, monitoring. |
| Robinhood Chain Testnet |
46630 |
Read-only gas balance |
No published feed directory, no Uniswap registry record, no issuer deployments, no aggregator support. Every dependent feature reports "unsupported". |
One build targets one network (NEXT_PUBLIC_NETWORK). Mainnet and testnet data never mix. The chain id reported by every RPC endpoint is checked before any of its data is used.
Market watch
| Capability |
Source |
Mainnet |
Needs |
| Reference USD price + observation time + block |
Chainlink AggregatorV3 proxies (62-feed Robinhood directory) |
38 registry assets (ETH, WETH, USDG, cbBTC, LINK, USDe + 32 Stock Tokens) |
RPC |
| Price freshness (feed age vs heartbeat) |
Round updatedAt vs the directory heartbeat |
Yes |
RPC |
| Underlying market session (open / closed / holiday / early close / unknown) |
Embedded NYSE calendar 2026–2028, America/New_York, DST aware |
Stock Tokens (crypto assets are "continuous") |
— |
| Issuer tokenization window |
Documented window Monday 02:00 – Saturday 02:00 Europe/Paris |
Stock Tokens |
— |
| uiMultiplier and oraclePaused flags |
Stock Token contracts (ERC-8056) |
Stock Tokens |
RPC |
| Onchain pool availability + active liquidity |
Uniswap v3 factory/pools, Uniswap v4 StateView + ReservesLens, hookless keys at four fee tiers against USDG and WETH/ETH |
Yes (not exhaustive: other venues are not enumerated) |
RPC |
| Executable quotes + price impact vs reference |
KyberSwap aggregator /routes, Uniswap v3 QuoterV2 |
Yes |
Internet / RPC |
| Feed round history |
getRoundData walked on the proxy |
Yes |
RPC |
| Recorded observation history |
Evaluator runs (30-day retention) |
When the evaluator runs |
Database + evaluator |
Reference prices are never executable quotes, and a price difference between venues is never presented as arbitrage.
Wallet and watchlist
| Capability |
Mainnet |
Notes |
| Read-only address entry |
Yes |
Stored in the browser; nothing is signed. |
| Wallet connection (injected) + network mismatch detection |
Yes |
useConnection().chainId vs the configured chain; switch/add network prompt. |
| Native balance |
Yes |
Pinned block. |
| Registry ERC-20 balances (+ share-equivalent for Stock Tokens) |
Yes |
Scope labelled "registered tokens only"; balanceOf never rebases, uiMultiplier shown. |
| Complete ERC-20 inventory |
When Blockscout answers |
Scope labelled "every ERC-20 the explorer indexed"; 429/5xx shown as unavailable. |
| Persistent watchlist |
Signed in |
Guests keep a browser-only watchlist, clearly labelled. |
| Explorer links, manual refresh, last-updated time |
Yes |
|
Alert engine
| Rule type |
Observation |
Supported assets |
| Price above / below threshold |
Chainlink reference price (stale prices are not evaluated) |
Assets with a feed |
| Feed age above threshold |
Seconds since the latest round |
Assets with a feed |
| Market session transition |
NYSE calendar |
Stock Tokens |
| Quote price impact above threshold |
KyberSwap or Uniswap v3 quote vs reference prices |
Assets with a quote adapter |
Rules store owner, network, asset, type, threshold (decimal string), unit, source, enabled flag, cooldown, last evaluation, last result, last state and last trigger time. Evaluation is deterministic (bigint decimal arithmetic), edge-detected with cooldown re-notification, and every notification is bound to the observation that produced it (duplicate-proof after retries). Missing or stale data is "unknown", never zero.
The evaluator runs from a protected cron endpoint (/api/cron/evaluate, CRON_SECRET) or the standalone pnpm worker. The UI distinguishes worker active, worker delayed, background monitoring not configured and database unavailable from recorded runs. Browser notifications are optional, permission-based and tab-bound: no push delivery is promised.
Transaction review
| Input |
What happens |
| Transaction hash |
Transaction, receipt and block fetched; calldata decoded; logs decoded (Transfer, Approval, WETH Deposit/Withdrawal, Uniswap v3/v4 Swap, Permit2 Approval); recorded balance changes of the sender from Transfer events; findings. No simulation is implied. |
| Unsigned request (chain, from, to, value, data) |
Fields validated; chain id bound to the deployment; recipient code + Sourcify identity + registry match; calldata decoded; gas estimated (with a sender balance override when needed); eth_simulateV1 with traceTransfers for estimated balance changes; findings. |
Decoders: ERC-20 (approve, transfer, transferFrom, increase/decreaseAllowance, permit), WETH9, Permit2 (approve, permit, lockdown), Uniswap SwapRouter02 (exact input/output, multicall), Universal Router (command names, v3/v2 legs, wrap/unwrap, Permit2 permit), KyberSwap MetaAggregationRouterV2 (swap, swapSimpleMode), Uniswap V2 Router02. Unknown selectors are reported as unknown.
Findings use four labels only: Information, Review recommended, High-impact permission, Unknown. There is no "safe" verdict. Each review lists what was checked, what was unavailable, the block used and whether the simulation succeeded. Nothing is signed or sent; seed phrases and private keys are never requested.
Agent assistant
With ANTHROPIC_API_KEY: a server-side tool-backed assistant (model ANTHROPIC_MODEL, default claude-opus-5-5) using listSupportedAssets, getAssetObservation, getWatchlist, getAlertHistory, getTransactionReview, getIntegrationStatus, getMarketSessions. Tools enforce the caller's scope; the model cannot change rules or execute anything; tool text is data, never instructions; refusals and tool limits are reported.
Without a key: "AI explanations unavailable" and deterministic template answers built from the same tools (stale prices, why an alert fired, what an approval allows, what a transaction recorded, session status, monitoring status, per-asset observation).
Landing page live examples and token info
- The three rule cards are example rules with fixed thresholds (NVDA > 150 USD, ETH feed age > 3,600 s, SPY sell impact > 1.5 %) evaluated at request time by the production rule engine against live observations; each shows the observed value, source, observation time and verdict, and "Unknown" when data is stale or unavailable.
- The review card is the latest successful transaction sent to a registry router (SwapRouter02, else the Universal Router), found through Blockscout and decoded by the production review pipeline; when the explorer does not answer, the card says so.
- "Preview alert" rings the bell and shows a notification built from the live NVDA observation; it states that none of the visitor's rules triggered.
- Token section:
$BELLW, network, contract address ("Soon" until published, then the verified on-chain facts). Informational only.
Not implemented / unavailable
- Transaction execution, signing or scheduling of any kind.
- Push notifications, e-mail, Telegram.
- Enumeration of every pool on the chain, RFQ venues (0x, 1inch Fusion, LiFi), Rialto propAMM, Lighter.
- Price prediction, strategies, "safe" verdicts, audits.
- Testnet prices, venues or Stock Tokens (none are published).
Data sources
Every figure in Bellwatch carries its source and observation time. This is the list of sources the server is allowed to contact (an allow-list in src/lib/util/http.ts); user-supplied URLs are never fetched.
| Source |
Used for |
Access |
Notes |
Robinhood Chain public RPC rpc.mainnet.chain.robinhood.com (+ robinhood-rpc.publicnode.com, robinhood.drpc.org as fallbacks) |
balances, feeds, pools, transactions, gas estimates, eth_simulateV1 |
key-free, rate-limited |
Chain id verified per endpoint. Set RPC_UPSTREAM_URL for a dedicated provider (Alchemy is the documented recommendation). |
| Chainlink data feeds on chain + reference-data directory |
reference prices, heartbeats, descriptions |
on-chain reads; directory is a public JSON |
Stock feeds include the corporate-action multiplier and update 24/5. |
Robinhood issuer registry api.robinhood.com/rhj/assets |
canonical Stock Token addresses, multipliers, trading capabilities |
key-free, rate-limited |
Used at verification time; the app registry is generated from it and cross-checked on chain (beacon slot). |
Robinhood Chain docs docs.robinhood.com/chain/* |
chain ids, endpoints, explorers, token semantics, tokenization window |
public |
Read at verification time. |
Uniswap developers.uniswap.org/deployments.json |
v3/v4 contract addresses for chain 4663 |
public |
v1.0.0, generated 2026-11-04. |
| Uniswap v3 factory / pools, v4 StateView / ReservesLens, QuoterV2 |
pool existence, liquidity, reserves, quotes |
on-chain reads |
Only registry pairs at standard tiers are checked. |
KyberSwap aggregator API (/robinhood/api/v1/routes) |
aggregated quotes and route price impact |
key-free, rate-limited |
Router address verified against the Sourcify exact match. |
| DefiLlama coins API |
optional cross-check of market prices |
key-free |
Never the primary reference. |
Blockscout robinhoodchain.blockscout.com/api/v2 |
complete ERC-20 inventory of an address, explorer links |
key-free (honest User-Agent) |
Slow for cold addresses; failures are reported as unavailable. |
| Sourcify v2 |
contract identity for reviews |
key-free |
404 = no verified source (reported as unverified, not unsafe). |
NYSE nyse.com/markets/hours-calendars |
holidays and early closes 2026–2028 (embedded) |
public |
Outside the covered years the session is "unknown". |
| Anthropic API |
explanation layer |
requires ANTHROPIC_API_KEY |
Optional. |
Observation records carry value, unit, chainId, source, sourceUrl, observedAt, fetchedAt, blockNumber and a status of live, stale, unavailable, unsupported or illustrative. Token amounts are stored and compared as integer base units or decimal strings; no floating point is used for balances or thresholds.
Verification
Generated by pnpm verify:integrations on 2026-11-09T01:05:14.990Z (UTC) against Robinhood Chain (chain 4663), head block 109661810.
Result: 72 ok, 0 warnings, 0 failures. Warnings are unreachable sources from the machine that ran the check (for example DNS-blocked hosts); failures are mismatches between the registry and the sources and must be fixed before shipping.
| Group |
Item |
Result |
Detail |
| network |
rpc.mainnet.chain.robinhood.com |
ok |
eth_chainId → 4663 |
| network |
robinhood-rpc.publicnode.com |
ok |
eth_chainId → 4663 |
| network |
robinhood.drpc.org |
ok |
eth_chainId → 4663 |
| network |
testnet rpc |
ok |
eth_chainId → 46630 |
| network |
head block |
ok |
109661810 |
| simulation |
rpc.mainnet.chain.robinhood.com |
ok |
eth_simulateV1 status 0x1, traced logs 1 |
| simulation |
robinhood-rpc.publicnode.com |
ok |
eth_simulateV1 status 0x1, traced logs 1 |
| simulation |
robinhood.drpc.org |
ok |
eth_simulateV1 status 0x1, traced logs 1 |
| docs |
connecting |
ok |
HTTP 200 |
| docs |
stock-tokens |
ok |
HTTP 200 |
| docs |
oracles-and-price-feeds |
ok |
HTTP 200 |
| docs |
contracts |
ok |
HTTP 200 |
| chainlink |
directory |
ok |
62 feeds |
| issuer |
rhj/assets |
ok |
203 assets |
| asset |
ETH (eth) |
ok |
native / 18 / feed ETH / USD = 214167961485 @ 2026-11-09T00:33:22.000Z |
| asset |
WETH (weth) |
ok |
WETH / 18 / feed ETH / USD = 214167961485 @ 2026-11-09T00:33:22.000Z |
| asset |
USDG (usdg) |
ok |
USDG / 6 / feed USDG / USD = 99995842 @ 2026-11-08T15:51:29.000Z |
| asset |
cbBTC (cbbtc) |
ok |
cbBTC / 8 / feed CBBTC / USD = 7514783709400 @ 2026-11-09T00:51:29.000Z |
| asset |
LINK (link) |
ok |
LINK / 18 / feed LINK / USD = 1052909524 @ 2026-11-09T00:50:33.000Z |
| asset |
USDe (usde) |
ok |
USDe / 18 / feed USDE / USD = 99897549 @ 2026-11-09T00:29:35.000Z |
| asset |
NVDA (stock:NVDA) |
ok |
NVDA / 18 / uiMultiplier 1000775159164630595 / feed RHNVDA / USD = 18195730162 @ 2026-11-09T01:03:54.000Z |
| asset |
AAPL (stock:AAPL) |
ok |
AAPL / 18 / uiMultiplier 1000566080061092436 / feed Robinhood AAPL / USD = 34422286647 @ 2026-11-09T01:00:27.000Z |
| asset |
TSLA (stock:TSLA) |
ok |
TSLA / 18 / uiMultiplier 1000000000000000000 / feed RHTSLA / USD = 35827292797 @ 2026-11-09T01:00:27.000Z |
| asset |
SPY (stock:SPY) |
ok |
SPY / 18 / uiMultiplier 1001717991187472003 / feed RHSPY / USD = 73857984951 @ 2026-11-09T01:00:43.000Z |
| asset |
GOOGL (stock:GOOGL) |
ok |
GOOGL / 18 / uiMultiplier 1000193924414112587 / feed Robinhood GOOGL / USD = 28573985170 @ 2026-11-09T01:00:40.000Z |
| asset |
AMZN (stock:AMZN) |
ok |
AMZN / 18 / uiMultiplier 1000000000000000000 / feed Robinhood AMZN / USD = 26009067997 @ 2026-11-09T01:00:37.000Z |
| asset |
MSFT (stock:MSFT) |
ok |
MSFT / 18 / uiMultiplier 1000412952576205964 / feed RHMSFT / USD = 53276876758 @ 2026-11-09T01:00:26.000Z |
| asset |
META (stock:META) |
ok |
META / 18 / uiMultiplier 1000541459879451635 / feed Robinhood META / USD = 61290021902 @ 2026-11-09T01:00:17.000Z |
| asset |
QQQ (stock:QQQ) |
ok |
QQQ / 18 / uiMultiplier 1000700791241405425 / feed Robinhood QQQ / USD = 65753609790 @ 2026-11-09T01:00:36.000Z |
| asset |
AMD (stock:AMD) |
ok |
AMD / 18 / uiMultiplier 1000000000000000000 / feed RHAMD / USD = 44935616728 @ 2026-11-09T01:00:48.000Z |
| asset |
SPCX (stock:SPCX) |
ok |
SPCX / 18 / uiMultiplier 1000000000000000000 / feed Robinhood SPCX / USD = 13613264149 @ 2026-11-09T01:00:30.000Z |
| asset |
GME (stock:GME) |
ok |
GME / 18 / uiMultiplier 1000000000000000000 / feed Robinhood GME / USD = 2502815652 @ 2026-11-09T01:00:49.000Z |
| asset |
COIN (stock:COIN) |
ok |
COIN / 18 / uiMultiplier 1000000000000000000 / feed Robinhood COIN / USD = 13313465175 @ 2026-11-09T01:00:34.000Z |
| asset |
PLTR (stock:PLTR) |
ok |
PLTR / 18 / uiMultiplier 1000000000000000000 / feed Robinhood PLTR / USD = 16130643669 @ 2026-11-09T01:03:39.000Z |
| asset |
MSTR (stock:MSTR) |
ok |
MSTR / 18 / uiMultiplier 1000000000000000000 / feed Robinhood MSTR / USD = 12641193983 @ 2026-11-09T01:00:49.000Z |
| asset |
ORCL (stock:ORCL) |
ok |
ORCL / 18 / uiMultiplier 1004762916906792672 / feed Robinhood ORCL / USD = 11629380597 @ 2026-11-09T01:00:31.000Z |
| asset |
TSM (stock:TSM) |
ok |
TSM / 18 / uiMultiplier 1002728577037771211 / feed Robinhood TSM / USD = 37879171972 @ 2026-11-09T01:00:22.000Z |
| asset |
INTC (stock:INTC) |
ok |
INTC / 18 / uiMultiplier 1000000000000000000 / feed RHINTC / USD = 5606873515 @ 2026-11-09T01:02:39.000Z |
| asset |
MU (stock:MU) |
ok |
MU / 18 / uiMultiplier 1000183456682946507 / feed RHMU / USD = 68979640494 @ 2026-11-09T01:00:19.000Z |
| asset |
CRCL (stock:CRCL) |
ok |
CRCL / 18 / uiMultiplier 1000000000000000000 / feed Robinhood CRCL / USD = 6676623564 @ 2026-11-09T01:00:38.000Z |
| asset |
SLV (stock:SLV) |
ok |
SLV / 18 / uiMultiplier 1000000000000000000 / feed Robinhood SLV / USD = 4779264023 @ 2026-11-09T01:00:33.000Z |
| asset |
USO (stock:USO) |
ok |
USO / 18 / uiMultiplier 1000000000000000000 / feed RHUSO / USD = 14835172539 @ 2026-11-09T01:00:49.000Z |
| asset |
IONQ (stock:IONQ) |
ok |
IONQ / 18 / uiMultiplier 1000000000000000000 / feed Robinhood IONQ / USD = 3205301253 @ 2026-11-09T01:00:45.000Z |
| asset |
RGTI (stock:RGTI) |
ok |
RGTI / 18 / uiMultiplier 1000000000000000000 / feed Robinhood RGTI / USD = 1168029167 @ 2026-11-09T01:04:46.000Z |
| asset |
NBIS (stock:NBIS) |
ok |
NBIS / 18 / uiMultiplier 1000000000000000000 / feed Robinhood NBIS / USD = 12892849512 @ 2026-11-09T01:00:34.000Z |
| asset |
CRWV (stock:CRWV) |
ok |
CRWV / 18 / uiMultiplier 1000000000000000000 / feed Robinhood CRWV / USD = 4610980948 @ 2026-11-09T01:00:48.000Z |
| asset |
ASML (stock:ASML) |
ok |
ASML / 18 / uiMultiplier 1000877991671177134 / feed Robinhood ASML / USD = 142242737245 @ 2026-11-09T01:00:22.000Z |
| asset |
BABA (stock:BABA) |
ok |
BABA / 18 / uiMultiplier 1000000000000000000 / feed Robinhood BABA / USD = 10924339776 @ 2026-11-09T01:04:46.000Z |
| asset |
RKLB (stock:RKLB) |
ok |
RKLB / 18 / uiMultiplier 1000000000000000000 / feed Robinhood RKLB / USD = 5345791558 @ 2026-11-09T01:00:27.000Z |
| asset |
SNDK (stock:SNDK) |
ok |
SNDK / 18 / uiMultiplier 1000000000000000000 / feed RHSNDK / USD = 82891380704 @ 2026-11-09T01:00:46.000Z |
| asset |
DELL (stock:DELL) |
ok |
DELL / 18 / uiMultiplier 1001019944360538286 / feed Robinhood DELL-USD = 32892970868 @ 2026-11-09T01:00:46.000Z |
| asset |
CLSK (stock:CLSK) |
ok |
CLSK / 18 / uiMultiplier 1000000000000000000 / feed Robinhood CLSK / USD = 610944644 @ 2026-11-09T01:00:41.000Z |
| multicall3 |
multicall3 0xcA11bde05977b3631167028862bE2a173976CA11 |
ok |
3808 bytes of code; Sourcify exact_match Multicall3 |
| chainlink |
ethUsd 0x78F3556b67E17Df817D51Ef5a990cDaF09E8d3A9 |
ok |
9571 bytes of code; Sourcify exact_match EACAggregatorProxy |
| chainlink |
usdgUsd 0x61B7e5650328764B076A108EFF5fa7282a1B9aD2 |
ok |
9571 bytes of code; Sourcify exact_match EACAggregatorProxy |
| issuer-registry |
accessControlsRegistry 0xe10b6f6B275de231345c20D14Ab812db62151b00 |
ok |
2332 bytes of code; Sourcify exact_match AccessControlsRegistry |
| uniswap-v3 |
factory 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA |
ok |
24535 bytes of code; Sourcify match UniswapV3Factory |
| uniswap-v3 |
quoterV2 0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7 |
ok |
8273 bytes of code; Sourcify match QuoterV2 |
| uniswap-v3 |
swapRouter02 0xCaf681a66D020601342297493863E78C959E5cb2 |
ok |
24497 bytes of code; Sourcify match SwapRouter02 |
| uniswap-v3 |
positionManager 0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3 |
ok |
24384 bytes of code; Sourcify match NonfungiblePositionManager |
| uniswap-v4 |
poolManager 0x8366a39CC670B4001A1121B8F6A443A643e40951 |
ok |
24009 bytes of code; Sourcify match PoolManager |
| uniswap-v4 |
stateView 0xF3334192D15450CdD385c8B70e03f9A6bD9E673b |
ok |
3531 bytes of code; Sourcify match StateView |
| uniswap-v4 |
reservesLens 0x0000001b173C3bbF3984D417d8614E3eed34865B |
ok |
14142 bytes of code; Sourcify match ReservesLens |
| uniswap-v4 |
quoter 0x8Dc178eFB8111BB0973Dd9d722ebeFF267c98F94 |
ok |
6118 bytes of code; Sourcify match V4Quoter |
| uniswap-v4 |
universalRouter 0x204FAca1764B154221e35c0d20aBb3c525710498 |
ok |
24380 bytes of code; Sourcify match UniversalRouter |
| uniswap-v4 |
permit2 0x000000000022D473030F116dDEE9F6B43aC78BA3 |
ok |
9152 bytes of code; Sourcify match Permit2 |
| kyberswap |
router 0x6131B5fae19EA4f9D964eAc0408E4408b66337b5 |
ok |
13724 bytes of code; Sourcify exact_match MetaAggregationRouterV2 |
| uniswap |
deployments.json |
ok |
v1.0.0 generated 2026-11-04T20:10:11.000Z; 53 records for 4663; missing 0 |
| kyberswap |
routes ETH→USDG 0.01 |
ok |
code 0; amountOut 21458174; router 0x6131B5fae19EA4f9D964eAc0408E4408b66337b5 |
| defillama |
prices/current |
ok |
{"robinhood:0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168":{"decimals":6,"symbol":"USDG","price":1,"timestamp":1794186342,"confidence":0.999},"robinhood:0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73":{"decimals":18,"symbol":"WETH","price":2140.940700237834,"timestamp":1794185811,"confidence":0.99}} |
| explorer |
blockscout /api/v2/stats |
ok |
HTTP 200; average block time 101 ms |
| calendar |
nyse.com hours-calendars |
ok |
HTTP 200; mentions 2026 true, 2028 true |
Sources
Not verified / unavailable
- No Chainlink L2 sequencer-uptime feed is published for Robinhood Chain; feed heartbeat staleness is the primary freshness guard.
- Testnet (46630): no feed directory, no Uniswap registry record, no issuer deployments, no KyberSwap support; the testnet build exposes gas balances only.
- RFQ venues (0x, 1inch Fusion, LiFi), the Rialto propAMM and Lighter are named in the issuer documentation but are not integrated: no verified contract record was established for them here.
Deployment
Three pieces: the web app (Next.js), a PostgreSQL database, and the scheduled evaluator (a cron that calls the protected endpoint, or the standalone worker).
1. Web app
Any Node 22+ host that runs next build / next start, or Vercel.
Environment (see .env.example):
| Variable |
Required |
Purpose |
NEXT_PUBLIC_NETWORK |
yes |
robinhood (4663) or robinhood-testnet (46630). |
NEXT_PUBLIC_SITE_URL |
yes |
Canonical URL. |
DATABASE_URL |
for sign-in, rules, history |
postgres://… in production. |
SESSION_SECRET |
production |
≥ 16 random characters; signs session cookies. |
CRON_SECRET |
for monitoring |
Shared secret of /api/cron/evaluate. |
RPC_UPSTREAM_URL |
recommended |
Dedicated JSON-RPC endpoint (Alchemy, Chainstack, QuickNode, dRPC). Chain id is verified. |
ANTHROPIC_API_KEY, ANTHROPIC_MODEL |
optional |
Explanation layer. |
MONITOR_INTERVAL_SECONDS |
optional |
Cadence the deployment promises (status compares the last run against it). Default 900. |
UPSTREAM_DOH |
dev only |
Resolve *.robinhood.com through DNS-over-HTTPS on machines with hijacked DNS. |
Build: pnpm install && pnpm build (runs scripts/sync-docs.mjs first). Start: pnpm start (port 27510) or next start.
Vercel
vercel.json declares the framework and a cron (*/15 * * * * → /api/cron/evaluate). Vercel sends Authorization: Bearer $CRON_SECRET when the CRON_SECRET environment variable is set on the project. Set every variable above in the project settings. Heavy routes declare maxDuration (60 s; the agent route 300 s, which needs Fluid compute or a Pro plan).
A 15-minute cadence keeps a free-tier managed PostgreSQL (which suspends after idle minutes) within its monthly compute allowance; a faster cadence needs a paid database or the standalone worker.
2. Database
PostgreSQL 14+. Apply migrations once per deploy:
DATABASE_URL=postgres://… pnpm db:migrate
(AUTO_MIGRATE=1 makes the app apply them on first use instead.) Locally, DATABASE_URL=pglite://./.data/pglite runs an embedded single-process PostgreSQL with no setup; memory:// is used by the tests.
Tables: users, auth_nonces, sessions, watchlist_items, alert_rules, rule_evaluations, notifications, observations, worker_runs, tx_reviews, agent_conversations, agent_messages (see drizzle/0000_init.sql).
3. Scheduled evaluator
Either:
- Cron → endpoint (serverless): a scheduler calls
POST /api/cron/evaluate with Authorization: Bearer <CRON_SECRET> every MONITOR_INTERVAL_SECONDS. One call is one bounded pass (50 s budget).
- Standalone worker (any host with Node):
pnpm worker loops every WORKER_INTERVAL_SECONDS (default 60) in HTTP mode: it calls the web app's endpoint (WORKER_TARGET_URL or NEXT_PUBLIC_SITE_URL) with CRON_SECRET, so it works with whatever database the web app owns, including the local PGlite file.
pnpm worker -- --mode=direct opens DATABASE_URL itself (PostgreSQL only; PGlite cannot be shared between processes).
pnpm worker -- --once runs one pass and exits (useful in CI or as a systemd timer).
The product shows Worker active when the last recorded run is within twice the promised interval, Worker delayed when it is older, and Background monitoring not configured when no run was ever recorded or no database is configured. A browser tab is never presented as 24/7 monitoring.
4. Verification before shipping
pnpm verify:integrations # re-checks the registry against the chain and the published sources → docs/VERIFICATION.md
pnpm check # typecheck, lint, unit tests
pnpm build && pnpm start # production server on 27510
node scripts/smoke.mjs # HTTP smoke + ownership checks (SMOKE_BASE=https://your-host for a live check; CRON_SECRET to exercise the cron)
node scripts/shot.mjs http://127.0.0.1:27510/ shots/landing.png --bell # bell motion check + screenshot
Nothing in these steps sends a transaction or deploys a contract.
Test results
Checks actually run for this release, on 2026-11-09 (UTC), from the development machine (Windows 11, Node 24.16, pnpm 9.15). Robinhood Chain hosts are DNS-hijacked on this machine's ISP, so server-side reads ran with UPSTREAM_DOH=1 (DNS-over-HTTPS pin); a deployment on a normal network needs no pin.
| Check |
Command |
Result |
| Type check |
pnpm typecheck |
clean |
| Lint (eslint 10, eslint-config-next 16.4, React Compiler rules) |
pnpm lint |
clean |
| Unit tests (vitest 5) |
pnpm test |
49 passed / 49 — decimal arithmetic, rule engine (thresholds, cooldown, dedupe keys, stale/missing data, session transitions), NYSE session + tokenization window with DST, calldata decoders + selectors + findings, strict tool schemas, evaluator on in-memory PGlite (observations, notifications once, retry de-duplication, owner scoping, worker status) |
| Integration verification |
pnpm verify:integrations |
72 ok, 0 warnings, 0 failures (chain ids on 4 endpoints, eth_simulateV1 on 3 endpoints, 4 docs pages, feed directory 62 feeds, issuer registry 203 assets, 38 registry assets: symbol/decimals/uiMultiplier/beacon slot/feed description/decimals/latestRoundData, 15 integration contracts with code + Sourcify, Uniswap registry, KyberSwap route, DefiLlama, Blockscout, NYSE calendar) — table in docs/VERIFICATION.md |
| HTTP smoke + security (dev server, port 27500) |
CRON_SECRET=… node scripts/smoke.mjs |
65 passed / 65 after the template-routing fix (first run 64/65: the guest question "status of monitoring" was routed to the alert template because ring matched monitoring; fixed with word boundaries). Covers pages, health/status, registry, market snapshot (live Chainlink prices with source/time/block; NVDA session + multiplier + tokenization window; ETH pools), quotes (KyberSwap + Uniswap v3), wallet reads with scope labels, invalid address 400, chain-mismatch preview 409, unlimited approval → high-impact, eth_simulateV1 balance change for a native transfer, unknown hash 404, SIWE for another chain 409, SIWE sign-in for two generated accounts, nonce replay 400, rule create/validate, B cannot read/modify/evaluate/delete A's rule, manual evaluation triggers, inbox evidence, no duplicate inside the cooldown, watchlists separate, agent answers with trace and no cross-user leak, cron 401 without secret and a real pass with it, worker-active afterwards, RPC relay allow-list |
| Browser flow with a stub wallet (dev server) |
node scripts/flow.mjs --base=http://127.0.0.1:27500 |
29 passed / 29 — sign-in gate, SIWE through the stub (no transaction requested), rule create → evaluate now → notification with evidence → mark read → pause/resume, watchlist add/remove, read-only balances with scope labels, unsigned preview with high-impact finding and the checked/unavailable list, chain mismatch message, agent starter question (template mode), settings table, sign-out, wrong-network prompt and switch, landing bell hook + preview toast. Screenshots: shots/flow-*.png |
| Bell motion probe (headless Chrome, SwiftShader) |
node scripts/shot.mjs <url> … --bell |
desktop: mode webgl-full, 15,100 particles, pivot world position (0, 1.02, 0), angle range −0.19…+0.19 rad (≈ ±11°), clapper range −0.23…+0.23 rad and phase-shifted from the body, frames advancing; mobile emulation: webgl-lite, 5,620 particles, same motion; --reduced-motion: static SVG (svg-reduced-motion); ?bell=svg: SVG fallback with CSS swing; preview click: ring envelope 0.041 → 0 within 2 s and the illustrative toast rendered; no horizontal overflow at 1440 and 390 px |
| Production build + start |
NEXT_DIST_DIR=.next-build pnpm build, pnpm start (27510) |
build ok (one cosmetic font warning); smoke 65/65; flow 29/29; worker pass recorded; details below |
Production build run
NEXT_DIST_DIR=.next-build pnpm build (Next 16.4.5, Turbopack): compiled successfully; one warning ("Failed to find font override values for font Big Shoulders": next/font has no size-adjust metrics for this family; cosmetic). 25 routes: landing, app pages and docs prerendered static, API routes and the asset detail page dynamic.
pnpm start on port 27510, then:
SMOKE_BASE=http://127.0.0.1:27510 CRON_SECRET=… node scripts/smoke.mjs → 65 passed / 65.
node scripts/flow.mjs --base=http://127.0.0.1:27510 → 29 passed / 29 (first production run 28/29: the flow driver clicked "Remove" and then kept looking for the button that had already disappeared; the driver now checks the expected state when a control vanishes after a click; the app was right).
pnpm worker -- --once (HTTP mode against 27510) → one evaluator pass recorded (ok: 2 rules, 0 triggered), status flipped to "Worker active"; --mode=direct with the PGlite URL refuses with the documented message; pnpm db:migrate applied the migration.
- Screenshots (
scripts/shot.mjs, SwiftShader): landing desktop (webgl-full, 15,100 particles, bell check ok), mobile (webgl-lite, 5,620 particles, bell check ok), reduced motion (svg-reduced-motion fallback), ?bell=svg (svg-no-webgl fallback), preview-alert click (toast + ring), scrolled hero, full page, every app page including the mobile watchlist, docs desktop and mobile. No horizontal overflow on any capture, no console errors. Files in shots/.
- Final clean rebuild (same code plus the docs and a hero scroll tweak): build ok, smoke 65 / 65, flow 29 / 29, bell probe ok. The local PGlite file was reset once before this run after a tooling accident on the development machine (a second server process opened the same data directory and was force-killed, after which PGlite aborted on every query); PGlite is single-process development storage, production uses PostgreSQL.
- Checks are only as good as their environment: all network reads went through the public endpoints (DoH-pinned) from one machine during a weekend session. US equities were closed and the Stock Token feeds showed weekend ages until the 24/5 window reopened at 01:00 UTC Monday; both states were observed and rendered as such.
Live deployment (2026-11-09 04:22 UTC)
- Vercel project
bellwatch (team flow3515), deployment dpl_99wF5K5ML2odBK7uP6B6hqfguzyY, READY, aliased to https://bellwatch.vercel.app (the deployment-specific URL is SSO-gated, 302, as Vercel's default). Build on Vercel: compiled with the same single font warning; Vercel chose Node 26.x because engines was a range, so engines.node is now pinned to 24.x for the next deploy.
- Database: Neon Free (
bellwatch-db, Vercel Marketplace, us-east-1) attached to production/preview/development; migrations applied from this machine over the unpooled URL before the first deploy. Cron */15 * * * * → /api/cron/evaluate registered with CRON_SECRET.
SMOKE_BASE=https://bellwatch.vercel.app CRON_SECRET=… node scripts/smoke.mjs → 65 passed / 65 (pages, live Chainlink prices, pools, quotes, wallet reads, review preview with eth_simulateV1, SIWE sign-in of two generated accounts against Neon, ownership isolation, manual evaluation + inbox, cron pass with the secret → status "Worker active"). The smoke leaves two throwaway test accounts in the database.
scripts/shot.mjs https://bellwatch.vercel.app/ --bell → webgl-full, 15,100 particles, swing −0.19…+0.19 rad from the top pivot, clapper separate; no overflow, no console errors.
- MetaMask/Blockaid rates
bellwatch.vercel.app as BLOCK / DRAINER before any wallet connected (a known false positive for fresh *.vercel.app hosts); wallet sign-in should be served from a custom domain, which can be attached without a code change.
Not exercised
- The Anthropic model path was not exercised live (no
ANTHROPIC_API_KEY on this machine); the template path was. The runner uses the SDK shapes from the official documentation (client.beta.messages.create, fallbacks: 'default', strict tools) and the strict-schema unit test guards the tool definitions; test one real reply after setting a key.
- Browser notifications were not granted in headless Chrome; the permission flow and its limits are described in the UI.
- No transaction was signed or sent anywhere; the stub wallet rejects every send and the RPC relay refuses non-read methods.
- Testnet build (
NEXT_PUBLIC_NETWORK=robinhood-testnet) was type-checked and its registry is separate, but it was not run end to end.
Privacy
- Guests (no sign-in): the watchlist, read-only address and preferences live in the browser's local storage only. Read-only lookups (balances, market data, transaction reviews, agent questions) are answered from the server but not stored.
- Signed-in users (wallet signature): the server stores the wallet address, watchlist items, alert rules, rule evaluations, notifications, saved transaction reviews and agent conversations, keyed to the address. Sessions are HMAC-signed cookies backed by a server row and expire after seven days; signing out revokes the row.
- Authentication is Sign-In with Ethereum: a single-use nonce, domain and chain binding, a ten-minute expiry and signature verification. Connecting a wallet alone grants nothing. Seed phrases and private keys are never requested; Bellwatch never signs or sends transactions.
- Outbound requests from the server go only to the allow-listed providers in
docs/DATA-SOURCES.md. User-supplied URLs are never fetched. Public routes are rate-limited per IP.
- AI: when an API key is configured, the question and the tool results needed to answer it are sent to the Anthropic API; nothing else is. Without a key nothing leaves the server.
- Retention: recorded observations are kept for 30 days; everything else until the user deletes it (rules are soft-deleted).
Bellwatch AI is an independent project and not affiliated with Robinhood.