How-to guide
Deploy testnet contracts
The testnet deployer installs the complete deterministic infrastructure in one run. It is safe to rerun: a contract already at its target address is skipped only when its runtime bytecode matches, and an unexpected contract at a target address stops the deployment.
Before you deploy
- Complete the repository setup in the README.
- Use a dedicated testnet account and fund it with enough testnet ETH for the remaining steps.
- Use an HTTPS RPC endpoint. Loopback HTTP endpoints are accepted for local test networks.
- Make sure the deployer account has no pending transactions.
Sepolia is the default target (chain ID 11155111). A different testnet must support EIP-1559, the Cancun opcodes used by Zoltar and Uniswap V4, and the Osaka CLZ opcode used by the compiled contracts. The deployer rejects Ethereum mainnet (chain ID 1).
Deploy with GitHub Actions
Use the Deploy Testnet Contracts workflow for a deployment from main.
- Create a protected GitHub environment named
testnet-deployment. - Add the deployer's private key as the environment secret
TESTNET_DEPLOYER_PRIVATE_KEY. Optionally addETHERSCAN_API_KEYas well; without it the post-deploy verification step skips each explorer that requires a key. - Open Actions → Deploy Testnet Contracts → Run workflow.
- Select
main, complete the inputs, and enterDEPLOYas the confirmation. - Review the job summary for each planned deployment's result and address. It includes transaction hashes for contracts deployed during the run.
Use a public RPC URL without credentials. Workflow inputs are stored in GitHub metadata and are not secret.
Deploy locally
Load PRIVATE_KEY from a secret manager or a hidden prompt. Never paste the key into a command, because the command may be saved in shell history. In Bash:
read -rsp 'Testnet deployer private key: ' PRIVATE_KEY \
&& echo && export PRIVATE_KEY
Run the deployer with an explicit RPC endpoint and spending limits:
bun run deploy:testnet -- \
--rpc-url=https://rpc.example \
--chain-id=11155111 \
--max-fee-per-gas-nanoeth=100 \
--max-total-cost-eth=20
Remove the key from the shell when the command finishes:
unset PRIVATE_KEY
The deployer reads the exported PRIVATE_KEY automatically. You can instead pass --private-key=0x... directly, but then the complete command, and therefore the key, may be saved in shell history. Run bun run deploy:testnet -- --help for all options. Options other than --private-key also accept uppercase arguments after -- or environment variables.
| Input | Default | Purpose |
|---|---|---|
RPC_URL / --rpc-url |
Required | RPC endpoint for the target network |
CHAIN_ID / --chain-id |
11155111 |
Expected decimal chain ID |
MAX_FEE_PER_GAS_NANO_ETH / --max-fee-per-gas-nanoeth |
100 |
Rejects higher RPC fee suggestions |
MAX_TOTAL_COST_ETH / --max-total-cost-eth |
20 |
Caps the conservative preflight estimate and transaction budget |
PRIVATE_KEY / --private-key |
Required | 0x-prefixed 32-byte deployer key |
The defaults are authorization limits, not a spend forecast or a required balance. Before sending a transaction, the command checks the RPC chain ID, EVM features, EIP-1559 support, canonical deployer compatibility, expected bytecode, and fee limits. It then estimates only the missing deployment steps. If the conservative estimate exceeds MAX_TOTAL_COST_ETH, it exits before funding or deploying anything. Per-transaction checks enforce the same budget while the deployment runs.
The run logs each planned contract with Status: deployed, Status: already deployed, or Status: ready (installed without a submitted transaction), then verifies the bootstrap support contracts. A step that fails logs Status: failed and stops the run.
When deployment succeeds, the command automatically runs explorer source verification for the target chain. Verification requires ETHERSCAN_API_KEY; each explorer that needs a key and has none is skipped with a message. A verification failure makes the command exit nonzero even though the contracts are deployed, and prints the command to retry on its own: bun run verify:contracts -- --chain-id=<id>. Treat a nonzero exit as "check which stage failed" rather than "nothing was deployed".
Recover an interrupted run
Wait for all pending transactions to settle, then rerun the same command. The deployer revalidates completed contracts and resumes with the first missing step. A testnet that rejects the fixed legacy transactions for the canonical deployers must provide both deployers as predeploys.
What gets deployed
- deterministic genesis REP
- the canonical CREATE2 deployer and Permit2
- a deterministic Uniswap V3 SwapRouter
- the Zoltar and Augur Statoblast protocol factories and their bootstrap support contracts
- the Statoblast Trading factory and router, at the canonical 0.30% fee
Uniswap addresses come from one registry shared by the UI, the bots, and the deployer (shared/core/ts/deployment/uniswapDeployments.ts).
On Sepolia, WETH, the Uniswap V3 factory, QuoterV2, the V4 PoolManager, and the V4 Quoter are Uniswap's published contracts (V3, V4). The deployer verifies that each one carries Uniswap's exact runtime code. An Anvil node with the Sepolia chain ID receives them by replaying Uniswap's original creation transactions, vendored byte for byte in scripts/artifacts/uniswap-deployment.json. Uniswap publishes no SwapRouter (v1) on Sepolia, so the deployer installs one bound to the published factory.
Any other chain receives deterministic WETH plus a complete Uniswap V3 and V4 deployment from the pinned bytecode, so deploying to a new testnet needs no configuration. Reusing Uniswap's own contracts on another chain additionally requires vendoring their creation transactions and runtime hashes in the artifact; today only Sepolia has them. The bots read core addresses from the tracked Sepolia manifest, so they target Sepolia and Sepolia replays (such as the local Anvil network) until a deterministic testnet has a generated manifest.
The command does not create Uniswap pools or add liquidity. Protocol factories create market-specific security pools, share tokens, oracle coordinators, auctions, escalation games, delegates, and child-universe contracts later, when those features are used. Genesis REP allocations are reviewed as part of launching a release; the deployment status oracle reference explains how clients read which steps are installed.