Deployment runbook
Deploy order matters: contracts → indexer → frontend (each needs the previous one's output).
0. Prerequisites
- A funded deployer key on the target chain (testnet faucet).
pnpm installat the repo root.- A v2-style router for fee conversion. Testnet uses the included mock (deployed by the script); mainnet uses the chain's real Uniswap deployment.
1. Contracts (Foundry)
cd contracts
cp ../.env.example .env # DEPLOYER_PRIVATE_KEY, PROTOCOL_TREASURY, BLOCKSCOUT_API_KEY
forge test # full suite should pass
# Testnet 46630 — deploys the protocol + 18 mock stocks + mock router, funds & allow-lists them
forge script script/DeployTestnet.s.sol \
--rpc-url robinhood_testnet --broadcast --verify \
--gas-estimate-multiplier 600 --slow
Robinhood testnet's L1-data-gas component is under-weighted by the node's eth_estimateGas — small transactions out-of-gas at default multipliers. Always pass --gas-estimate-multiplier 600 --slow.
The script writes contracts/deployments/<chainId>.json with every address. To seed the demo MAG7 index afterwards: forge script script/SeedDemo.s.sol ….
Register basket assets on an existing deployment (owner-gated):
cast send <FEE_REGISTRY> "setAssets(address[],bool)" "[0x…,0x…]" true \
--rpc-url robinhood_testnet --private-key 0x…
2. Indexer (Ponder on Railway)
railway login
railway init # or connect the GitHub repo, root = indexer/
railway add --database postgres # provisions DATABASE_URL
Service variables (from deployments/<chainId>.json):
CHAIN_ID=46630
PONDER_RPC_URL=https://rpc.testnet.chain.robinhood.com
INDEX_FACTORY_ADDRESS=<indexFactory>
FEE_PROCESSOR_ADDRESS=<feeProcessor>
PROTOCOL_TREASURY_ADDRESS=<protocolTreasury>
START_BLOCK=<deploy block>
Deploy with railway up (config in indexer/railway.json: start command pnpm start --schema $RAILWAY_DEPLOYMENT_ID, healthcheck /ready).
Deploys can crash-loop on transient Orbit-RPC BlockNotFoundError during realtime sync — the code is fine; re-run railway up until a deploy stabilizes to SUCCESS.
3. Frontend (Next.js on Vercel)
The app lives in web/ and is self-contained. Either import the repo in Vercel with Root Directory = web, or:
cd web && vercel --prod
After a fresh contract deploy, sync web/lib/contracts.ts and web/lib/shared.ts (ASSETS[<chainId>]) from the new deployments/<chainId>.json, and set the NEXT_PUBLIC_* env vars (chain id, indexer URL, contract addresses — inlined at build time, so redeploy after changing).
4. Optional: convert poker
keeper/keeper.ts is a permissionless convenience cron — it computes safe min-outs and calls FeeProcessor.convert for indexes with accrued fees. Run it anywhere (pnpm start), or don't: anyone can convert.
5. Mainnet promotion gate
Promote to 4663 only after all of: the tokenized-equity custody spike passes, professional re-audit of the high-severity areas, US-person geo-gating + legal review for stock baskets, oracle/TWAP-floored min-outs wired for permissionless conversion, and mainnet stock-buy routing confirmed (RFQ/0x vs AMM pools). See Security.