Skip to content

Latest commit

 

History

History
267 lines (245 loc) · 23.7 KB

MAINNET.md

File metadata and controls

267 lines (245 loc) · 23.7 KB

Mainnet

Zecured is built for Zcash testnet and Base Sepolia, where its Base side ran live on 27 October 2026, and it is tested on forks of Base Sepolia and Base mainnet. The mainnet beta changes configuration, not code. Full mainnet also needs the designs not yet built (a dispute bond, per-signer reimbursement, the fixes in Known limitations), and what code can't bring: independent operators, real bonds and liquidity, and an audit. This page says what was checked against mainnet, what the configuration changes and why, what a deployment needs, and what is designed but not built. The decisions are in docs/DECISIONS.md; the parties and the money are in docs/ECONOMICS.md.

1. Checked against mainnet

What How Result
Base contracts on mainnet's Morpho Blue and RISC Zero router The v3 contract suites on a Base mainnet fork (not yet re-run on v4.1): ZECURED_FORK_NETWORK=base-mainnet ZECURED_FORK_BLOCK=52688338 forge test --isolate (from contracts/; BASE_MAINNET_RPC_URL optional) 533 tests pass and one is skipped (its EIP-712 vectors are for chain id 84532); the invariant campaign holds. Morpho Blue, the AdaptiveCurveIRM, the 62.5 % LLTV and the router are at the same addresses as on Base Sepolia (read on-chain, 2026-10-16)
Real proofs on mainnet's verifier The same suite's real Groth16 seals (a Testnet deposit, an F1 spend) Both verify through the mainnet router (Groth16 selector 0x73c457ba, its emergency-stop wrapper not paused): the deposit mints, the theft slashes all five bonds
Zcash Mainnet header rules config/relay-mainnet.json, checked against Zebra v7.0.0-rc.0 and 1,600 real Mainnet headers (docs/ref/FACTS.md, D49) The relay follows the real Mainnet chain under the Mainnet rules, and refuses headers that break them (wrong bits, minimum difficulty after a long gap, times out of bounds, a bad Equihash solution, a hash above the target)
Every deployment config test/script/DeployConfig.t.sol reads each config/zecured-*.json through the deploy script's checks and the constructors' rules config/zecured-base-mainnet.json passes and has the values below
The price feed on the live sources test/fork/PriceFeedBaseFork.t.sol (needs BASE_MAINNET_RPC_URL), run at block 52,688,407 Both sources fresh; price() equal to the oracle of Morpho's cbZEC/USDC market on the same report
The whole deployment, mainnet configuration On a local anvil fork of Base mainnet: DeployPriceFeed over the live Chainlink and API3 feeds, DeployRelay with the Mainnet parameters and a real Mainnet checkpoint, DeployZecured with config/zecured-base-mainnet.json, then BondSigners (five bonds of 50,000 USDC) Nine contracts on Circle USDC, mainnet's Morpho Blue (a new zZEC/USDC market) and RISC Zero router, the feed as both the market's oracle and the cap's price; at a ZEC price of 1,187.68 USD the bonds cover about 105 ZEC, so the 100 ZEC cap is the limit, and minting is open

Not done yet: a shadow relay following Zcash Mainnet at Mainnet difficulty (it waits for NU7's Mainnet height: 3,543,405 was proposed on 2026-10-20 and was not yet ratified when last checked, docs/ref/FACTS.md), and a real Mainnet deposit into a Mainnet FROST vault, which the hackathon build does not make.

2. Configuration

contracts/config/zecured-base-sepolia.json (testnet, Base Sepolia) against contracts/config/zecured-base-mainnet.json (the template). Only what differs:

Parameter Testnet (Base Sepolia) Mainnet Why
loanToken MockUSDC, a test token we mint (MOCK: no value): the deploy script mints one on a local fork, and a Base Sepolia deployment takes one as LOAN_TOKEN (the file's default is Circle's Base Sepolia USDC, which no run used) Circle USDC 0x8335…2913 Test USDC the deployer can mint for the bonds, the lenders and the market makers; the contracts take any 6-decimal USDC
coverageBps (k) 10,000 5,000 With independent operators the bonds should cover twice what is locked
maxTotalZat 5 TAZ 100 ZEC Start small. The cap is immutable: raising it means a new deployment; within it, the bonds decide
minBond 100 USDC 50,000 USDC Five equal bonds that cover about 100 ZEC at k = 50 % (105 ZEC at 1,187.68 USD per ZEC, 98.8 at 1,265)
bountyCap 50 USDC 10,000 USDC 10 % of a slash in both
tierMaxAmountZat / tierMinWork one tier, 3.5 M (about ten Testnet blocks) up to 1 / 10 / 100 ZEC: the work of 6 / 20 / 190 Mainnet blocks Faking a deposit should cost the attacker about three times its value in mining revenue; recompute the raw work from the difficulty at deployment
slashConfirmationWork 38 M the work of 100 Mainnet blocks at least max(100 blocks, 3 x bountyCap / block value): a fake case gains at most the bounty
finalizeDelay 150 s 30 min Time for the honest feeders to replace a slow fake branch after a feeder outage
challengeWindow, livenessPeriod 24 h 72 h Time to notice and react, on both sides
commitGrace 2 h 6 h At least twice a payout's lifetime of 136 blocks (target height, the 120-block expiry, the nodes' tip skew and reorganization margin), which the deploy script checks: 6,800 s at 25 s blocks, 20,400 s at 75 s before NU7
disputeWindow / inclusionWindow 24 h / 6 h 72 h / 48 h Within the challenge window plus the unbonding, which the deploy script checks
unbondingPeriod 54 h 7 days Outlasts the challenge window, the liveness period and the commit grace
incidentWindow 3 days 7 days Cases of one incident share one price
capPriceMaxAge 1 h (demo price) 25 h The price feed's maxSourceAge (a 24-hour heartbeat plus an hour), which the deploy script requires
autoPauseOnCase off on An open F1/F2 case pauses new minting
journalV2 / branchIds v2 / NU7 0x77190AD9 v2 / NU6.3 0x37A5165B, NU7 0x77190AD9 The epoch of the deposit is part of what is proven; the Testnet build proves the NU7 epoch only
minRefundZat 20,000 100,000 A refund must be worth its own payout
refundAbandonPeriod 30 days 2 years
refundDormancy 48 h 7 days A refund still without a destination stops counting against the cap after it (R-11)
minSeize / minSeizeBps 100,000 zat / 0 0.05 ZEC / 5 % No liquidation small enough to grief a vault with dust claims
versionDepositDelay / versionViolationDelay / versionEstopDelay 10 min each 14 / 7 / 1 days A new verifier takes effect only after users had time to see it
zcashNetwork zcash-testnet zcash-mainnet The deploy script checks the relay's network

feeBps (0.5 %) and the leftover sink (the burn address) are business choices; the compensator can name a public-good address instead of burning what no victim needs.

3. A mainnet deployment

  1. Price feed. script/DeployPriceFeed.s.sol with config/price-feed-base.json deploys a ZecuredPriceFeed (design notes T-10) over Chainlink's ZEC/USD Standard proxy (0x69e5…6ec0, 18 decimals, 24-hour heartbeat) and API3's ZEC/USD reader proxy (0x24F2…Ec15), each fresh for 25 hours, agreeing within 5 %. The Standard proxy rather than the SVR-Backup one Morpho's cbZEC market reads: the same aggregator, without the liquidation auction's delay. The feed is both the market's oracle and the cap's price: export BASE_ORACLE (and CAP_PRICE, which defaults to it); the deploy script checks its primary source, its secondary, that it is not stale and that capPriceMaxAge is its maxSourceAge, and never puts the DemoShockOracle (a testnet demo control) in front of the market. A keeper calls poke() whenever state() differs from recordedState(), so that the last good price is recorded. API3's plan on Base ends on 2026-12-20: renew it, or the feed runs on Chainlink alone.
  2. Relay. Set nu7ActivationHeight in config/relay-mainnet.json once NU7's Mainnet height is assigned (it is type(uint32).max until then, which no chain crosses), deploy ZcashHeaderRelay with it, and run at least two independent feeders. A Mainnet header costs about 600,000 gas, most of it the Equihash check (test/relay/RelayCostMainnet.t.sol): on a Base mainnet fork at block 52,702,230 (base fee 0.005 gwei) that is about 3.0e-6 ETH of L2 execution and 8e-9 ETH of L1 data per header. A batch saves only the per-transaction part and is cheapest at 4 to 8 headers (597,000 gas per header at 4, 645,000 at 16, where memory grows). At NU7's 25-second blocks the relay takes 3,456 headers a day, about 0.010 ETH a day at that base fee (0.0035 ETH before NU7). The two feeders should not race for the same headers: one feeds, the other takes over when the relay's tip stalls.
  3. Guests. The guests workflow builds the deposit and violation guests for Mainnet; their image ids become the initial versions (DEPOSIT_IMAGE_ID, VIOLATION_IMAGE_ID).
  4. Contracts. DEPLOYMENT_NAME=base-mainnet forge script script/DeployZecured.s.sol --broadcast: nine contracts at predicted addresses, whose constructors check the wiring.
  5. Bonds. Each operator bonds from its own key (script/BondSigners.s.sol); nothing is minted before all five are in.
  6. Operators. Five independently run signer nodes (docs/OPERATORS.md), the coordinator, the liquidator and the market maker, with lenders supplying the market.
  7. An audit of the contracts, the guests and the signer policy.

4. Designed, not built

  • Adapter registry (T-07b). If Circle blacklisted the SettlementAdapter, liquidations and closes through it would stop, and the remedy today is a migration. The design adds adapters to the version registry: all five signers schedule a new adapter, which zZEC and the Registry accept after a public delay; three signers remove one at once, never the last.
  • Confirmations by proof only. Since v4 every payout is proven (proven delivery, D76): a payout is final on Base on a proof of its inclusion, or once its signed confirmation's dispute window closes undisputed (R-3), so a signed confirmation is a disputable first step. Mainnet could drop signed confirmations altogether, at the cost of a proof's delay (about an hour of confirmation work, then one GPU proof) before a claim counts as paid.
  • A dispute bond, a launch condition. On testnet a claimant disputes a confirmation for free, once per confirmation. Mainnet launches with a small bond on each dispute, returned when the dispute reopens the claim and paid to the prover when an inclusion proof upholds the confirmation. Routine inclusion proofs of every payout make disputes rare.
  • Reimbursement per signer share. The bonds pay an overdue claim pro rata, but its ZEC goes back to one destination the signers give together (three of them, each deriving the claim's own treasury destination from its id, W-6). Mainnet splits it: each signer registers its own reimbursement destination, and the claim's ZEC is paid back per share, one output per signer. Every commitment of a claim's input carries the same claim set (RT-4), so the split must be fixed when the bonds pay, as per-signer sub-claims of that input, not chosen at payout time.
  • Vault pool (T-12). Vaults made ahead of time (VaultPooled, signed by all five) and assigned to a user by a second message (VaultAssigned), also signed by all five, so that opening a vault is immediate and the DKG leaves the user's path. Assignment must take all five: with three, three colluders could assign one pool vault to a user and to themselves and land the user's deposit first.
  • Price feed risks. When the two sources disagree by more than 5 %, the market takes the lower price and the cap the higher: either source can pull liquidations forward, never mint more. The feed does not read Chainlink's L2 sequencer uptime feed: mainnet should treat the price as stale while Base's sequencer is down and for a grace period after it restarts, so that nothing is liquidated on prices users could not react to. While the feed is stale the market keeps the last good price: Morpho goes on lending against it, new minting stops at the cap and the bonds pay no liveness claim. A haircut that grows with staleness is an option to evaluate during the beta.
  • Swap: a maker stake and a verifiable delivery of the destination. On testnet a maker that never pays costs its taker only time: the USDC comes back after the pay window. A mainnet maker would stake USDC, slashable for an order it did not pay although it had received the order's destination. That needs the delivery to be provable without putting the receiver on Base: either the maker signs a receipt when it gets the salt and receiver (as the signers do for liveness), or the taker posts them encrypted to the maker's key with a proof that they open the order's commitment.
  • Swap before it opens on mainnet. The maker and the witness builders follow the configured Zcash network (D81); payment-main, with real seals, must be an active Swap version. USDC's blocklist applies: a blocklisted counterparty cannot be paid, and its order's USDC stays in the contract.
  • Share refresh and repair through the nodes. The threshold library refreshes every share of a vault and repairs a lost one, but no node runs either yet (docs/TIER3.md § 5). Mainnet needs both as node rounds, their private messages sealed node to node as in the DKG, and a runbook: a refresh after any suspected share leak, a repair when a signer loses its share.
  • Fee schedule. ZIP 317's marginal fee (5,000 zatoshi) and grace actions (2) are constants of the spend logic; a change of Zcash's fee rules would take a new deployment.

5. Mainnet beta

Not run for the hackathon (DECISIONS D82): this is the plan, and the code and configuration for it are in the repository. A capped, invite-only beta on Base mainnet and Zcash Mainnet, unaudited, and labelled so in the app: "Mainnet beta — unaudited — invite only — small caps".

  • Invitations. Vault openings (the coordinator's POST /vaults) and swap quotes need an invite code. Only the codes' hashes are stored, their uses are counted, and one switch closes new openings; exits and payouts never stop. No contract change.
  • Configuration (config/zecured-base-mainnet-beta.json, derived from the mainnet template and checked by the deploy script): maxTotalZat 1 ZEC, vaults of 0.05 and 0.1 ZEC, minBond 300 USDC (five bonds of 300 USDC cover about 0.6 ZEC at k = 50 % and 1,265 USD per ZEC; five of about 510 USDC open the whole cap), coverageBps 5,000, feeBps 50, the template's windows and work tiers, bountyCap 100 USDC, refundDormancy 7 days.
  • Relay: NU7's Mainnet height (3,543,405 proposed on 2026-10-20, not yet ratified; docs/ref/FACTS.md) in config/relay-mainnet.json, a checkpoint near the tip, two feeders (§ 3).
  • Price: ZecuredPriceFeed over Chainlink and API3; API3's plan runs until 2026-12-20, past the judging period.
  • Guests: deposit-main and violation-main v2 with real seals before the beta; payment-main before the swap opens on mainnet.
  • Swap: a 24-hour pay window, finalization after 1,800 seconds and 6 headers, the Intake's mainnet work tiers, the NU6.3 and NU7 branch ids, maxOrderUsdc 250, no fee, and a 14-day delay for a new Swap version.
  • Operations: a second stack with its own data directories and units, two independent Mainnet light wallet servers, the watchdog on both stacks, one prover and queue for both.
  • The Zcash network comes from the configuration (D81): the network config's chain id 8453 gives Mainnet (or zcash_network: "mainnet"), the maker's config likewise, the bots read it from the deployment's chainId and the relayer from its relay's networkId. A light wallet server of the other network is refused at its first answer. Until Mainnet's NU7 the witness builders walk NU6.3's epoch from 3,428,143: about 95,000 headers and compact blocks on 2026-10-19, read once into the header file and the summaries cache (minutes; then only the new blocks).

NU7 activation (about 2026-11-05). Before it: the relay's activation height, spacing (25 s) and averaging window (102 blocks from NU7); ZIP 221's history tree restarting at the activation, so a proof's leaf and peaks are of its own epoch; the guests' branch ids (deposit-main proves both NU6.3 and NU7); the off-chain stack on a zcash_protocol release with NU7's Mainnet height (the payouts' branch id, the nodes' expectations of it and the witness builders' epochs all come from it) and the P2P client announcing NU7's Mainnet protocol version (170,190). During it the activation window is watched; if anything is off, the operators pause new minting with zecured-ops, and exits go on.

6. What stays true on mainnet

The bonds cover what the cap admits, not more; five colluding signers can take every vault, and a new verifier version can only follow their public delay; proof of work protects a deposit proof only by the work it demands; and someone must build each witness and run the prover. docs/ECONOMICS.md § "What the bonds do not cover" says the same in numbers.

7. Known limitations and their designs

What our internal v4 review (2026-10-21, by reviewers who did not write the code) found that v4.1 does not close, with the design that would. Each has a row in docs/DECISIONS.md (D94 to D103). The last three items come from the review of the v4.1 diff.

  • W-22: compensation for claims paid on Zcash (medium). F3 does not check that the payout X it judges spent the committed input (a claimant cannot compute the input's tag), F4 does not either, and since W-1 F4 compensates a final claim too; Included does not bind X to the input (W-11). The spend logic lets a new commitment of a spent input name its claims again (D57 needs it), and the compensator registers a shortfall in any state but None. So three colluding signers who also hold a claim (as the liquidator, the market maker or a vault's owner) can have a claim that Zcash already paid compensated again: commit the input a second time naming an unrelated X2 and prove F3 on X2's output (or prove X2 included first), or commit their own claim under a wrong reference and prove F4. Each case slashes the commitment's signers, so while the bonds stand they lose more than they gain (mainnet's coverage is 50 %; at testnet's 100 % they gain at most the bounty, 50 USDC). After any slash the bonds are empty and new bonds are refused, so it costs nothing, and the compensation joins the open incident in layer 2 or 4, ahead of the lenders and owners of the real theft. Design: (a) a second Included and F4 that also prove X spent the committed input, with a viewing-key witness as F1's, and a Registry that records which payout paid each claim, so that F3 and F4 on a final claim count only against that payout; (b) claim-level compensation whose own case brought no funds is paid after layer 4, from what the incident leaves, so that self-compensation after a halt takes nothing from the victims.
  • W-23: the reimbursement, per signer and never blocking. v4.1 gives each claim the bonds paid its own treasury destination, signed by three signers who each derive it (W-6): three colluders can still sign another destination and take the honest signers' share. Design: each signer registers its own reimbursement commitment when it bonds, and the claim is paid back per share. A bond-paid claim without a destination moves into the change of its input's next commitment, bound to the change's tag, so that its input never freezes; the same for a compensated claim whose input is unspent. And a liveness arming needs the receipts of two different signers, with the later receivedAt: one dishonest signer can now arm a claim it never let be delivered.
  • W-24: borrowing during a challenge window. Until a theft's case is executed (24 hours on testnet, 3 days on mainnet) the owner of the robbed vault can still borrow up to the LLTV; the keeper burns the collateral down to the LLTV again whenever that would retire something (W-12), but what was borrowed in between is the compensator's. Design: the debt is fixed at frozenSince by a snapshot and later borrowing comes out of the owner's equity, or the collateral above the LLTV is escrowed when the case is proven.
  • W-25: a refund proof that cancels a vault. Anyone holding a vault's tag key K (the five signers, the prover) can cancel a vault whose activation was never proven with a Refund-mode proof of its deposit: a denial of service that costs the owner its vault and its fee. Design: Refund mode on such a vault only from the journal's account.
  • W-26: refunds and the cap. A refund is not counted against the coverage cap, yet a compromised vault's refund is compensated in full. Design: compensate a refund up to the cap's share only.
  • W-27: a proof that goes stale after its deadline. A swap settlement or refund proof that arrived in time but whose anchor later left the best chain cannot be proven again once the deadline passed; SWAP.md's "a proof only needs to arrive by the deadline" holds only while the proof stays valid. Design: a bounded period to prove again, and a taker's collateral on Sell.
  • W-28: the prover's budget when confirmations are withheld. If three colluders withhold a payout's confirmation, the honest Included proof must land within commitGrace less the payout's lifetime and the slasher's work: about 20 minutes in the worst case on testnet (docs/OPERATORS.md states the budget). Design: a larger commitGrace on mainnet.
  • W-29: liveness taken before an F3 is proven. A claimant that takes the bonds' liveness payment first and proves F3 afterwards is paid part of its 105 % by the honest signers' bonds: the F3 still slashes the culprits (W-5), but the slash goes to the pool, not back to them. Accepted for now: the culprits are slashed either way, and the order gains the claimant nothing.
  • W-30: a refund that cancels its own vault. A refund waiting on a vault whose activation was dropped can cancel that vault in the same transaction. No design yet: it is listed for the mainnet audit.
  • W-31: a false change record executed before the deposit lands. When an F1 case about a tag that colluders recorded as their own vault's change executes before the real deposit's proof lands, it compromises the colluders' vault, whose owner is counted as the victim; the case then follows the note to the deposit's vault once the tag is the deposit's (W-2). The false record is the signers' own violation, provable as F2 if its payout lands; that is the remedy for now.
  • W-3 and the invariant campaigns. The campaigns have no version governance: no action adds or removes a verifier version, so W-3 (a compromised vault's refunds past a removed version) is covered by fork tests (contracts/test/fork/V41Regressions.t.sol), not by the invariants. Left for the mainnet audit: campaign actions that add and remove versions while vaults are open, compromised and refunding.
  • N-12: the Ironwood index file. zecured_zcash_ops::txindex rewrites its whole file after each range it reads. Testnet's Ironwood history is small; Mainnet's only grows, so there the index appends to its file (or keeps a database) and never rewrites what it kept (D107).
  • Swap: a refused destination leaves the taker's USDC in escrow. The maker keeps a Buy order's place and ZEC for its destination only for reservation_secs (10 minutes) after it reads the order, and only within the cap of the order's taker on Base (N-10, D108). A destination given later, or for an order beyond the cap, may be refused (busy, no_inventory), and the taker's USDC then waits in ZecuredSwap until the pay window ends. Design: a cancel path for an order the maker declines (a release the maker signs), or a destination deadline shorter than the pay window.