Docs
Mechanics, the evidence behind the three gates, the randomness scheme and its weaknesses, the security argument, the risks, and how to deploy.
Deploy
Testnet (46630) first, then mainnet (4663). Everything below runs from the repository root unless noted. Foundry 1.5 and Node 24 with pnpm are required.
0. One-time
pnpm install
cd contracts && forge build && cd ..
cp .env.example .env # then fill DEPLOYER_PRIVATE_KEY
.env keys:
| Key | Meaning |
|---|---|
DEPLOYER_PRIVATE_KEY | Broadcaster for deploy, seed and keeper. Never the timelock proposer on mainnet. |
PROPOSER | Timelock proposer. Mainnet: the multisig. Testnet: defaults to the deployer. |
TIMELOCK_DELAY | Seconds. Mainnet 172800 (48 h). Testnet 0 so the seed can execute immediately. |
DEPTH_FLOOR_USDG | 6-decimal USDG. The Gate 2 constant from EVIDENCE.md. |
REVEAL_DELAY_L2 | L2 blocks between commit and reveal, ≥ 2. Default 4. |
STALENESS_GRACE | Seconds past a feed's heartbeat before it is stale. Default 3600. |
RPC_MAINNET, RPC_TESTNET | Public endpoints by default; prefer an Alchemy URL (https://robinhood-{mainnet,testnet}.g.alchemy.com/v2/{KEY}) for the indexer and keeper. |
0b. Local replica (Anvil)
The full flow runs locally with the testnet mock set. Scrip is 29.8 KB of bytecode: legal on Robinhood Chain (96 KB limit, see Robinhood's "Differences from Ethereum") but above Ethereum's 24 KB default, so Anvil and forge script need the limit raised. The ArbSys precompile is installed as LocalArbSys (returns block.number).
anvil --chain-id 31337 --block-time 1 --code-size-limit 98304
cd contracts
PK=$(cast wallet private-key "test test test test test test test test test test test junk")
cast rpc anvil_setCode 0x0000000000000000000000000000000000000064 $(forge inspect LocalArbSys deployedBytecode)
DEPLOYER_PRIVATE_KEY=$PK TIMELOCK_DELAY=0 forge script script/Deploy.s.sol --rpc-url http://127.0.0.1:8545 --broadcast --code-size-limit 98304
DEPLOYER_PRIVATE_KEY=$PK forge script script/SeedSeries.s.sol --rpc-url http://127.0.0.1:8545 --broadcast --code-size-limit 98304
cd .. && RPC_URL=http://127.0.0.1:8545 CHAIN_ID=31337 EXHAUST_SERIES=1 DEPLOYER_PRIVATE_KEY=$PK pnpm keeper
cd indexer && CHAIN_ID=31337 PONDER_RPC_URL_31337=http://127.0.0.1:8545 DATABASE_SCHEMA=scrip_local pnpm start
cd .. && NEXT_PUBLIC_CHAIN_ID=31337 pnpm sync:deployment && cd web && NEXT_PUBLIC_CHAIN_ID=31337 NEXT_PUBLIC_RPC_URL=http://127.0.0.1:8545 pnpm dev
This exact sequence was run on 2026-09-24: 35 packs committed and revealed by the keeper against real blockhash semantics, series 1 exhausted (175 / 175), indexer and app serving it.
1. Testnet
The testnet has no USDG, no Stock Tokens, no Chainlink feeds and no Uniswap deployment (Uniswap/contracts ships no 46630.json; Robinhood's docs list no testnet token addresses). Deploy.s.sol therefore deploys MockUsdg, a MockRouter, and for eight companies a MockScaledStockToken, a MockAggregator at that company's real mainnet price on 2026-09-24, and a ThinPool holding 100 000 USDG at that price. The protocol contracts are the same bytecode as mainnet.
- Fund the deployer with testnet ETH at
https://faucet.testnet.chain.robinhood.com(the faucet sits behind a browser challenge; it must be done by a person). Deployer for this repository: seeDEPLOYER_ADDRESSin.env. A dry run ofDeploy.s.solagainst the live testnet on 2026-09-24 simulated cleanly (all mocks, timelock, registries, randomness andScrip) and estimated 37.4 M gas, about 0.00075 ETH at the testnet's gas price; the seed, exhaustion and bounty funding add a few hundred thousand gas per transaction. 0.05 testnet ETH covers everything with margin. - Deploy:
Writescd contracts forge script script/Deploy.s.sol --rpc-url robinhood_testnet --broadcast --verify --verifier blockscout --verifier-url https://explorer.testnet.chain.robinhood.com/api/packages/config/deployments.46630.json. - Seed one series (registers the eight mocks and creates a 175-slip / 35-pack series with one complete set, then funds 25 USDG of bounties):
forge script script/SeedSeries.s.sol --rpc-url robinhood_testnet --broadcast - Exhaust it so the app has a full history and a closed series on first load:
The keeper commits a pack, waits for the L2 delay and the next L1 block, reveals, and repeats untilcd .. && CHAIN_ID=46630 EXHAUST_SERIES=1 pnpm keeperSeriesSoldOut, then exits once every pack is opened. Alternatively, from Foundry only:
repeated until sold out.forge script script/ExhaustSeries.s.sol --sig "commitBatch(uint16,uint256)" 1 5 --rpc-url robinhood_testnet --broadcast sleep 30 forge script script/ExhaustSeries.s.sol --sig "revealReady(uint16)" 1 --rpc-url robinhood_testnet --broadcast - Verify (if
--verifywas not used at deploy):forge verify-contract <addr> src/Scrip.sol:Scrip --chain-id 46630 --verifier blockscout --verifier-url https://explorer.testnet.chain.robinhood.com/api/ --constructor-args $(cast abi-encode "constructor(address,address,address,address,address,address,address)" ...)
2. Mainnet
PROPOSER= the multisig,TIMELOCK_DELAY=172800,DEPTH_FLOOR_USDG= the Gate 2 constant.forge script script/Deploy.s.sol --rpc-url robinhood --broadcast --verify --verifier blockscout --verifier-url https://robinhoodchain.blockscout.com/api/pnpm build:seriesregeneratespackages/config/series.4663.jsonfrom.research/depth.json(runpnpm measure:depthfirst if the measurement is stale). It keeps tokens with a Chainlink feed and a direct USDG v2 or v3 pool above the floor, and rounds caps to whole packs. The 2026-09-24 run gives 26 companies, 990 slips and 198 packs.- Send
PROBE_AMOUNT(1e12 raw units) of each token to the timelock, thenMODE=schedule forge script script/SeedSeries.s.sol --rpc-url robinhood --broadcastfrom the proposer key. 48 hours later,MODE=executefrom any key. - Fund bounties:
Scrip.fundBounties(amount)from any address, or letSeedSeriesdo it at execute time (BOUNTY_FUND_USDG). - Run the keeper permanently:
CHAIN_ID=4663 pnpm keeperunder a supervisor. Its liveness is part of the randomness model (RANDOMNESS.md §2): alert if no reveal happens within 5 minutes of aCommittedevent.
3. Indexer
cd indexer
CHAIN_ID=46630 PONDER_RPC_URL_46630=https://robinhood-testnet.g.alchemy.com/v2/KEY pnpm dev
Reads packages/config/deployments.<chainId>.json for the start block and addresses. Endpoints are listed in README.md.
4. Web
cd web
NEXT_PUBLIC_CHAIN_ID=46630 NEXT_PUBLIC_INDEXER_URL=http://localhost:42069 pnpm dev
5. Tests
cd contracts
forge test # unit, invariant (64×64), scenario
FOUNDRY_PROFILE=deep forge test --match-path "test/invariant/*" # 800×64 = 51 200 calls
forge test --match-path "test/fork/*" # mainnet fork; needs a weekday for fresh feeds
Public demo on ripstock.fun (test chain served from one machine)
Until the contracts are on Robinhood Chain testnet (blocked on faucet ETH for the deployer), the public site runs against the local Anvil replica, exposed safely:
| Piece | Where | Role |
|---|---|---|
| Anvil, chain 31337 | this machine, :8545 | the chain, with the mock deployment |
| Ponder | this machine, :42069 | the indexer |
scripts/gateway.mjs | this machine, :8787 | filtered JSON-RPC (reads and signed raw transactions only; anvil_*, evm_*, unsigned sends refused), faucet (gas + 1,000 mock USDG, rate limited), indexer relay, chain state backup every 10 min to .research/state/anvil-state.json |
| Cloudflare quick tunnel | .research/tunnel | public HTTPS address for the gateway |
Vercel project ripstock | ripstock.fun, www redirects | the site; /chain/rpc, /chain/faucet and /ix/* are rewritten to GATEWAY_URL (see web/next.config.ts) |
Visitors connect their own wallet (MetaMask, Rabby, WalletConnect); the site adds the test chain to it (wallet_addEthereumChain, RPC = the site's own /chain/rpc) and the faucet button on the checkout funds it with gas and 1,000 mock USDG. The single live series has 700 packs (CAP_SCALE=20). Resetting the chain keeps the same contract addresses as long as the deploy sequence is identical, so no site rebuild is needed; only the indexer schema name changes (DATABASE_SCHEMA in scripts/go-live.mjs).
Bring everything back (after a reboot, a sleep, or a dead tunnel):
node scripts/go-live.mjs
It restarts whatever is down (Anvil from the saved state), opens a tunnel, and, only if the tunnel address changed,
updates GATEWAY_URL on Vercel and redeploys. The site is down whenever this machine is off or asleep.
Local production build against the same gateway: node scripts/build-web.mjs (sets the env from Node, because Git
Bash on Windows rewrites values that start with "/").


