Documentation
How it works, what it costs, what is not done.
Written to be checked. Every address links to the explorer, every method names the contract call behind it, and the limitations are listed with the same weight as the features.
01
Product overview
TonStock is an on-chain platform for discovering and executing arbitrage opportunities across tokenized-stock liquidity pools on Robinhood Chain. The name is a brand; it has no relation to the TON blockchain.
The workflow is one line: discover pools → compare prices → find routes → simulate execution → execute eligible cycles → show the verified transaction result. Each step is a real contract read or a real transaction; where a step is not available on a network, the interface says so and disables it.
- Scanner. Pools of verified Stock Tokens on the canonical Uniswap V2/V3 factories, read at a single block.
- Route engine. Closed cycles of 2–4 distinct pools, quoted through the on-chain QuoterV2.
- Simulation. The actual
execute()call, run in aneth_callbefore anything is signed. - Executor. A flash-swap contract that completes a cycle atomically or reverts.
- Observatory and Activity. Indicative pool pricing with TWAP where available, and a log built only from executor events and receipts.
TonStock does not promise profit, is not risk-free, is not affiliated with Robinhood Markets or Uniswap Labs, and its contract has not been independently audited.
02
Supported network
| Mainnet | Testnet | |
|---|---|---|
| Name | Robinhood Chain | Robinhood Chain Testnet |
| Chain ID | 4663 | 46630 |
| Gas asset | ETH | ETH |
| Public RPC | https://rpc.mainnet.chain.robinhood.com | https://rpc.testnet.chain.robinhood.com |
| Explorer | https://robinhoodchain.blockscout.com | https://explorer.testnet.chain.robinhood.com |
| Verified DEX deployment | Uniswap V2 + V3 (official registry) | None listed |
| Stock Token registry | Yes | No deployments listed |
| TonStock status | Scanner, quotes, simulation. Execution when an executor is deployed. | Read-only. Nothing can be verified, so nothing is enabled. |
Network facts come from docs.robinhood.com/chain/connecting and are confirmed with a live eth_chainId. This build is running against Robinhood Chain (4663). Blocks arrive roughly every 0.1 s; that figure is used only to size windows, never in accounting.
Cross-chain routes are out of scope: a cycle that spans two chains cannot be atomic, and TonStock does not pretend otherwise.
03
Pool discovery
Which tokens
The asset list is the issuer's Stock Token registry (https://api.robinhood.com/rhj/assets): 194 tokens at the last verification (block 68,065,809). A matching ticker proves nothing — the chain carries many look-alike tokens — so each address must also pass an on-chain identity check: its ERC-1967 beacon slot must hold the issuer's beacon 0xe10b6f6B275de231345c20D14Ab812db62151b00, and symbol() / decimals() must match the registry. pnpm verify:registry repeats the whole check.
Which pools
- Enumeration (live, cached 10 min).
factory.getPool(stock, quote, fee)for every Stock Token × {USDG, WETH} × every enabled fee tier (0.01%, 0.05%, 0.3%, 1%),factory.getPairfor V2, plus the WETH/USDG connector pools. Asking the canonical factory is itself the membership proof. - Factory events (snapshot). Pools between two stocks cannot be enumerated cheaply, so
PoolCreatedlogs are scanned offline in bounded block ranges with backoff, filtered to pools whose two tokens are both supported. Every snapshot entry is re-validated withfactory.getPoolat run time; a mismatch drops it.
Nothing scans the chain on a page load. Pools against unknown tokens are ignored, and Uniswap V4 pools are not read in this version.
Hollow pools
A V3 position one tick wide reports enormous active liquidity and delivers almost nothing. Every V3 pool above the dust floor ($25 of +1 % depth) is therefore probed with the real quoter: a swap of min(claimed depth, $1000) must fill with at most 3% price impact beyond the pool fee. Pools that fail are shown, flagged hollow, and excluded from aggregates, spreads and routing.
04
Pricing methodology
All pool state in a comparison is read in a multicall pinned to one block number, and that block travels with the data to the screen.
- Spot price. V3: from
slot0.sqrtPriceX96, squared and adjusted for token order and decimals. V2: ratio of reserves. Integer arithmetic throughout; floats appear only when a number is formatted. - Normalisation. USDG-quoted prices are taken as they are. WETH-quoted prices are converted with the reference ETH rate: the spot price of the deepest verified WETH/USDG pool at the same block. If no such pool is available, WETH markets stay in WETH and are not compared with USDG markets.
- Stock/stock pools have no quote currency. They are used for routing, never for a USD price.
- Depth +1 %. Quote-token input that moves the price by 1 % while active liquidity stays constant:
virtualQuoteReserve × (√1.01 − 1). Exact for V2; for V3 it describes the active range only and ignores initialized ticks — which is why it is verified by a probe and never used as an executable amount. Raw token balances are never presented as depth. - Aggregate price.
Σ(price × depth) / Σ depthover pools that are active, above the dust floor and depth-verified. - Raw spread. (dearest − cheapest) / cheapest across those same pools. It ignores price impact and is never shown as profit.
- TWAP.
pool.observe([1800, 0])→ arithmetic-mean tick → price via the same TickMath the pool uses. If the pool's observation ring does not reach back 30 minutes the call reverts (OLD) and the TWAP is reported as unavailable; V2 pairs need two historical observations the public RPC cannot serve, so they are unavailable too. A WETH-quoted TWAP is converted with the current reference rate, which the table states.
These are indicative on-chain pool prices. They are not an official stock-market feed and not a validated oracle; do not value collateral with them.
05
Quotes and simulation
Route search
Cycles start and end in a settlement token (USDG or WETH), use 2–4 distinct pools, never enter a token twice, and only use verified pools. They are ranked by raw edge — the product of spot rates after pool fees — and the best 48 go to the quoter. Probe sizes are fractions (5%, 15%, 40%, 100%, 250%) of the route's bottleneck depth, so quoting stays cheap and relevant.
Quotes
Executable output comes from Uniswap's QuoterV2 (0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7), which runs the real swap through initialized ticks and active liquidity; V2 hops use the constant-product formula on reserves from the same block. Hops are chained and, because a route never reuses a pool, the chain is exact. Each quote is an isolated eth_call with a gas cap, so one pathological pool cannot distort another quote.
Every quote carries: source block, timestamp, expiry (20 s), route, input, expected output, gross cycle profit, protocol fee, estimated gas, the gas conversion source and the estimated net result. Pool fees are inside the quoter output and are never subtracted again. If gas cannot be valued in the settlement token, the route is not labelled net-profitable.
Simulation
Before signing, the app re-quotes and runs the actual execute() call from your account in an eth_call, then estimates its gas. 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 chain state — labelled preview. A preview proves what the contract would do at that block; it cannot be executed.
A quote is invalidated when the amount, route, account or network changes, when it expires, or when a pool on the route changes price. A simulation is an estimate: state changes about ten times a second, and other traders see the same pools.
06
Flash-swap mechanics
- Borrow. The executor calls
swapon the first pool. Uniswap pools send the output before they are paid and then call back (uniswapV3SwapCallbackoruniswapV2Call). - Swap. Inside that callback the remaining hops run as exact-input swaps using the amounts actually received, measured as balance deltas.
- Repay. The first pool receives exactly the settlement amount it is owed.
- Settle. Profit is the increase of the executor's settlement balance across the whole call. The protocol share is split off, your minimum is enforced after that share, and the rest is transferred to the caller. A
CycleExecutedevent records it.
Everything happens in one transaction or not at all. You supply no tokens and grant no allowance; you pay gas.
Safety properties
- Each hop must be the pool the immutable V2/V3 factory returns for its tokens and fee. There are no arbitrary call targets and no caller-supplied calldata.
- Callbacks are accepted only during an execution, only from the single pool expected next, and only once; the flash payload is hash-pinned. Nested callbacks from later hops are expected one at a time, which keeps reentrancy protection compatible with them.
- A pool cannot appear twice in a route (it is locked while its own swap is in flight), the settlement token may only open and close the cycle, and no token is entered twice.
- Tokens already held by the contract — accrued fees, donations — cannot subsidise a losing cycle and cannot leak to a caller: payout equals the measured increase, and a non-increase reverts.
- Every hop's received amount must equal what the pool reported, which rejects fee-on-transfer and rebasing behaviour at run time. Partial fills revert, so no intermediate token is left behind.
- Amount bounds, a 2–4 hop limit, a deadline, a token allow-list and an immutable fee cap are enforced on-chain. The recipient is always the caller.
07
Fees
| Cost | Amount | Notes |
|---|---|---|
| Protocol fee | 10% of positive cycle profit | Immutable per deployment and capped at 10% by a constant. Rounded down. Never charged on trade size. A reverted cycle pays none. The app shows the rate read from the deployed contract; until then, this proposed rate, labelled as such. |
| You keep | 90% of positive cycle profit | Transferred to the signing account inside the same transaction. |
| Pool fees | 0.01%–1% per hop (V3), 0.30% (V2) | Kept by liquidity providers. Already included in every quoted output. |
| Gas | Paid in ETH by the sender | Estimated from the actual call. A reverted transaction still costs gas. |
Your minimum profit is checked on-chain in the settlement token, after the protocol fee. The contract cannot see what you pay for gas, so this threshold alone does not guarantee a positive result after gas — set it above the gas estimate.
08
Transaction lifecycle
| State | Entered when | What you see |
|---|---|---|
| Simulating | You confirm the review. The route is re-quoted and the call simulated from your account. | A failure stops here with the decoded reason; nothing is sent. |
| Awaiting signature | The prepared request is handed to your wallet. | Rejecting it ends the flow — nothing was sent. |
| Submitted | The wallet returns a transaction hash. | Hash with an explorer link. |
| Pending | The node reports the transaction. | A speed-up switches the hash being tracked; a cancellation ends the flow. |
| Confirmed | A receipt arrives with status success. | Realised profit and fee decoded from the receipt event, plus gas paid. |
| Reverted | A receipt arrives with status reverted. | The revert reason (replayed at that block) and the gas spent. No fee is taken. |
No state is advanced by a timer. The success state exists only after a receipt confirms it.
Execute is enabled only when all of these hold: wallet connected · supported network · executor deployment verified on-chain · sufficient gas balance · fresh quote · successful simulation · valid thresholds.
09
Contract addresses
TonStockExecutor
Reading the chain…
$TONSTOCK token
The token's contract address is published in exactly two places: the Token section of this site and the project's X account (@tonstockrh). It is never written into the source code: the site reads it from server configuration, and the publishing tool refuses any address that does not have bytecode and answer like an ERC-20 on chain 4663. While the section says CA: Soon, no address has been announced. Name, symbol, decimals, supply and any Uniswap pool shown there are read live from the chain; no supply schedule, utility or price statement is made.
Third-party contracts TonStock reads or calls
| Contract | Address | Source |
|---|---|---|
| Uniswap V3 Factory | 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA | developers.uniswap.org/deployments.json |
| Uniswap V2 Factory | 0x8bcEaA40B9AcdfAedF85AdF4FF01F5Ad6517937f | developers.uniswap.org/deployments.json |
| Uniswap QuoterV2 | 0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7 | same registry; factory() and WETH9() cross-checked on-chain |
| Multicall3 | 0xcA11bde05977b3631167028862bE2a173976CA11 | bytecode presence checked on-chain |
| USDG (6 decimals) | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 | symbol() / decimals() read on-chain |
| WETH (18 decimals) | 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 | matches QuoterV2.WETH9() |
| Stock Token beacon | 0xe10b6f6B275de231345c20D14Ab812db62151b00 | issuer beacon; identity check for every Stock Token |
The typed registry with provenance lives in src/config/registry.ts; pnpm verify:registry re-checks every line against its source and the live chain.
10
Administrative permissions
The executor has an owner (two-step transferable). The owner can:
- allow or disallow route tokens and settlement tokens (
setTokenSupport,setSettlementToken); - change the address that receives protocol fees (
setFeeRecipient); - pause and unpause new executions (
setPaused) — accrued fees stay withdrawable; - recover tokens that are not accrued fees, e.g. sent by mistake (
sweep).
The owner cannot: change the protocol fee or its cap, change the factories, redirect a caller's profit (it is paid inside execute and never rests in the contract), take tokens from users (none are ever approved or deposited), or upgrade the contract — it is not a proxy. Protocol fees accrue in the contract and are pulled with withdrawFees by the fee recipient or owner, so a broken recipient can never block executions.
The Stock Tokens themselves are controlled by their issuer, who can pause, block-list, burn and upgrade them. A cycle touching a paused or block-listed token reverts.
11
Known limitations
- Competition and latency. Blocks are ~0.1 s apart and the sequencer orders transactions. A web interface is slow next to a co-located bot; expect many opportunities to be gone before inclusion. Reverts cost gas.
- Efficient markets. Most of the time no route qualifies. That is a correct result, not an outage.
- Gas estimate. Scan results use a local gas model (forge measurements × 1.25); only the workspace uses an estimate of the real call. The L1 data fee is included only in the latter.
- Depth is an indicator. The +1 % figure assumes constant liquidity; the probe verifies up to $1000 only.
- TWAP coverage depends on each pool's observation cardinality; V2 has none here.
- Uniswap V4 pools are neither read nor routed in this version; other DEXes are not integrated.
- Size search is a five-step ladder, not a continuous optimiser.
- Public RPC. It throttles and is not archival. Reads retry with backoff; when a refresh fails the last successful data stays on screen, marked stale, with the real error.
- Reverted-attempt history needs the explorer's index and is read from your browser; confirmed executions do not depend on it.
- Issuer controls. Stock Tokens can be paused, block-listed or upgraded by their issuer at any time.
12
Deployment and review status
| Item | Status |
|---|---|
| Website | Published. Every figure on it is a live chain read; the hero labels real pools. |
| Scanner, pricing, observatory | Live against mainnet data. |
| Quotes (QuoterV2) and route search | Live against mainnet data. |
| Executor simulation | Preview only (state-override eth_call of the compiled bytecode). |
| Executor contract — public deployment | Not deployed to any public network. |
| Executor contract — tests | Local suite on real Uniswap V2/V3 bytecode plus a mainnet-fork suite against real Stock Token pools. See the repository README for counts and how to run them. |
| Independent security audit | None. Passing tests are not an audit. |
| On-chain execution from this site | Disabled. |
13
Developer integration
The contract interface is small. ABI: contracts/abi/TonStockExecutor.json.
struct Hop {
address pool; // must equal factory.getPool/getPair for the hop
address tokenIn;
address tokenOut;
uint24 fee; // V3 fee tier; 0 for V2
uint8 kind; // 0 = Uniswap V2 pair, 1 = Uniswap V3 pool
}
function execute(
Hop[] calldata hops, // 2–4 hops, hops[0].tokenIn == hops[last].tokenOut
uint256 amountIn, // flash amount in the settlement token
uint256 minProfit, // caller's minimum AFTER the protocol fee
uint256 deadline // unix seconds
) external returns (uint256 userProfit, uint256 protocolFee);
event CycleExecuted(
address indexed caller, address indexed settlementToken,
uint256 amountIn, uint256 grossProfit, uint256 userProfit,
uint256 protocolFee, address[] pools
);Read protocolFeeBps(), supportedToken(address), settlementToken(address) and paused() before building a call; simulate with eth_call and decode the custom errors (CycleNotProfitable, MinProfitNotMet, PartialFill, PoolNotCanonical, …) from the ABI.
HTTP endpoints of this app
| Endpoint | Returns |
|---|---|
GET /api/markets | Pools, prices, depth, probes and counts at one block. |
GET /api/routes?settlement=USDG|WETH&token=0x… | Ranked, quoted cycles with full accounting. |
POST /api/quote | Fresh quote for one route; with simulate:true also the executor simulation. |
GET /api/observatory | Per-pool spot, aggregate, TWAP and observation age. |
GET /api/activity | CycleExecuted events with receipts. |
GET /api/status | Head block, gas price and executor verification. |
All endpoints are rate-limited and validate their input. /api/rpc is a read-only relay for the wallet flow with a method allow-list and a fixed upstream — it is not a general RPC proxy. Amounts are decimal strings in base units. See the README for setup, tests and deployment. Questions about the design are answered in the Orbit Engine walkthrough.