How FloraStocks works
Specific about what is live, what is a preview, and what is not connected. Every number the product shows can be traced to a chain read at a stated block, and every model answer to the evidence it cites.
Overview
FloraStocks combines four things on Robinhood Chain: discovery of tokenized-asset pools (issuer-verified Stock Tokens against USDG and WETH on Uniswap V2/V3), executable route quotes with a full cost breakdown and an exact-call simulation, wallet-approved execution of closed cycles through a verified executor contract, and a crypto-only assistant in which two distinct models analyze, critique and synthesize over the same on-chain data the terminal reads.
There is no FloraStocks token, no partnership, no audit and no launch date implied anywhere in the product. Most of the time no route is eligible — an efficiently arbitraged market is the normal state, and the Routes page says so with the reason for every rejected candidate.
Networks
| Fact | Value | Source |
|---|---|---|
| Supported chain | Robinhood Chain · 4663 | docs.robinhood.com/chain/connecting + live eth_chainId on every status read |
| Type | Arbitrum Orbit L2, Ethereum blobs for data availability | same page |
| Gas token | ETH | same page |
| Explorer | robinhoodchain.blockscout.com | same page |
| Public RPC | https://rpc.mainnet.chain.robinhood.com | listed as rate-limited and not for production; a provider endpoint goes in FLORASTOCKS_RPC_URL (server-side) |
| Testnet | 46630 — read-only, not used | Uniswap lists no deployment there, so nothing can be verified |
| Other chains | Not supported | A wallet on any other chain is asked to switch; nothing is quoted or executed cross-chain (a cycle across two chains cannot be atomic). |
Blocks arrive about every 0.1 s. Every payload carries the block it was read at; the UI shows block, timestamp and age next to the data.
Assets and pools
Asset identity. The asset list is the issuer’s Stock Token registry (https://api.robinhood.com/rhj/assets) — 195 tokens at block 71,231,041. A ticker proves nothing (the chain carries look-alikes), so each address must also be an ERC-1967 beacon proxy of the issuer’s beacon 0xe10b6f6B275de231345c20D14Ab812db62151b00 with matching symbol()/decimals(). Two pools are only compared when they hold the same verified contract; a different wrapper or issuer with the same ticker is never treated as interchangeable. Settlement assets: USDG 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 (6 dp) and WETH 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 (18 dp).
Pools. factory.getPool for every Stock Token × {USDG, WETH} × fee tier (0.01%, 0.05%, 0.3%, 1%), factory.getPair for V2, plus WETH/USDG connectors — asking the canonical factory is the membership proof. Stock/stock pools come from a bounded PoolCreated log scan; every entry is re-validated with getPool at run time.
Liquidity. “Depth +1 %” is the quote-token input that moves the price 1 % at constant liquidity — an indicator, not an executable size. Every pool above the dust floor (25 USDG) is probed with a real quote of up to 1000 USDG that must fill within 3% impact beyond its fee. Pools that fail are shown as hollow and excluded from spreads and routing. Depth is only claimed where it was measured this way.
Uniswap v4. v4 pools between verified assets are listed from a PoolManager census as an unsupported venue. They are never priced or routed: Uniswap v4 adapter not implemented: hooks can change fees and pricing, and the executor cannot settle v4 swaps.
Prices and quotes
- All pool state in a comparison is read in one multicall pinned to one block; the block travels with the data to the screen.
- Spot references (Markets page) come from pool state — V3 from
sqrtPriceX96, V2 from reserves — normalised for each token’s decimals and shown in the pool’s quote asset, with a USDG conversion through the deepest verified WETH/USDG pool at the same block. They are before price impact: not an exchange price, not an oracle, not executable. - Executable quotes (Routes page) come from QuoterV2 (0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7) hop by hop — each an isolated, gas-capped
eth_callat the newest block — and from the constant-product formula on same-block reserves for V2 hops. Pool fees and price impact are inside them. - The raw spread — dearest vs cheapest verified pool of an asset — is a signal, never a result. A route is called eligible only when a quoted cycle is positive after protocol fee and gas.
- Candidate cycles (2–4 distinct pools, starting and ending in the settlement asset) are ranked by spot edge; the best 48 are quoted at sizes of 5%, 15%, 40%, 100%, 250% of the route’s shallowest depth.
- Quotes expire after 20 s and are invalidated when the amount, route, wallet, chain or executor changes. The route panel always re-quotes at the newest block.
Fees and cost accounting
| Line | How it is obtained |
|---|---|
| Raw price spread | Product of spot rates after pool fees, minus 1. Ranking only. |
| Quoted route output | QuoterV2 / V2 formula, hop by hop. Pool fees and price impact are already inside it. |
| Pool (trading) fees | Inside the quote — never subtracted a second time. |
| Flash-liquidity fee | None separate: a Uniswap flash swap repays the first pool in the settlement asset; that pool’s swap fee (inside the quote) is the borrowing cost. |
| Executor (protocol) fee | 0% by default, of a positive result only. Immutable per deployment, capped at 10%; the live value is read from the deployed contract. |
| Gas | eth_estimateGas of the actual call in the route panel (a forge-measured model × 1.25 in the scan), times the current gas price. |
| Gas in the settlement asset | Only from a credible, same-block rate: the deepest depth-verified WETH/USDG pool (≥ 10,000 USDG of depth), vetoed if fresh Chainlink ETH/USD ÷ USDG/USD disagrees by more than 2%. Otherwise gas stays in ETH and the net result is marked incomplete. |
| Estimated net result | Quoted result − protocol fee − gas, in the settlement asset, when gas could be converted. |
| Required minimum result | max(floor, estimate × (1 − tolerance)), enforced on-chain after the protocol fee. The floor defaults to the estimated gas cost. |
| FloraStocks charges | Nothing beyond the deployed executor’s immutable protocol fee (0 by default). No subscription, no data fee, no fee on quotes or simulations. |
All token amounts are integers (bigint) in base units with the token’s own decimals; rounding is down for amounts you receive and up for costs.
Simulation and statuses
“Simulate exact call” runs execute(hops, amountIn, minProfit, deadline) in an eth_call at the latest block, from your address (a placeholder before you connect), and estimates its gas. The response echoes the exact calldata. A simulation is bound to the route, amount, minimum-profit rule, deadline window, wallet, chain and contract: changing any of them, or letting it age past 30 s, or getting within 20 s of its deadline, invalidates it. Deadlines (30–600 s) are measured in chain time.
While no executor is deployed, the same compiled bytecode is injected at a scratch address with an eth_call state override and run against live state — labelled preview. It shows what the contract would do; there is nothing to sign.
| Route status | Meaning |
|---|---|
| Awaiting quote | No executable quote for these inputs yet. |
| Quoted | Quoted on-chain at the block shown. Not simulated for these exact parameters. |
| Simulating | The exact executor call is running in an eth_call at the latest block. |
| Simulation passed | The exact call succeeded in simulation and still binds to these parameters. Not a guarantee of inclusion or profit. |
| Simulation failed | The exact call reverted in simulation. See the decoded reason. |
| Quote expired | The quote or simulation is past its lifetime or an input changed. Refresh before anything else. |
| Unsupported | The route needs a venue or pool type this app cannot quote or execute correctly. |
A successful simulation is not a guarantee: state changes about ten times a second, other traders see the same pools, and a reverted transaction still costs gas.
Execution
Reading the chain…
Gates. Execute is enabled only when the wallet is connected on chain 4663, the executor passed on-chain verification, the gas balance covers the estimate with margin, the quote is fresh and a simulation for these exact parameters is valid. The review sheet shows the contract, route, flash amount, expected result, protocol fee, gas estimate, required minimum, deadline and recipient. No token approval exists in this design: the cycle is funded by a flash swap, so you never grant an allowance and never supply tokens. Your wallet asks for confirmation; nothing is signed automatically, and no seed phrase or key is ever requested.
Atomic means one transaction. A route is called atomic only because one verified executor transaction performs the whole cycle and reverts as a unit. Sequential wallet swaps are not atomic arbitrage and are not offered.
Mechanics. The executor calls swap on the first pool; Uniswap sends the output first and calls back (uniswapV3SwapCallback / uniswapV2Call). Inside the callback the remaining hops run exact-input with the amounts actually received, the first pool is repaid, and profit is the increase of the contract’s settlement balance across the whole call. Your minimum is checked after the protocol fee and the rest is paid to you — in one transaction or not at all.
- Every hop must be the pool the immutable V2/V3 factory returns for its tokens and fee; no arbitrary call targets, no caller calldata.
- Callbacks are accepted only during an execution, only from the single pool expected next, once; the flash payload is hash-pinned; reentrancy is blocked.
- Balances already in the contract cannot subsidise a cycle or leak: payout is the measured increase and a non-increase reverts.
- Received amounts must equal what the pool reported (rejects fee-on-transfer/rebasing); partial fills revert; tokens are allow-listed; 2–4 hops; deadline; immutable fee cap.
- Owner can: allow/disallow tokens, change the fee recipient, pause new executions, sweep non-fee balances. Owner cannot: change the fee or factories, redirect a caller’s profit, take user tokens, or upgrade (not a proxy).
Tested with unit tests on real Uniswap V2/V3 bytecode and mainnet-fork tests on real Stock Token pools (counts in the repository README). Not independently audited. Where no verified executor is configured, quoting and the preview simulation keep working and execution is disabled with that reason; the owner console at /deploy deploys the contract from the owner’s own wallet.
Activity and receipts
Before your wallet opens, the call is re-quoted and simulated again on the server and from your browser; the wallet receives exactly those bytes. States are driven only by real events: re-simulated → awaiting wallet → submitted → pending → confirmed or reverted (plus rejected, replaced and cancelled). Success exists only after a receipt with status success; output is decoded from its CycleExecuted event, gas from the receipt.
Execution rows come from CycleExecuted events and receipts, each with a confirmation state from the node’s block tags — included on L2, batch posted to L1 (safe), or finalized on L1 — and the last 256 blocks are re-scanned on every pass so a reorganised execution disappears. Output and gas are shown separately unless both are in ETH (WETH settlement); no historical conversion is claimed. Your wallet’s recent transactions, including reverted calls (which emit no event), are read from the explorer’s API in your browser and labelled as such. CSV export writes exact base units.
Model workflow
Reading the intelligence configuration…
Two distinct configured models work on every question. Model A plans which evidence is needed, analyzes it and writes the answer; Model B receives the question, the evidence and the initial answer and looks for unsupported claims, stale data, missing risks, calculation errors and unstated uncertainty; Model A then incorporates the critique, keeping unresolved disagreements visible instead of forcing agreement. If the reviewer fails or times out, the answer says the review did not complete — a single-model answer is never labelled as reviewed. Two models are not proof of anything; agreement is not evidence.
- Checking scope. A cheap rule prefilter, then model A, decide whether the question is crypto (on-chain liquidity, tokens, contracts, wallets, routes, transactions). Off-topic questions get a short redirect and no tool runs.
- Fetching evidence. Model A plans at most 8 read-only tool calls from a fixed allowlist, in at most 2 rounds. Unknown tools and invalid arguments are dropped and reported. Calls run concurrently with their own timeouts; every result — including failures and unavailable sources — becomes a numbered evidence item (E1, E2, …). Context attached from the terminal is evidence too.
- Analyzing. Model A answers from the evidence with findings typed as observed, calculated, interpretation or unverified, each citing evidence ids. Citations to ids that do not exist are stripped and the finding becomes unverified.
- Reviewing. Model B — a different model — critiques the analysis against the same evidence: unsupported claims, stale data, missing risks, calculation errors, overstated certainty, scope. This stage only completes when model B has actually returned; if it fails or times out, the answer is delivered as unreviewed and says so.
- Preparing answer. Model A folds the critique into the final answer. Points it keeps despite the reviewer, and reviewer points it rejects, stay visible as disagreements.
The models never read the chain themselves. The server runs allow-listed, read-only tools with strict schemas and per-call timeouts, and the answer can cite only evidence those tools returned. Every retrieved string — token names, contract metadata, explorer records — is untrusted data that can never override instructions or authorize anything; there is no code execution, no open URL fetching and no tool that can sign or send a transaction.
| Tool | What it reads | Source · limits |
|---|---|---|
token_lookup | Registry entry, on-chain name/symbol/decimals/totalSupply, and whether the ERC-1967 beacon slot points at the issuer beacon (issuer-verified Stock Token). Ticker matches are never identity. | RPC multicall + verified registry · One token per call. |
token_metadata | Bytecode size, ERC-1967 implementation/beacon/admin slots and the beacon implementation. | RPC · ERC-1967 slots only; other proxy patterns are not detected. |
pool_lookup | Verified pools for an asset or one pool by address: venue, fee, pair, spot price and denomination, +1 % depth, probe status, routing eligibility. | Engine priced snapshot (factory-listed pools, one block) · Up to 12 pools per call, sorted by depth. |
market_overview | Counts, widest raw spot spreads, deepest markets, ETH→USDG reference rate with its Chainlink cross-check, gas price, Uniswap v4 census. | Engine priced snapshot · Top 8 spreads and 6 markets. |
wallet_balances | ETH plus every registry token held with a non-zero balance, at one block. | RPC multicall · Registry tokens only; other assets are not listed. |
wallet_recent_transactions | Recent transactions of a wallet from the explorer indexer. | Blockscout API v2 (configurable base) · Up to 25 rows; reported as unavailable when the indexer challenges or does not answer within 45 s. |
contract_code | Bytecode presence, Sourcify and explorer verification records, proxy resolution and an ABI summary naming owner-only / admin-looking functions. | Sourcify v2 + Blockscout API v2 + RPC · Describes what the code can do; never judges safety. |
route_quote_explain | Fresh quote for a route and size with the full cost breakdown (hop amounts, gross result with pool fees inside, protocol fee, gas and its valuation, net result, raw edge, verdict). | Engine quoter (QuoterV2 / V2 reserves) at the newest block · Routes of 2–4 verified hops; default size 100 USDG or 0.05 WETH. |
simulation_explain | eth_call of the exact executor call (deployed executor, or a state-override preview): success with profit and gas, or the decoded revert explained. | Engine simulation · Preview mode when no executor is deployed; nothing can be signed. |
tx_receipt | Receipt by hash with block time, gas cost, log count and the decoded CycleExecuted event when the target is the verified executor. | RPC · Token transfers inside a call are not decoded. |
holder_concentration | Top-10 holders with their share of the on-chain total supply. | Blockscout API v2 + RPC totalSupply · Unavailable when the indexer does not answer; holder lists can lag the chain. |
Every final answer separates what was observed in the data, what was calculated from it, what is the model’s interpretation, and what could not be verified, and states the block or timestamp the data is as of. No confidence percentages are invented. A contract reader never issues a “safe” verdict and does not pretend to be an audit. Tokenized-stock questions are in scope for on-chain liquidity, contracts, routes and transactions; general off-chain stock analysis is not.
- Read-only: there is no tool that signs, approves, sends or spends. The models cannot reach a wallet.
- Every tool result, context string and token name reaches the models inside tagged data blocks that the system rules declare untrusted; text that looks like an instruction inside data is treated as text.
- The models never see raw HTML and cannot fetch URLs; only the RPC, the configured indexer base and Sourcify are contacted, by the server.
- Tool outputs are bounded (arrays and strings truncated) before they reach a model or the browser.
- Budget: at most 8 tool calls in 2 planning rounds, 90 s per model call by default, 240 s for the whole workflow.
- No chain of thought is exposed and no confidence percentages are given. Findings say whether they were observed, calculated, interpreted or could not be verified.
- Two models are a second look, not proof. When the reviewer fails, the answer is delivered as unreviewed — never labelled dual-model reviewed.
- Scope: on-chain liquidity, contracts, routes and transactions are in scope; off-chain stock analysis is not.
- Conversation history stays in the browser (localStorage), bounded; nothing is stored server-side.
Configuration: ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENAI_BASE_URL, FLORA_MODEL_A, FLORA_MODEL_B, FLORA_AI_TIMEOUT_MS, FLORA_AI_RATE_LIMIT_PER_MINUTE, FLORASTOCKS_INDEXER_API_URL, FLORA_AI_MOCK. When FLORA_MODEL_A/FLORA_MODEL_B are unset and ANTHROPIC_API_KEY is set, A = anthropic/claude-sonnet-5 and B = anthropic/claude-opus-5-5 (ids from Anthropic's current model list, checked 2026-09-24; override either with the env variables when a model is renamed or retired). With no credentials the feature reports itself as not configured. Conversation history is stored in your browser only. Open the workspace at /app/intelligence.
Integrations
| Integration | Status | Source / reason |
|---|---|---|
| Uniswap V2 factory | Supported | 0x8bcEaA40B9AcdfAedF85AdF4FF01F5Ad6517937f · developers.uniswap.org/deployments.json |
| Uniswap V3 factory · QuoterV2 | Supported | 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA · 0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7 · same registry; factory() and WETH9() cross-checked |
| Multicall3 | Used for reads | 0xcA11bde05977b3631167028862bE2a173976CA11 |
| Stock Token registry + beacon | Supported | https://api.robinhood.com/rhj/assets · beacon 0xe10b6f6B275de231345c20D14Ab812db62151b00 |
| Chainlink ETH/USD, USDG/USD | Cross-check only | 0x78F3556b67E17Df817D51Ef5a990cDaF09E8d3A9 · 0x61B7e5650328764B076A108EFF5fa7282a1B9aD2 · reference-data-directory (robinhood-mainnet) |
| Chainlink sequencer-uptime feed | Not available | Recommended by Robinhood’s docs; none is listed for chain 4663. |
| Blockscout API (explorer) | Used where it answers | Wallet transactions, holders and contract records. It challenges server-side callers at times; the app then says "unavailable" rather than guessing, and reads it from your browser on the Activity page. |
| Sourcify | Used | Verified source and ABI records for chain 4663 (contract lookups by the assistant). No record means "not verified there", nothing more. |
| Uniswap v4 PoolManager | Listed, not routed | 0x8366a39CC670B4001A1121B8F6A443A643e40951 · hooks can change pricing; no v4 settlement adapter |
| Morpho Blue (flash loans) | Available, not used | Uniswap flash swaps already fund every supported cycle at no extra fee; a second funding model would add attack surface without changing results. |
| Order-book venues, aggregators | Not integrated | No verified on-chain quote interface for spot cycles; nothing is inferred from them. |
| Model providers | Configurable | Anthropic, or any OpenAI-compatible endpoint, through a server-side adapter; see Model workflow. Credentials never reach the browser. |
The full record — sources, dates, method and open dependencies — is in docs/INTEGRATIONS.md in the repository; pnpm verify:registry re-checks the typed registry (src/config/registry.ts) against its sources and the live chain.
Token
$FLORA is the FloraStocks token. Its contract address is published in exactly two places and nowhere else: the Token section of the landing page and GET /api/token. Both read the server-side setting FLORA_TOKEN_ADDRESS, which pnpm token:ca 0x… sets only after the contract has been checked on Robinhood Chain (bytecode, name, symbol, decimals, total supply). Until then every surface shows $FLORACA: …; an address quoted anywhere else is not ours.
The app does not use the token for anything. Scanning, quoting, simulation, execution and the assistant never read a $FLORA balance; there is no staking, no treasury, no yield, no fee discount, no reward, no governance and no promised value. Once live, the site shows only what the chain reports at a stated block: the contract facts and, if a canonical Uniswap V2 / V3 pool against USDG or WETH holds liquidity in range, that pool’s spot price and +1 % depth with its venue and fee tier — indicative, before the pool fee and price impact. When there is no such pool, it says so.
Limitations
- A web interface is slow next to a co-located bot. Expect opportunities to vanish before inclusion; reverts cost gas.
- The +1 % depth figure is an indicator; probes verify only up to 1000 USDG.
- Sizes come from a five-step ladder, not a continuous optimiser.
- The public RPC throttles and is not archival; the last good read stays on screen, marked stale, with the real error.
- Stock Tokens can be paused, block-listed or upgraded by their issuer; a cycle touching one then reverts.
- Only Uniswap V2/V3 closed cycles on chain 4663 are supported. No v4, no cross-chain, no order books, no sequential wallet swaps.
- Model answers are explanations of retrieved evidence, not advice. They can be wrong; the reviewer can miss things; agreement between two models is not proof. Off-chain stock analysis is out of scope.
- The executor contract is not independently audited.
Configuration
Copy .env.example to .env.local. Everything is optional; with nothing set, the app reads the public RPC, execution is disabled and Intelligence is marked unavailable. Nothing is simulated in place of a missing dependency.
| Variable | Purpose |
|---|---|
FLORASTOCKS_RPC_URL | Server-side RPC for chain 4663 (keyed provider, archive node or local fork). Never sent to the browser; the browser reads through the same-origin relay /api/rpc. |
NEXT_PUBLIC_EXECUTOR_ADDRESS · NEXT_PUBLIC_EXECUTOR_DEPLOY_BLOCK | A deployed FloraStocksExecutor. Set with pnpm executor:set after on-chain checks. Empty = execution disabled. |
ANTHROPIC_API_KEY · OPENAI_API_KEY · OPENAI_BASE_URL | Model credentials, server-side only. |
FLORA_MODEL_A · FLORA_MODEL_B | Two distinct model ids as provider/model-id (analyst and reviewer). Identical ids are refused. |
FLORA_AI_TIMEOUT_MS · FLORA_AI_RATE_LIMIT_PER_MINUTE | Per-call model timeout and questions per minute per client. |
FLORASTOCKS_INDEXER_API_URL | Blockscout-API-v2-compatible base for wallet transactions and holders (defaults to the official instance). |
FLORASTOCKS_DOH · FLORASTOCKS_ONLY_SYMBOLS · NEXT_PUBLIC_LOCAL_FORK | Developer-machine switches: DNS-over-HTTPS pinning, a reduced symbol set for local forks, the permanent LOCAL FORK banner. |
Developer interface
struct Hop { address pool; address tokenIn; address tokenOut; uint24 fee; uint8 kind; } // kind 0 = V2, 1 = V3
function execute(Hop[] calldata hops, uint256 amountIn, uint256 minProfit, uint256 deadline)
external returns (uint256 userProfit, uint256 protocolFee);
event CycleExecuted(address indexed caller, address indexed settlementToken, uint256 amountIn,
uint256 grossProfit, uint256 userProfit, uint256 protocolFee, address[] pools);| Endpoint | Returns |
|---|---|
GET /api/markets | Pools, prices, depth, probes, reference rate and v4 summary at one block. |
GET /api/pools/v4 | Uniswap v4 pools between verified assets (listed, unsupported), paginated. |
GET /api/routes?settlement=USDG|WETH&maxHops=2..4&token=0x… | Quoted cycles with accounting, rejection reasons and excluded pools. |
POST /api/quote | Fresh quote for one route; with simulate: true, the exact-call simulation and its calldata. |
GET /api/activity?caller=0x…&page=1&limit=25 | Receipt-derived execution history with confirmation state. |
GET /api/status | Network, finality tags, adapters, freshness, registry provenance, executor checks. |
GET /api/intelligence/status | Whether two distinct models are configured, their labels, and the reason when not. |
POST /api/intelligence/ask | The two-model workflow as a stream of real events (scope, evidence, analysis, review, synthesis) ending in a structured answer. |
GET /api/token | $FLORA: "soon", or the contract address with name, symbol, decimals, total supply, canonical pools and market, read at one block. |