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:

KeyMeaning
DEPLOYER_PRIVATE_KEYBroadcaster for deploy, seed and keeper. Never the timelock proposer on mainnet.
PROPOSERTimelock proposer. Mainnet: the multisig. Testnet: defaults to the deployer.
TIMELOCK_DELAYSeconds. Mainnet 172800 (48 h). Testnet 0 so the seed can execute immediately.
DEPTH_FLOOR_USDG6-decimal USDG. The Gate 2 constant from EVIDENCE.md.
REVEAL_DELAY_L2L2 blocks between commit and reveal, ≥ 2. Default 4.
STALENESS_GRACESeconds past a feed's heartbeat before it is stale. Default 3600.
RPC_MAINNET, RPC_TESTNETPublic 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.

  1. 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: see DEPLOYER_ADDRESS in .env. A dry run of Deploy.s.sol against the live testnet on 2026-09-24 simulated cleanly (all mocks, timelock, registries, randomness and Scrip) 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.
  2. Deploy:
    cd contracts
    forge script script/Deploy.s.sol --rpc-url robinhood_testnet --broadcast --verify --verifier blockscout --verifier-url https://explorer.testnet.chain.robinhood.com/api/
    
    Writes packages/config/deployments.46630.json.
  3. 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
    
  4. Exhaust it so the app has a full history and a closed series on first load:
    cd .. && CHAIN_ID=46630 EXHAUST_SERIES=1 pnpm keeper
    
    The keeper commits a pack, waits for the L2 delay and the next L1 block, reveals, and repeats until SeriesSoldOut, then exits once every pack is opened. Alternatively, from Foundry only:
    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
    
    repeated until sold out.
  5. Verify (if --verify was 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

  1. PROPOSER = the multisig, TIMELOCK_DELAY=172800, DEPTH_FLOOR_USDG = the Gate 2 constant.
  2. forge script script/Deploy.s.sol --rpc-url robinhood --broadcast --verify --verifier blockscout --verifier-url https://robinhoodchain.blockscout.com/api/
  3. pnpm build:series regenerates packages/config/series.4663.json from .research/depth.json (run pnpm measure:depth first 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.
  4. Send PROBE_AMOUNT (1e12 raw units) of each token to the timelock, then MODE=schedule forge script script/SeedSeries.s.sol --rpc-url robinhood --broadcast from the proposer key. 48 hours later, MODE=execute from any key.
  5. Fund bounties: Scrip.fundBounties(amount) from any address, or let SeedSeries do it at execute time (BOUNTY_FUND_USDG).
  6. Run the keeper permanently: CHAIN_ID=4663 pnpm keeper under a supervisor. Its liveness is part of the randomness model (RANDOMNESS.md §2): alert if no reveal happens within 5 minutes of a Committed event.

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:

PieceWhereRole
Anvil, chain 31337this machine, :8545the chain, with the mock deployment
Ponderthis machine, :42069the indexer
scripts/gateway.mjsthis machine, :8787filtered 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/tunnelpublic HTTPS address for the gateway
Vercel project ripstockripstock.fun, www redirectsthe 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 "/").