Local development
In one paragraph. Everything in V2 can be run on your machine against a throwaway Anvil chain (chain id 31337) with a mock collateral token that has no value. Since the canonical-manifest change, a local stack is built in two phases: the Foundry scripts deploy and write a pending manifest, and a read-only TypeScript finalizer verifies the deployment against the RPC and writes the canonical manifest with the chain's genesis and anchor block. The matcher, oracle and web refuse anything else. None of this broadcasts to a public chain, and you should keep it that way: the matcher refuses to start on a non-31337 chain without explicit private keys, the deploy scripts refuse public Anvil identities outside 31337, and the finalizers never send a transaction.
Prerequisites
- Bun and Foundry (
anvil,forge) onPATH. cd overdrive/exchange && bun install(workspace:core,matcher,web,ledger,oracle;market-data/is a separate package).- Contracts are compiled by
forge scripton first run;overdrive/lib/submodules must be checked out (git submodule update --init). - Start from a directory without a canonical
overdrive/deployments/v2/31337.perps.json/31337.options.json:DeployV2.s.solrefuses to run if the canonical perps file exists ("canonical perps manifest already exists"), andDeployOptionsV2.s.sollikewise for options. The committed copies predate the verification anchors and are not accepted by the loader (parseVerificationincore/src/deployments.ts), so move them aside and regenerate.
The flow
flowchart TD
A[anvil --chain-id 31337] --> B[DeployV2.s.sol\nwrites 31337.perps.pending.json]
B --> C[finalize-perps-v2.ts\nwrites 31337.perps.json + 31337.json]
C --> D[DeployOptionsV2.s.sol\nreads 31337.perps.json\nwrites 31337.options.pending.json]
D --> E[finalize-options-v2.ts\nwrites 31337.options.json + 31337.json]
E --> F{options series?}
F -- yes --> G[SeedOptionsV2.s.sol → pending → finalize-options-v2.ts again]
F -- no --> H
G --> H[push a signed PricePayload\nreplicate pushPrice from e2e-v2.ts]
H --> I[matcher OD_V2=1] --> J[web VITE_V2=1]
1. Chain
anvil --port 8545 --chain-id 31337 --code-size-limit 50000 --gas-limit 200000000
2. Perps: deploy, then finalize
cd overdrive/perps
MARKETS=CMPT,GRID forge script script/DeployV2.s.sol --rpc-url http://127.0.0.1:8545 \
--broadcast --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--disable-code-size-limit --non-interactive --sig "run()"
# -> overdrive/deployments/v2/31337.perps.pending.json (deploymentStatus: "pending")
cd ../exchange
bun run core/scripts/finalize-perps-v2.ts --rpc-url http://127.0.0.1:8545 \
--pending ../deployments/v2/31337.perps.pending.json
# -> 31337.perps.json and 31337.json with deploymentStatus: "verified",
# verifiedChainId, verifiedGenesisHash, verifiedAtBlock, verifiedBlockHash
The private key above is Anvil's published default account 0; it is not a secret and holds nothing outside your local chain. DeployV2.s.sol env knobs: MARKETS (ALL or exactly CMPT,GRID), QUOTE (defaults to a fresh MockQuote on 31337, minted 100 M units to the deployer), GOVERNANCE and MATCHER (default to the broadcast sender, which must equal governance), SIGNER0..2 (default Anvil 0–2), ACTIVATE (default true on 31337 so markets are tradable despite enabled=false candidate configs; rejected elsewhere). On 31337 the finalizer accepts the pending file without a reviewed-config file; on any other chain --reviewed-input is mandatory and its keccak must match the hash the deploy script committed into the pending manifest.
3. Options: deploy, then finalize
DeployOptionsV2.s.sol reads the canonical 31337.perps.json (so step 2 must be finalized first) and requires a reviewed options configuration on every chain, including 31337: OPTIONS_CONFIG_FILE pointing at a JSON with governance, quote, three signers, and per-market marketId, sleeveExpiry and config (the OptionConfig fields), plus OPTIONS_CONFIG_HASH equal to its keccak. It deploys the pricer, resolver, engine and ERC-1155 token, sets each market's option config, deploys one writer sleeve per reviewed market bound to that sleeveExpiry, and writes 31337.options.pending.json.
cd overdrive
OPTIONS_CONFIG_FILE=<path> OPTIONS_CONFIG_HASH=<keccak of that file> \
forge script script/DeployOptionsV2.s.sol --tc DeployOptionsV2 \
--rpc-url http://127.0.0.1:8545 --broadcast --private-key 0xac09…ff80 \
--disable-code-size-limit --non-interactive --sig "run()"
cd exchange
bun run core/scripts/finalize-options-v2.ts --rpc-url http://127.0.0.1:8545 \
--pending ../deployments/v2/31337.options.pending.json --reviewed-config <same path>
The options finalizer re-reads the pricer signers and every market's live OptionConfig, sleeve expiry and collateral decimals at one pinned block, checks that TOKN is disabled, and compares the canonical perps anchors before and after its reads. To create option series and secondary pools, run SeedOptionsV2.s.sol with SERIES_INPUT_FILE/SERIES_INPUT_HASH (the complete desired series list) and the same OPTIONS_CONFIG_FILE/HASH, then finalize again with --reviewed-series. Seeding requires the market to be enabled in the registry and the option config to be calibrated and approved; it never funds a sleeve, issues an option or activates a market.
4. Push a price
After a deploy there is no price on chain until someone pushes a snapshot; the matcher will not invent one for V2 markets, and settleBatch reverts without an accepted priceSequence. core/scripts/e2e-v2.ts contains a pushPrice helper that signs a PricePayload (CMPT I = M = $2.50, GRID I = M = $100, validUntil = now + 300 s) with Anvil accounts 0 and 1 and calls TrionPriceVerifierV2.pushSnapshot. Replicate it, or run the script itself:
cd overdrive/exchange/core
ANVIL_PORT=8545 bun run scripts/e2e-v2.ts --keep-anvil --deploy-only
e2e-v2.ts spawns its own Anvil on a random port, creates an isolated overdrive/deployments/local-v2-* directory, and delegates to core/scripts/local-v2-stack.ts (deployLocalV2Stack), which runs DeployV2.s.sol, calls finalizePerpsV2, activates CMPT and GRID once their registry delay has elapsed on the local clock, writes a reviewed options configuration from the OptionPricerV2.t.sol fixtures, runs DeployOptionsV2.s.sol and calls finalizeOptionsV2. It then seeds prices and, without --deploy-only, runs the §8 acceptance vectors (funding epochs, liquidation, ADL, halt/recovery, replay protection, writer-sleeve deposit); the latest recorded run is 8/8 local vectors. It never touches the committed 31337.*.json and does not seed option series. The isolated run recorded in docs/review/v2/local-options-launch-proof.json went further by hand: all seven perp markets, a deterministic CMPT+GRID series list through SeedOptionsV2.s.sol and finalize-options-v2.ts --reviewed-series, and an idempotent replay of the seed that had no transactions to broadcast and passed the finalizer on its default build-artifact path.
Run the matcher in V2 mode
cd overdrive/exchange/matcher
OD_V2=1 \
OD_RPC_URL=http://127.0.0.1:8545 \
OD_CHAIN_ID=31337 \
OD_MATCHER_PORT=8790 \
OD_DEPLOYMENTS_DIR=../../deployments/v2 \
OD_STATE_DIR=./state-v2local \
bun run src/main.ts
| Variable | Effect (overdrive/exchange/matcher/src/config.ts, main.ts) |
|---|---|
OD_V2=1 (or --v2) | Load the canonical deployments/v2/<chainId>.*.json, run validateV2Bindings against the RPC (genesis and anchor block must match), boot one EngineV2 + relay + ingester per listed market. |
OD_RPC_URL | JSON-RPC; the chain id is read from it. |
OD_CHAIN_ID | Optional guard; startup fails if it disagrees with the RPC. |
OD_RELAYER_KEY | Sends settleBatch. Defaults to Anvil key 0 only on 31337; required elsewhere, and rejected if it is a public Anvil account. |
OD_MATCHER_KEY | Signs batches (TrionPerpetual domain). Defaults to the relayer key on 31337; required on other chains; must match the matcher address in the manifest. |
OD_MARK_EVIDENCE_TOKEN | Enables the authenticated GET /v2/mark-evidence endpoint the oracle uses to capture depth (at least 32 characters). Without it the endpoint answers 503 EVIDENCE_UNAVAILABLE. |
OD_STATE_DIR | SQLite store plus per-market <chainId>-<sym>.wal and -replica.sqlite. Delete it for a clean WAL. |
OD_TAKER_FEE_BPS, OD_MAKER_REBATE_BPS | Advertised in /v1/config; defaults 5 / 3. |
OD_MNEMONIC_FILE, OD_RELAYER_INDEX, OD_PUBLISHER_INDEX | Key derivation from a seed file instead of hex keys. |
Each engine re-reads the market's registry entry on every admission and batch proposal (marketStateLoader); if governance disables the market on your local chain, new orders are rejected and unproposed matches are cancelled while cancels, deposits and withdrawals keep working.
The seeded snapshot is valid for 300 s and the matcher does not renew it. Once validUntil passes, /v1/markets and /v1/account answer 503 V2_PRICE_UNAVAILABLE, the ticker stream reports status: "UNAVAILABLE", and new orders cannot settle until a fresh signed snapshot is pushed with a higher sequence. This is deliberate: an expired price is reported as missing, never as current.
Run the oracle services (observation only)
The V2 publisher and signers can be run locally, but they are gated hard. bun run src/main.ts --observe-v2 in overdrive/exchange/oracle loads the canonical manifest, verifies chain anchors, reads the committee and registry, and then only records observations and mark checkpoints; it requires OD_RPC_URL, OD_CHAIN_ID, OD_ORACLE_POLICY (a JSON policy naming bookEvidenceUrl, three signer endpoints on three distinct hosts, and per-market sources with rightsApproval evidence files), OD_ORACLE_STATE_DB and OD_MARK_EVIDENCE_TOKEN. Actual signing (OD_ORACLE_SIGNING_ENABLED=1 on each signer.ts with one OD_SIGNER_KEY) and publication (OD_PUBLISH=1 plus OD_ORACLE_PUBLICATION_ENABLED=1 and a relayer key that is not a signer) exit with an error before loading keys or databases unless those approvals are set. On 31337 loopback HTTP endpoints and public Anvil accounts are allowed; on any other chain they are refused. See Data & methodology.
Run the web app
cd overdrive/exchange/web
VITE_V2=1 VITE_MATCHER_URL=http://127.0.0.1:8790 VITE_RPC_URL=http://127.0.0.1:8545 bun x vite --port 5173
Import an Anvil account into an injected wallet, mint or transfer MockQuote, approve the market's PerpetualV2, then deposit from the Assets panel. Trade CMPT or GRID. The Options and Liquidity pages list only series present in the finalized options manifest; with "series": [] they have nothing to select. With seeded series the pages are fully exercisable locally: the recorded CMPT run (docs/review/v2/local-options-launch-proof.json, screenshots docs/review/v2/web-flow/08-options-provider.png and 09-options-settled.png) covered a 1,000-unit writer deposit, a mint of 10 CT under an explicit maximum total premium (3 rejected before any transaction, 3.5 accepted at 3.371211), quote-only and option-bearing DLMM bins, a 0.4-quote swap, a five-bin Provide deposit, quote-only exits on both pages while the series was expired with no fixing, settlement at a synthetic 2.4 fixing, a 6.139314 CT claim, full LP exit and writer redemption, with the mock quote total unchanged at the end. Only CMPT was driven through the browser; GRID was seeded and left alone. The index, volatility and fixing were locally signed fixtures with Anvil's public keys, nothing more.
Verify the whole loop headlessly
bun run overdrive/exchange/web/scripts/e2e-browser-flow.ts
This allocates free ports, boots its own Anvil + deploy via e2e-v2.ts, starts the matcher with a temporary OD_STATE_DIR, builds and serves the web app with vite preview, and drives connect → deposit → post-only maker → IOC taker → on-chain batch → TP/SL → hard revoke → withdraw in a headless browser, saving screenshots under docs/review/v2/web-flow/. On Windows it re-launches itself under Node to avoid a Bun/Playwright pipe hang. It covers the perp path only; it has no options or liquidity step, so the options lifecycle above is evidenced by the proof JSON and its receipts, not by this script.
Options V3 locally
The V3 order book has its own one-command harness, overdrive/exchange/options-book/scripts/dev-harness.ts. It starts an Anvil on port 9107, deploys the V3 mocks (overdrive/test/v3/mocks/: a six-decimal mock quote token with public mint, a mock market registry, a mock price verifier with settable snapshot and fixing, and a mock OptionPricerV2) plus OptionsExchangeV3, lists the reviewed 126-series chain with expiries shifted into the future where needed, funds Anvil accounts 1–4 with mock quote and deposits them, writes a verified-shaped manifest for chain 31337, runs the book service on port 8799 (http://127.0.0.1:8799/v3/options), seeds a two-sided book and prints the URLs and environment the desk and the market maker need. Service configuration is OD_RPC_URL, OD_V3_MANIFEST, OD_OPTIONS_PORT, OD_OPTIONS_DB and the settler key OD_OPTIONS_SETTLER_KEY; the market maker takes OD_OPTIONS_MM_KEY. Everything it produces is mock collateral of no value. See the Options order-book API.
Safety notes
- Never point
forge --broadcastat chain 4663 from a development machine. The production V2 deployment on 4663 (overdrive/deployments/v2/4663.json) is live with real USDG; its deploy and finalize procedure is an operator runbook, not this page, and the scripts refuse public Anvil identities off 31337. The4663.*.dryrun.jsonfiles are pre-cutover predictions only. - Mock collateral is minted at will and is worth nothing. Do not present local balances, fills or funding as economic results.
- Local runs use Anvil's public keys as governance, matcher and oracle signers. None of that resembles a production key ceremony; every script and service in this page refuses those keys off 31337.
- The finalizers are read-only and lock their destination files; if one dies mid-run, confirm no other finalizer is running before removing a
*.finalize.lock.
Source trail: overdrive/perps/script/DeployV2.s.sol; overdrive/script/DeployOptionsV2.s.sol, SeedOptionsV2.s.sol; overdrive/exchange/core/scripts/finalize-perps-v2.ts, finalize-options-v2.ts, v2-publication.ts, e2e-v2.ts, local-v2-stack.ts; overdrive/exchange/core/src/deployments.ts, v2-bindings.ts; overdrive/exchange/matcher/src/config.ts, main.ts, server.ts, mark-evidence.ts, index-feed.ts; overdrive/exchange/oracle/src/main.ts, publisher.ts, signer.ts, v2-runtime.ts; overdrive/exchange/web/scripts/e2e-browser-flow.ts; overdrive/exchange/web/src/api.ts, wallet.ts, v2-config.ts; docs/review/v2/local-options-launch-proof.json.
Repository-owned documentation · September 2026 · Educational material, not investment advice and not an audit.
Documentation
V2 · Live on Robinhood Chain