Implementation Mandate
A standing instruction to every implementor (human or agent) building zkCoins. It is deliberately short on "how to code" and exact on what done means, what the source of truth is, and how to make decisions without asking.
0. Status: green field
zkCoins is green field. Nothing in production must be preserved. There is no data to migrate, no API contract to keep stable, and no backward compatibility to honour. Every existing artefact in the node, sdk, and app repositories is a prototype and MAY be rewritten, replaced, or deleted in full. You are free to design the codebase from scratch. The only fixed point is the protocol defined in the Specification.
This freedom is also a responsibility: where the current code disagrees with the spec, the spec wins and the code is changed — never the other way around without a spec change going through this repo first.
1. Source of truth
The single normative source is docs/specification.md in the zk-coins/docs repository (this site). It is complete: every key, hash, identifier, byte layout, circuit bound, proof-system parameter, wire format, Nostr event kind, Blossom endpoint, REST route, and economic rule needed to build the system is fixed there. If you believe something is still ambiguous, that is a spec bug — open a PR against docs to resolve it, then implement; do not resolve it silently in code.
2. Scope and order
The work is not finished when the node compiles. It is finished only when all three layers conform to the spec and interoperate end to end:
node(Rust) — build to the spec: the singleCPlonky2 circuit (spec §2.5, §1.7.9), the canonical hashing/serialization (§1.7), the Bitcoin inscription layer with NISSHAC half-aggregation and the first-occurrence scanner (§3, §1.7.10), the Nostr relay + Blossom store + ZBE (§4, §7.3, §7.4), the capability-gated REST API (§5, §7.5), and the publisher interface (§3.4, §7.6).sdk(TypeScript) — rebuild the thin typed client and account adapter against the new/v1/API: BIP-39/32 derivation, BIP-340 signing with the sign-to-contract tweak, the transition proving handshake (/v1/tx→awaiting_signature→/sign), and the capability/pull REST flows. The SDK is the reference for all client-side crypto (derivation, hashing, signing) — byte-equivalent at the hash and derivation level, verification-checked for signatures (§3) — and nothing more. It does not verify proofs, run scan loops, decrypt bundles, or talk to Nostr/Blossom directly; those live in the node (see "Thin-client rule" below). It talks only to the wallet's own node over REST.app(Next.js) — rebuild on top of the rewritten SDK. Migrate the app's crypto path off its in-tree WASM onto the SDK's pure-TS primitives (the SDK exists to be that single implementation), and drive the full user journey (onboard, receive, send with the publisher-fee flow, balance, history, disclosure links) through the SDK against a local node. The app stays thin too: it renders what its node serves and signs with keys it holds; it does not re-implement node-side verification or scanning. The one client-side-crypto exception the spec allows is the stateless explorer applying a bearer view secret to an already-encrypted blob (spec §5) — that is presentation, not wallet trust-reduction.
Thin-client rule (hard, project-wide). zkCoins follows the Bitcoin full-node model: the wallet trusts its own node. There is no anti-node logic in the wallet/SDK/app — no client-side proof verification, no scan loops, no view-key/spend-key consistency checks against a second node, no "node integrity" UI. Anything whose purpose is to reduce trust in the node belongs node-side, or the answer is self-hosting. This rule is stated verbatim in every repo's CONTRIBUTING.md and constrains where each piece of functionality lives. The spec's client-side verification language (§2.3.3, §4.4) is always "the receiver or its node, on its behalf" — i.e. the node does it; the thin client delegates to the node it operates.
Done = node ✓ and sdk ✓ and app ✓, each conformant and the three proven to work together locally (§3, §4 below).
3. Definition of done
A layer is done only when all of the following hold:
- Spec-conformant. Every normative MUST/MUST NOT in
docs/specification.mdthat applies to the layer is implemented. The conformance test vectors (spec test-vector section) are generated and pinned (§4 below), and node and SDK each reproduce the values in their scope bit-for-bit (the SDK the hash- and derivation-level values; thecircuit_digest(C)comes from the node's deterministic §1.7.9 build; the V.5/V.6signaturevectors validate by BIP-340 verification including the sign-to-contract tweak check, not byte equality). - A-to-Z tested end to end. Beyond unit coverage there is a full-journey test that exercises a real flow across all three layers running locally: create three accounts (Alice, Bob; Carol for the token-standard-2 genesis), mint, pay (with a real publisher half-aggregating the transition nullifiers and inscribing the
AggregateStateNullifierV3against a local/regtest Bitcoin), the recipient discovers + verifies + credits the coin, and a confirmation link renders. No mocks on the protocol path — real proofs, real inscriptions, real Nostr/Blossom transport. The A-to-Z suite is a machine-evaluable pass predicate, not a narrative. Fixtures (normative): Alice = the spec V.2-ext mnemonic ataccount' = 0, Bob = the same mnemonic ataccount' = 1, Carol = the same mnemonic ataccount' = 2; assetUSD-Demo,decimals = 2,issuance_version = 1, supply1_000_000_000(the spec V.1/V.3 values); publisher fee1_000of the same asset; Bitcoin regtest with on-demand block mining; every confirmation wait is 6 mined blocks. Steps and assertions (each assertion is a hard pass/fail):- Boot both nodes;
GET /v1/infoon each equals the pinnedcircuit_digests(C,C_balance) and bounds. - Alice mints the asset (
kind: "mint"); job reachescompleted; the mint nullifier is inscribed on regtest and reaches §3.10completedafter 6 blocks; Alice's balance for the asset equals1_000_000_000. 2b. Carol (a fresh account,send_counter = 0) genesis-mints the second assetEUR-Demo(decimals = 2,issuance_version = 2,amount = 500_000_000,cap_total = 500_000_000,terms_salt = terms_salt_fixture, spec V.4) with explicit outputs to Alice (token-standard-2 forbids self-credit in the creating transition, spec §6.5 (f)/(g)); the mint anchors; after Alice's clause-10 receive of the EUR-Demo output reachescompleted, Alice's balances hold two assets, and every later assertion checks the exact full balances map (Requirement 8); Bob's USD-Demo receive (steps 3–5) must leave EUR-Demo untouched. - Alice sends
250_000to Bob (kind: "send") via a real publisher whose fee coin (1_000) rides the same transition; the publisher half-aggregates and inscribes anAggregateStateNullifierV3; the wallet-sideawaiting_signaturerecomputation (spec §7.5) passes. - Bob's node discovers and verifies the coin (the §2.3.3 checks) once its nullifier is
completedat 6 confirmations — the balance write happens in step 5's receive transition; Alice's balance equals999_749_000=1_000_000_000 − 250_000 − 1_000(change accounting exact). - Bob runs the receive transition (
kind: "receive", fold), self-published; it reachescompleted; after it reachescompleted, Bob's balance equals250_000(clause 7 writes balances only in the receive transition, spec §2.3.3 step 7). - A confirmation link for the payment renders and reports the §3.10 state
completed. - Reorg control (V.9 N-09): force a 3-block regtest reorg spanning a
pendingnullifier; after canonical replay both nodes'(size, mth)andnav_root = Hc("NfLog/Root", size ‖ mth)(spec §3.7) equal a fresh full rescan's. - Recovery control (Requirement 6): destroy Bob's node state; restore from seed + regtest chain + replicated blobs; Bob's balance and coin set equal the pre-destruction state.
- Portability control (Requirement 10): repoint Alice's wallet to a freshly synced second node by configuration only; balances and states identical; a send from the new node succeeds.
- Attestation control (Requirement 9(b)): Alice produces a balance attestation for USD-Demo via
POST /v1/attest/balance(spec §7.5); a fresh verifier validates it — proof, host-side anchor checks,nav_ceilingcanonical — against its own scan. - Grant control (Requirement 9(c)): Alice issues a view grant scoped to USD-Demo via
POST /v1/grants; the grantee pulls exactly the in-scope records via the §5.1 challenge–response and cannot pull EUR-Demo records (scope clamp, spec §5.1). The suite passes iff every assertion above holds. No mocks on the protocol path — real proofs, real inscriptions, real Nostr/Blossom transport (unchanged rule above).
- Boot both nodes;
4. Conformance vectors come first
The spec's Poseidon-dependent values are marked <REGEN> because they must be produced, not invented (the spec explicitly forbids guessing Poseidon digests). The first node task is to implement spec §1.7.1/§1.7.2 (Poseidon-Goldilocks + the E(·) field encoding) and the §1.7.9 circuit build, then compute every <REGEN> value (the empty roots, asset_id, ash, coin.identifier, nf, the Merkle roots, and the circuit_digest(C) per network) and submit them back to docs/specification.md as a PR. Once the node produces them and the SDK's independent primitive-level re-implementation (derivation, hashing, signing) reproduces the hash- and derivation-level values bit-for-bit, those values are locked and become the conformance baseline (the SDK cross-Rust parity suite, sdk/test/cross-rust/, builds on them); the circuit_digest(C) is reachable only through the §1.7.9 circuit build and is locked by the node's deterministic build alone, and the V.5/V.6 signature vectors are locked by BIP-340 verification — including the sign-to-contract tweak check (the transition's single S2C over H(ProofData) per spec §3.2, and the half-aggregate verification of §3.3) — rather than byte equality, because BIP-340 nonce derivation includes auxiliary randomness (spec §3.2). There is deliberately no second full protocol implementation; the independent assurance a second client would provide comes from the Assurance Roadmap instead. Do not treat any <REGEN> row as authoritative until it is generated and pinned.
Build report (normative artefact). Runbook steps 1–2 also produce build-report.md in the node repository: measured gate counts and proving times at the §2.5 bounds, measured on-chain sizes at k = 1, 10, 100, max (spec §1.7.10), the per-network circuit_digest values, and the toolchain/commit identifiers. The vectors-pin PR (step 3) links it; every spec/risks reference to 'the build report' resolves here.
5. Working method: autonomous, logged, professional, consistent
Both the implementor and the agent that maintains/completes this spec work the same way:
- Be autonomous. Do not stop to ask which option to take. When the spec leaves a genuine implementation choice (a data-structure shape, an internal module boundary, a library, a test layout), pick the option that is most professional and consistent with the rest of the system, and proceed. Run the work to completion rather than pausing for confirmation.
- Log decisions, don't request them. Every non-obvious choice is recorded — in the relevant repo's
DECISIONS.md(or a PR description / ADR), with a one-line why. The point is a durable trail, not a question. Only escalate to a human if the same issue blocks twice after genuine attempts, or if a choice would change a user-visible or architectural property in a way the spec does not cover — in which case the resolution goes into the spec first. - Spec changes flow through
docs. If implementation reveals a spec gap or error, fix it by a PR todocs/specification.md(and regenerate any affected vectors), then implement against the corrected spec. Code and spec never diverge silently. - Professional & consistent is the tie-breaker. Whenever two designs are otherwise comparable, choose the one a careful engineer would defend in review: consistent naming and structure across node/sdk/app, no half-finished paths, no speculative abstraction, tests that prove behaviour rather than restate it.
Appendix A — Decisions taken while completing the spec
These are the decisions made to close the open implementation questions, recorded here so the rationale is durable. Each is now normative in docs/specification.md; the why is "professional & consistent" applied to the facts found in the node and research repos.
| # | Decision | Where | Why |
|---|---|---|---|
| D1 | Proof system pinned to Plonky2 1.1.0 (crates.io), Goldilocks field, PoseidonGoldilocksConfig, D = 2, cyclic recursion. | §1.7.9 | Already the working, tested choice in node; bit-stable. |
| D2 | Circuit config = standard_recursion_zk_config() (zero-knowledge on); FRI rate_bits 3 / cap_height 4 / pow 16 / 28 queries / ConstantArityBits(4,5), 100-bit FRI security. | §1.7.9 | ZK is mandatory (proofs travel to receivers/scanners and carry nk in the witness — Req 2). The node currently uses the non-ZK config; corrected here. FRI values are the Plonky2 standard the node already runs. |
| D3 | Circuit bounds MAX_TX_INPUTS = 8, MAX_TX_OUTPUTS = 8 (outputs count recipients + change + fee coin); wallet splits larger transactions. | §2.5 | Matches the node's proven MAX_IN/OUT_COINS = 8; covers a payment + change + fee with margin. |
| D4 | On-chain state nullifiers, no publisher circuit (paper model, research#16 / f392fa0): each transition publishes its account-state nullifier (Pkᵢ, Rᵢ) on Bitcoin; a publisher only NISSHAC half-aggregates the signatures (no circuit, no secret keys) into one AggregateStateNullifierV3 inscription; every node rebuilds the accumulator by first-occurrence keyed by Pkᵢ. Reverted from the batched C_batch/BatchInscription/prev_root→new_root design. | §3.1–§3.6, §1.7.10, §2.2 | Restores contention-free self-publish and Bitcoin-only, availability-independent double-spend reconstruction; the batched root-chain could not provide all three of constant size, contention-free publish, and sound first-occurrence without a new trust base. ~64 B/tx asymptotic. |
| D5 | Canonical proof serialization = Plonky2 to_bytes() for all hashed/content-addressed proofs; bincode only for private at-rest storage. | §1.7.9, §3.2 | Portable and stable across implementations; bincode layout is serde-version-fragile and must never enter a content address. |
| D6 | ZBE (chunked ChaCha20-Poly1305, 64 KiB chunks, counter nonce, index-binding AAD) for bundle blobs; plain NIP-44 v2 only for the small control events. | §4.2.1 | NIP-44 v2's 65 535-byte cap cannot carry ~100 KB proof bundles; ZBE reuses the same AEAD primitive family, so no new assumption. |
| D7 | Nostr event kinds fixed: gift-wrap 1059 / seal 13 (NIP-59), delivery rumor 1420, ACK rumor 1421, recipient profile 30420 (addressable, d=address), publisher profile 30421 (addressable, d=op_pubkey). Blossom BUD-01/02 under /blossom. | §7.3, §7.4 | A coherent, collision-free kind block; addressable kinds for the two discoverable profiles; Blossom is the de-facto Nostr content-addressed blob standard, and its SHA-256 key equals the spec's blob_id. |
| D8 | Versioned /v1/ REST API (public projection, submit+proving job handshake with wallet-held signing, capability-gated pull, publisher interface), JSON for control, canonical binary for artefacts, decimal-string big integers. | §7.1, §7.5, §7.6 | Green field allows a clean design; keeps the proven async-job proving model from the node but reframes it onto the spec's submit/pull/publish interfaces and custody boundary. |
| D9 | Fee-coin mechanism: spender picks a publisher from its op-signed profile (flat fee per transition), adds a fee output coin to the publisher's fee_address under the same ocr as the payment, hands over the transition nullifier (Pkᵢ, Rᵢ, sᵢ, R') + fee CoinProof. Atomicity comes from the shared ocr bound by the one nullifier: the publisher cannot collect the fee without anchoring the payment, and an un-anchored transition's fee never reaches completed, so a censoring publisher collects nothing. Re-pick on censorship (or self-publish) is safe (idempotent first-occurrence on Pkᵢ). | §3.8, §7.6 | Trustless, needs no new on-chain field, reuses the coin model exactly. The paper's first-to-publish fee design is noted as a later privacy upgrade. |
| D10 | nk moved out of the SPEND branch to its own hardened branch A/3' and into the operational bundle {ivk, ovk, op, nk, op_secret} held by the wallet's own node. | §1.2, §6.1, §6.2 | The prover derives nullifiers in-circuit from the witnessed nk (§2.1 clause 4), and proving is node-side (thin-client rule) — so the prover must hold nk. It cannot spend; it can link the account's own spends, hence own-node-only. The prior "wallet only" label was unimplementable. |
| D11 | Receive is a real C transition (clause 10): the creating proof is verified in-circuit (PCD across accounts), the coin admitted into the coin-history SMT and balances; MAX_RX_COINS = 4. | §2.1, §2.3.3, §2.5 | Without it, receipts lived outside the recursion — the account's proof attested nothing about held coins, breaking §2.2's transitivity claim and any downstream verification. |
| D12 | Conditional NAV + fifth ProofData field nav_commitment: the fifth public input is a hiding commitment Hc("NavCommit", nav_root ‖ nav_rand) — where nav_root = Hc("NfLog/Root", size ‖ mth) and nav = (size, mth) — to the transition's conditional nullifier-accumulator value nav — the chain-derived accumulator value containing every dependency nullifier (opened only to a coin's recipient/disclosure verifier). Clause 1 carries it forward monotonically (prefix), and clause 10(c) forces every received coin's creating nav to be a prefix of the receiver's own — making anchoring transitive; a receiver checks nav is canonical on its own scan; reorg handling is bounded by the 6-confirmation finality directive (spec §3.9), not a no-op branch. nav_rand = HKDF("zkCoins/v1/NavRand", op_secret ‖ u64-be(send_counter)) keyed by a hardened branch A/4' = op_secret (in the operational bundle), so any node holding the bundle reproduces it (node portability) while it stays unpredictable. serialize(ProofData) = 192 bytes (ProofData is the v1 TransitionEssenceV3). (revised 2026-07-23): serialize(ProofData) is 192 bytes with the sixth npk_commit field; the nullifier accumulator is an RFC-6962 append-only Merkle log — see spec §1.4, §1.7.6, §2.1. The conditional NAV is set to the shared size_final prefix (the ≥6-confirmation-final prefix, not the live chain tip) — not a per-account minimal-covering size; v1 has no early build against still-pending dependencies, so a not-yet-final dependency means the wallet waits until it is final before building/proving (spec §2.3.2 step 5, §3.9). | §1.4, §2.1, §3.9 | Closes the collusion-inflation hole transitively (per-hop out-of-circuit checks are not), bounds reorg finality at 6 confirmations (v1 deliberately does not adopt the paper's arbitrary-depth no-op — see Paper-Deviation Analysis D-16, issues #105/#106), and hides receive-recency (a decodable public root would leak it). op_secret is a dedicated branch so nav randomness never shares key material with the Nostr signature. Replaces the retired anchors-MMR mechanism, re-based directly onto the chain-derived nullifier accumulator (no separate structure). |
| D13 | detect_tag/epk as cleartext tags on the outer gift-wrap event (zkdt/zkepk); the rumor carries only {blob_id, blob_locators, ack_nonce}. | §4.2, §4.4, §7.3 | The promised 1-ECDH + 1-Poseidon scan without AEAD work is impossible if the tag sits inside the NIP-44-encrypted seal. Both values are per-coin fresh and random-looking — no linkability is added. |
| D14 | out_ciphertext mechanism for ovk: per outgoing coin, K_out = HKDF("zkCoins/v1/OutKey", ovk ‖ epk) encrypts K_tx; stored in the self-delivered record. | §1.3, §4.2, §5.8 | ovk was referenced everywhere ("recover outgoing plaintext", zkavk = ivk ‖ ovk) with no defined mechanism; the Sapling-style out-ciphertext supplies it and grounds the incoming-only (ivk, 32 B) vs full (ivk ‖ ovk, 64 B) zkavk variants. |
| D15 | The public double-spend view is on Bitcoin: the on-chain state nullifiers (Pkᵢ, Rᵢ) are the only public objects a scanner needs — there is no off-chain consensus object. ZBE applies only to the private CoinProof bundles (custody/bearer recovery), which are k = 3-replicated. | §3.6, §4.6, §4.2.1 | The retired batched design put the double-spend set in an off-chain BatchBundle, coupling admission to data availability; the paper model reconstructs it from Bitcoin alone, so replication claims are reserved for private bearer recovery only. |
| D16 | Spec wording aligned to the thin-client rule: Requirement 4's verifier is "the receiver, or its node on its behalf"; §6.2/§6.3 attribute verification to the wallet's own node; foreign-node-only wallets get fail-closed discrepancy detection, not client-side proof verification. | §6.2, §6.3, §6.7 | The old §6.2/§6.3 text ("the wallet MUST verify against Bitcoin") contradicted this hard project rule (see "Thin-client rule" above and every repo's CONTRIBUTING.md). |
If any of these is contradicted by a hard implementation constraint, change it in the spec first, regenerate affected vectors, then implement (a change to a frozen element is a version bump, spec §1.7.8).
Path to mainnet (normative runbook)
The ordered, machine-followable sequence from first build to mainnet activation. Each step names its inputs, outputs, and a hard pass predicate; a step starts only when every earlier step's predicate holds. There is no human-gated step (project decision 2026-07-22; Assurance Roadmap) — "PR merged" steps are repository mechanics, not review gates.
| # | Step | Inputs | Outputs | Pass predicate |
|---|---|---|---|---|
| 1 | Implement §1.7 primitives + §1.7.9 circuit build (node) | spec §1.7, §2 | node builds C and C_balance deterministically for regtest/testnet/mainnet tags | two clean-checkout builds of the same commit produce identical circuit_digest(C) and circuit_digest(C_balance) per network tag |
| 2 | Generate every <REGEN> value + the V.8 byte checks | step 1 binary | the filled V.2–V.6/V.8/V.11 values; build-report.md (measurements, §4) | node reproduces every already-pinned SHA-256/Bech32m/V.8 byte exactly; all Poseidon <REGEN> cells produced; a second run reproduces them bit-for-bit |
| 3 | Vectors-pin PR to docs | step 2 values | spec with all byte-pinnable <REGEN> cells filled; F-08 status matrix filled with commit links | CI green; diff touches only vector cells + status matrix; digests in the PR equal step 1's; zero <REGEN> placeholders remain except the explicitly non-byte-pinned cells (V.5/V.6 signature values — locked by verification, and the deployment-specific block_anchor) |
| 4 | SDK primitive parity | pinned spec | sdk/test/cross-rust/ suite | every V.7-parity-matrix "byte-equal" row reproduced bit-for-bit by the SDK; V.5/V.6/V.8 verification rows pass |
| 4b | Full protocol stack | steps 1–4 | complete node (REST §7.5, publisher §7.6, Nostr/Blossom §7.3–§7.4, kernel §7.8), SDK, app | every §7.5/§7.6 endpoint serves its normative shape against a local instance; all unit/integration suites green |
| 5 | Negative controls | pinned spec | V.9 suite in node (+SDK where in scope) | every V.9 signing/encoding case (N-01–N-08, N-11–N-17) rejects with the named reason, zero accepts; N-09/N-10 (regtest reorg controls, needing a live chain) run at step 6, not here; every V.11 negative control (NL-1–NL-7) rejects; the D-05 V.11 differential-test against the independent RFC-6962 reference passes |
| 6 | A-to-Z suite on regtest | steps 1–5 | the §3 machine pass predicate | every numbered assertion of the §3 A-to-Z suite holds, including the reorg controls (N-09: canonical replay converges — the accumulator value equals a fresh full rescan's; N-10: the node detects the displacement and /health/ready stops reporting ready), restore (Req 6), and portability (Req 10) controls |
| 7 | Public testnet | steps 1–6 green | testnet deployment (network tag zkCoins/v1/testnet, running against Bitcoin Signet — the maintained public test chain; the retired testnet3 MUST NOT be used) | Assurance gate "Public testnet" — all listed criteria hold; the A-to-Z journey — minus the regtest-only controls (forced reorg N-09/N-10 and on-demand mining), which remain step 6 — passes against Signet with natural block cadence |
| 8 | Mainnet gate check | steps 1–7 | gate checklist run | every step 1–7 output is green — a machine-evaluable checklist, not a human sign-off: V.4/V.8/V.11 vectors filled and byte-equal in the V.7 parity matrix (step 4); V.9 and V.11 negative controls all reject, zero accepts (step 5); the §3 A-to-Z suite's numbered assertions all hold on regtest (step 6) and on public testnet (step 7); the D-05 V.11 differential-test against the independent RFC-6962 reference passes; remediation Gates A–C all closed (Paper-Conformance Remediation); full detail in Assurance gate "Real value (mainnet)" |
| 9 | Genesis + deployment | step 8; an operator-provisioned Bitcoin wallet funding the publisher's inscription fees (BTC UTXOs, spec §3.8); the §6.1 compose stack configuration | mainnet node config: pinned digests for tag zkCoins/v1/mainnet, bitcoin_network = mainnet, SECURITY.md present in the repo with a monitored contact (an operational duty, not a review gate); the operator (a) broadcasts the genesis inscription (the network's first zkCoins nullifier inscription — the mint of the genesis/first asset, spec §2.3.1), (b) once it confirms, reads its Bitcoin block height as the pinned activation_height (spec §3.6), (c) assembles network-params.json = the pinned tuple { network_tag, circuit_digest(C), circuit_digest(C_balance), activation_height, finality_confirmations = 6 } in its canonical encoding (spec §3.6) and pins its SHA-256, and (d) publishes network-params.json so it is identical across all nodes | GET /v1/info on the deployed node returns network = mainnet, the pinned digests, finality_confirmations = 6, and the pinned activation_height (§3.6) — identical to the published network-parameter value; the published network-params.json's SHA-256 matches the pinned digest; and /health/ready is 200 with scanner_lag = 0 after initial sync |
| 10 | Mainnet activation | step 9 | first real-value issuance/transition | the first mainnet transition completes the full §3 journey (mint → send → receive → confirmation link) with every §3.10 state reaching completed; the v1 freeze has been in force since the vectors pin (step 3, spec §1.7.8); from this point breaking it would strand real value |
Activation is deliberately unceremonial: mainnet "activation" is nothing more than the first real transition against the mainnet network tag after step 9 — there is no switch to flip and no coordinator. Rollback before step 10 is free (nothing carries value); after step 10 a version bump additionally carries real-value migration consequences.