US equities trade on Robinhood Chain as tokens that never rebase.
A split moves an on-chain multiplier and your balance does not change at all —
so every wallet reading balanceOf reports a position that is wrong.
Website · Documentation · Telegram bot · Live premiums
Two products from one data layer: a website worth linking to, and a Telegram agent that answers and acts.
1. Your balance is not your position.
Stock tokens never rebase. A split or dividend moves an on-chain uiMultiplier
while every holder's raw balance stays byte-for-byte identical:
underlyingShares = rawBalance × uiMultiplier ÷ 1e18
Any wallet, tracker or tax export reading plain balanceOf is wrong the moment a
corporate action lands. CRWD currently carries a multiplier of 4.0 — a 4:1
split already applied — so a raw balance understates real holdings by 300%.
2. Two prices for the same company.
Each token has an on-chain price set by pool liquidity and an exchange price set
by NASDAQ or NYSE. The gap is the premium, and it widens every night and weekend
because only one of the two markets ever closes. /market prices every one of them
against its real listing.
npm install
cp .env.example .env.local # the app degrades gracefully without keys
npm run dev
npm test # node --test, no test framework to installBlockscout, GeckoTerminal, DexScreener, Yahoo Finance and Nasdaq's splits calendar need no credentials at all.
| Route | Purpose |
|---|---|
/ |
The mechanism, live evidence, and how the agent works |
/docs |
Reference: purpose, every command, and what it refuses |
/market |
Premium wall — every stock token against its listing |
/explorer |
Every asset on the chain, ranked by real activity |
/radar |
Multiplier state ranked by reporting error |
/history |
Every corporate action the chain has recorded |
/exposure |
Per-address reconciliation of balanceOf vs balanceOfUI |
/alerts |
Subscribe to the bot |
/autonomous |
How execution would work, and why it is not live |
/method |
Technical method, data sources, limits |
/token/[address] |
Dual charts: on-chain candles beside the equity |
The bot is not a command menu with an LLM bolted on. It decides which tools to call, calls several when a question needs them, and acts — sending a chart, changing an alert scope — instead of telling the user which command to type.
33 tools. Read tools return live data. Act tools change only what the caller owns — their chat, their subscription — and are all reversible.
MARKET /price /chart /premium /token /news /market /splits
CHAIN /chain /flow /portfolio /delegation
MISSIONS /mission /missions /every /schedules /unschedule
WATCH /watch /watches /unwatch /settings
TRACKING /track /tracks /untrack
SESSION /status /cancel /help /stop
Or plain language: "why is NVDA above its stock price?", "show me the AAPL chart", "what actually trades on this chain?"
Every figure comes from a tool. The model has no way to invent a price.
/mission <objective> takes a goal rather than a sequence of steps. The agent
decides what to observe, observes it, decides what that means, and asks before
doing anything consequential:
OBSERVE → REASON → DECIDE → ACT → VERIFY
Nothing is stated that was not observed. Every tool result enters an evidence ledger with an id. A conclusion must cite ids that resolve; one that cites nothing, or cites an id that does not exist, is downgraded to "evidence is not sufficient" rather than published. URLs are filtered against the set some tool actually returned, so an invented source cannot survive into an answer. Confidence is dropped unless at least two observations sit behind it.
High-risk actions always ask. Tools are classified by what they can do: reads, reversible changes the caller owns, and consequential actions. The third class requires explicit approval in every permission mode, with no override — and an unclassified tool is treated as consequential, so a tool added later cannot become autonomously callable by being forgotten.
An action is not done until it is verified. After acting, an independent read confirms the world is in the state the action claimed. A tool with no verifier is reported as unverified, never as success.
Bounded three ways — iterations, tool calls, wall clock — because an agent that decides when to stop can decide not to. Identical calls are deduplicated, results are truncated, and the whole registry is never offered at once: routing cuts 33 tools to the handful an objective implies.
A mission waiting for approval is persisted, so the answer can arrive hours later in a different process.
/mission investigate why NVDA is trading above its stock price
/missions your missions, and anything awaiting approval
Also available at POST /api/mission (authorised, rate-limited).
/watch <contract address> monitors any token on this chain — not just the
tokenised stocks — and alerts on moves the user defines in /settings:
/watch 0x2e8c31162b855a2ffa90f6f8634643ad6f111e18
Price up and down as percentages from the price at the moment of watching; market cap, liquidity and 24h volume as levels. Every threshold takes a preset or a custom value, per user, through inline buttons.
One command, two meanings. /watch took a wallet address before this
existed, and still does — that scopes corporate-action alerts to the tokens a
wallet holds. The chain is asked which kind of address it is (a wallet has no
bytecode) rather than the user, so neither behaviour needs a second command.
/watch wallet 0x… and /watch token 0x… force the reading either way.
One alert per move, not one per poll. A threshold that fires disarms, and
re-arms only once the metric retreats through a band — half the threshold for a
percentage move, 5% of the level otherwise. A token that reaches +10% and keeps
climbing produces one alert; a token that round-trips and rallies again produces
two. An alertCooldown sits behind that as a second limit.
Missing data is never zero. A provider outage leaves the reading absent, and an absent metric evaluates no condition at all — otherwise the first API failure would fire every "below" alert on every watchlist at once.
Cost is bounded by tokens, not users. One centralised cycle serves every watch. A hundred people watching the same token is one market read; thirty distinct tokens is one call per provider.
| Source | Used for | Ceiling |
|---|---|---|
| Chain RPC + Multicall3 | Multiplier state, balances | Public endpoint is rate-limited |
| Blockscout | Token discovery, holders, price, logos | — |
| Codex | Aggregate liquidity, flow, OHLCV, full token list | No ranked holder list on this plan |
| DexScreener | Price, liquidity, volume, trade counts | 300 req/min, per IP. The batch endpoint takes 30 addresses but caps the reply at 30 pairs, so totals must be fetched per token |
| GeckoTerminal | Candles, pool discovery, batched prices, aggregate reserve | 30 calls/min, per IP; 30 addresses per batch; no market cap without a CoinGecko listing |
| Yahoo Finance | Underlying equity price | Unofficial; may return nothing |
| Finnhub | Profiles, analysts, earnings, session state | /stock/split withheld on free tier |
| Nasdaq | Announced splits calendar | Window-based, not an open range |
| CoinGecko | Global crypto | Demo tier; Pro endpoints rejected |
| Tavily / Exa / Firecrawl / Jina | Search, semantic search, JS rendering, fast reads | — |
| GitHub | Repo analysis, code search | 5,000 req/hour, 1,000 results/query |
Alchemy's free tier caps
eth_getLogsat a 10-block range, which cannot read a full corporate-action history. Log reads therefore use the public endpoint, which serves an unbounded range —VELTR_LOGS_RPC_URLexists for that split.
Discovery is interface-based. A contract that answers uiMultiplier() is a
stock token. No hardcoded address list, so new listings appear automatically.
Caches reject degenerate results. A throttled RPC returning an empty token set would otherwise be cached as truth for thirty minutes, emptying the product with no error anywhere.
State is mtime-invalidated. The scheduler and the HTTP handlers can run in separate worker processes; a plain memo makes request handlers serve state frozen at their own first read.
The scheduler runs in-process. Telegram long-poll, a 60-second corporate-action watcher, a 15-second token-watch tick, and a once-daily brief — no external cron. Single-instance only.
Watching starts no process. /watch appends a row; the next monitoring cycle
picks it up. So the number of timers never grows with the number of users, and a
restart resumes every watch from disk with nothing lost.
Market figures are derived, not taken. Blockscout publishes price, volume and
market cap as precomputed fields, and they do not survive checking: for NVDA it
reported $1.22M of 24h volume against $4.00M actually traded, and a market cap
that disagrees with its own supply times its own price. Price and pool totals
come from the pools themselves, summed across every pair the token is the base
of; market cap is totalSupplyUI × price. The raw supply would be wrong by
exactly the multiplier after a corporate action — the misreporting this project
exists to catch, and therefore the one place it must not appear.
A premium needs a market, not just a price. Thirty-eight tokens trade in no indexed pool; their only price is derived from the share, so comparing the two is nearly circular. Below $10,000 of pool depth a $250 trade moves the price five percent. Both are excluded, which is the difference between a dislocation and a rounding artefact with a large number attached.
Autonomous execution. The rail is built and the EIP-7702 authorisation format verified on mainnet, but no delegation has been broadcast. Veltr watches and prepares; it does not execute.
EIP-7702 rather than an ERC-4337 smart account is deliberate: a 4337 account has a new address, so users would have to migrate existing positions before anything could protect them.
The delegate is MetaMask's stateless implementation. Bytecode probing showed
ZeroDev Kernel carries an initialize() function, which leaves a window between
delegation and initialisation in which anyone could seize the account. Stateless
removes that window.
The session key, when it exists, can only reduce risk, and funds may only move to the delegating account. Under compromise the worst reachable outcome is an unwanted position close — not a drain.
node scripts/generate-keys.mjs # writes keys to .env.local, prints addresses
node scripts/delegate.mjs # dry run
node scripts/delegate.mjs --executeInformational tooling. Not investment, tax, or legal advice. Stock tokens on Robinhood Chain are debt securities, not equity — holders receive no shareholder rights.
