Skip to main content

Deployment runbook

Deploy order matters: contracts → indexer → frontend (each needs the previous one's output). There is no keeper to deploy — the fee split runs inline in the hook.

0. Prerequisites

  • A funded deployer key on the target chain (testnet faucet).
  • pnpm install at the repo root.
  • The chain's Uniswap V4 PoolManager address (testnet: 0x8366a39CC670B4001A1121B8F6A443A643e40951).

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 (registry-first, hook mined to its permission bits),
# plus mock stocks + price feeds + hookless USDG/stock reserve pools
forge script script/DeployTestnet.s.sol \
--rpc-url robinhood_testnet --broadcast --verify \
--gas-estimate-multiplier 600 --slow
Testnet gas estimation

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. The IndexFeeHook address must satisfy Uniswap V4's hook-permission bits — the deploy script CREATE2-mines it (the deterministic proxy 0x4e59b448… is present on testnet).

End-to-end validation: script/LaunchTradeE2E.s.sol launches a single-sided index, runs buys, and asserts the inline split grows the USDG reserve + in-kind buyback + basket every swap (and that the market cap walks up the curve from ~$20k).

Register basket assets / feeds on an existing deployment (owner-gated):

cast send <FEE_REGISTRY> "setAssets(address[],bool)" "[0x…,0x…]" true \
--rpc-url robinhood_testnet --private-key 0x…
cast send <FEE_REGISTRY> "setPriceFeed(address,address)" <STOCK> <FEED> \
--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_REGISTRY_ADDRESS=<feeRegistry>
FEE_HOOK_ADDRESS=<feeHook> # the per-swap FeeSplit source
START_BLOCK=<deploy block>

Deploy with railway up (config in indexer/railway.json: start command pnpm start --schema $RAILWAY_DEPLOYMENT_ID, healthcheck /ready).

railway up uploads source — always run it after any indexer change

The Railway indexer is deployed by railway up (a CLI source upload), not by a git push or a variable change. Changing a variable and redeploying reuses the stale code. After any indexer code change (e.g. a changed event signature — which otherwise silently stops matching), you must re-run railway up. Reindex from START_BLOCK on every factory/hook change.

Never read contracts inside a handler on this chain

The testnet RPC is a pruned node — a historical eth_call from a Ponder handler during backfill fails missing trie node. Handlers must not do historical contract reads; resolve on-chain state (e.g. bondOf, token metadata) from the frontend at latest instead.

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 (DEFAULTS) and web/lib/shared.ts (ASSETS[<chainId>]) from the new deployments/<chainId>.json. The app reads addresses from these DEFAULTS when the NEXT_PUBLIC_* env vars are unset.

4. Mainnet promotion gate

Promote to 4663 only after all of: the tokenized-equity custody spike passes, a professional audit of the high-severity areas, US-person geo-gating + legal review for stock baskets, confirmation of the real Universal Router vs the custom V4SwapRouter, and confirming mainnet stock-buy routing (the inline basket leg assumes hookless USDG/stock V4 reserve pools — Robinhood equities trade via RFQ/0x, so this routing needs validating). See Security.