Deploy runbook
How baskets get deployed. The checks, the gates, and the rollback stance.
For operators and the curious: the canonical procedure behind every basket deployment. It is deliberately gate-heavy: immutable contracts deserve paranoid deploys.
Hard prerequisites
- A Safe on chain 4663 as guardian and fee recipient. The deploy scripts hard-refuse EOAs for these roles, and the addresses are immutable per basket; there is no "fix it later".
- A funded deployer EOA (~0.01 ETH covers everything;
relay.link/bridge/robinhood bridges
in seconds). Key held in an encrypted keystore (
cast wallet import), never a plaintext file. - Fresh Chainlink feeds. Stock feeds heartbeat every 24h, 24/5, and the units computation aborts on any stale feed, which in practice means running during US market hours. (Pool-priced constituents trade 24/7 and are always fresh.)
- Token addresses re-verified against the
official contracts page.
The tooling re-reads
symbol()/decimals()on-chain and aborts on mismatch; the docs check is a mandatory human step. Docs unreachable or disagreeing with the config → halt.
The pipeline
Compute units with fresh prices
compute-units reads live Chainlink prices, converts equal USD weights into
raw units for a ≈$100 basket token, and writes each basket's deploy JSON
(tokens, units, fee bps, guardian, fee recipient, caps). It prompts for the
literal word verified after the address check. Sanity pass on the output:
equal USD legs, total ≈ $100, guardian/feeRecipient = the Safe.
Dry-run
forge script script/Deploy.s.sol without --broadcast prints the full
resolved config: on-chain symbols, units, caps, Safe. Human sign-off of
this output, line by line, is the gate for broadcasting.
Broadcast + verify
Broadcasts are gated behind an explicit CONFIRM_DEPLOY=yes environment
variable, and Blockscout source verification runs in the same command
(--verify --verifier blockscout). The script asserts isFullyBacked()
post-deploy. Deploy scripts are not idempotent; a successful broadcast
is never re-run (it would deploy a duplicate).
Smoke test with real money
Before announcing anything: zap-mint ~$10 of each basket through the full
funnel from a fresh account (email → USDG via Relay → Enable USDG → Mint),
redeem a fraction in-kind, confirm isFullyBacked() on Blockscout, check
the geoblock (HTTP 451 via VPN) and the weekend staleness badge, and record
the transaction hashes publicly.
Post-launch guardian operations
Every guardian transaction (cap raises included) requires explicit human sign-off and executes from the Safe, publicly on Blockscout. Cap raises follow the published roadmap criteria, never front-running them.
Rollback stance
There is no upgrade path, by design. If a deployed basket has wrong units:
- Pause its minting via the Safe (
setMintPaused(true)). - Deploy a corrected basket.
- Point the frontend at the new one.
Redemption on the wrong basket keeps working forever; nobody's funds are ever stuck behind a mistake. This is the trade the protocol makes everywhere: no ability to fix in place, in exchange for no ability to tamper.