Settlement contract and proof interface

Draft protocolSource at 169cbd2 ↗Source SHA-256 63c28d2…f35
Status
Draft normative companion; implementation design
Specification
Revision 14, 2026-09-22
Library baseline
Commonware v2026.9.0

This companion fixes the settlement contract, its storage layout, the amount representation, and the proof guards that SPECIFICATION.md section 16 depends on. The specification takes precedence. This document defines requirements, not an implemented or deployed contract; compiler artifacts, live deployment pins, and executable qualification remain separate gates.

Fixed design requirements:

  • Makechain uses a dedicated beneficiary-cumulative settlement contract on Tempo Moderato for devnets and qualification testnets and a separate deployment on Tempo mainnet. The existing deployment and its receipt-proof flow remain intact.
  • Every Makechain credit comes from a real deposit and authentic Tempo proof. Makechain has no genesis credit allocation, local faucet credit, discretionary grant, operator mint, or mocked positive verifier. Tokens obtained through a Tempo testnet faucet may be deposited normally; the deposit and proof remain real Tempo state.
  • The only launch asset is pathUSD. One pathUSD base unit is one microdollar and one Makechain credit unit. A payer may fund a different beneficiary.
  • Consensus verifies supplied evidence without Tempo RPC. RPC is an external acquisition mechanism, never a consensus oracle.
  • Each network launches a non-proxy contract with immutable logic. Deposit pausing, administrator rotation and treasury withdrawals remain operational state transitions in that fixed logic and cannot change any beneficiary cumulative.

The selections below are reviewable through the architecture decision process. Deployment addresses, code hashes, compiler output, and trusted checkpoints must come from qualified artifacts, never fabricated placeholders. Evidence-bound values are a provisional devnet profile and require fixture qualification before a deployed chain uses them.

1. Contract boundary

1.1 Code identity

Immutable logic. Deploy a non-proxy contract independently on Moderato and mainnet. Keep operational authority in ordinary storage so deposits can be paused and the administrator can rotate without changing code. A separate two-step treasury-role rotation keeps operational key replacement independent of code replacement. Fixed logic lets the treasury withdraw deposited pathUSD, but pausing, role changes, and withdrawals cannot write a beneficiary cumulative. Only a real deposit of the fixed accepted asset may raise one. Do not include delegatecall, an implementation slot, an upgrade function, an asset setter, a grant/mint path, or a cumulative decrement/reset path.

This makes one authenticated contract account and one pinned runtime code hash sufficient for code identity. It also makes a code defect expensive: a fix requires a new deployment and the separately reviewed cumulative-migration procedure discussed in section 5, preserving every beneficiary cumulative exactly. The operational roles and their storage remain mutable under the fixed code; that mutability is not a code-update path. No administrative operation can write the monetary mapping.

1.2 ABI contract

The interface below is fixed for this design. Constructor arguments are (address initialAdmin, address initialTreasury) in that order. Errors raised by this contract are Unauthorized(), InvalidAddress(), InvalidAmount(), InvalidState(), TransferMismatch(), and ReentrantCall(). A failed external token call reverts the whole operation; it never produces a successful deposit. A change to this interface requires an explicit contract-design revision.

interface IMakechainSettlementV2 {
    error Unauthorized();
    error InvalidAddress();
    error InvalidAmount();
    error InvalidState();
    error TransferMismatch();
    error ReentrantCall();
    event Deposited(
        address indexed payer,
        address indexed beneficiary,
        uint256 microdollars,
        uint256 beneficiaryLifetimeMicrodollars
    );
    event DepositsPaused(address indexed admin);
    event DepositsUnpaused(address indexed admin);
    event AdminTransferStarted(address indexed currentAdmin, address indexed pendingAdmin);
    event AdminTransferred(address indexed previousAdmin, address indexed newAdmin);
    event TreasuryTransferStarted(address indexed currentTreasury, address indexed pendingTreasury);
    event TreasuryTransferred(address indexed previousTreasury, address indexed newTreasury);
    event Withdrawn(address indexed treasury, uint256 microdollars);

    function PATH_USD() external view returns (address);
    function cumulativeMicrodollars(address beneficiary) external view returns (uint256);
    function depositsPaused() external view returns (bool);
    function admin() external view returns (address);
    function pendingAdmin() external view returns (address);
    function treasury() external view returns (address);
    function pendingTreasury() external view returns (address);

    function deposit(address beneficiary, uint256 microdollars)
        external
        returns (uint256 beneficiaryLifetimeMicrodollars);
    function pauseDeposits() external;
    function unpauseDeposits() external;
    function beginAdminTransfer(address nextAdmin) external;
    function acceptAdmin() external;
    function cancelAdminTransfer() external;
    function beginTreasuryTransfer(address nextTreasury) external;
    function acceptTreasury() external;
    function cancelTreasuryTransfer() external;
    function withdraw(uint256 microdollars) external;
}

PATH_USD is a compile-time constant, not constructor-selected or mutable state. The constructor rejects zero or self initial admin/treasury addresses. It starts with deposits paused, both pending roles zero, all cumulatives absent/zero, and reentrancy status 1 (not entered). The administrator unpauses after deployment review. Admin and treasury transfers are two-step and reject zero, self, and the current role holder as destinations. The current admin alone pauses/unpauses deposits and starts an admin transfer. The current treasury alone starts a treasury transfer. In each case the nominated address alone accepts its role. The current treasury alone calls withdraw, and the destination is always the current treasury address rather than a caller-supplied address. Pause affects only deposit; proof reads, role recovery, and treasury withdrawal remain available.

The split roles are deliberate. The admin can stop new exposure and rotate operations but cannot raise a beneficiary cumulative or withdraw funds. The treasury can withdraw pathUSD but cannot change the admin, pause state, deposit semantics, or cumulatives; it can transfer its own role. Starting a new transfer replaces that role's prior pending nomination, and acceptance clears it. A current role holder may cancel its pending transfer through cancelAdminTransfer() or cancelTreasuryTransfer() respectively; cancellation requires a nonzero pending nomination, clears it, and emits the corresponding *TransferStarted(current, address(0)) event. Acceptance requires the caller to equal a nonzero pending nominee. There is no role-renunciation operation. Repeated pause or unpause calls that would not change state revert InvalidState(). A wrong caller reverts Unauthorized() before any role mutation. Admin and treasury may be the same address in a disposable devnet; production custody must explicitly review role separation. Role changes and withdrawals are operational events, never Makechain credit evidence.

1.3 Deposit semantics

For deposit(beneficiary, requested) with payer = msg.sender:

  1. Require deposits unpaused, beneficiary != address(0), and requested > 0.
  2. Read old = cumulativeMicrodollars[beneficiary]; require requested <= type(uint256).max - old, reverting InvalidAmount() otherwise. Read before = pathUSD.balanceOf(this).
  3. Execute pathUSD.transferFrom(payer, this, requested) and require the token call to succeed.
  4. Read after = pathUSD.balanceOf(this). Reject after < before and require after - before == requested. The received balance delta, not calldata or an event, is the paid amount.
  5. Write old + requested, emit Deposited, and return that value. Both deposit and withdraw share the same reentrancy guard, entered before any token call and restored only on success; a revert restores all contract and token effects. The entered status is 2. Token calls use the pinned standard safe ERC-20 call handling; balance deltas are still checked explicitly.

The payer and beneficiary are separate facts. Anyone may pay for any nonzero beneficiary; the beneficiary does not sign and may equal the payer. The beneficiary mapping records pathUSD base units, with no price multiplication, unit conversion, expiry, refund, or transferable claim. A misdirected deposit is therefore permanent from Makechain's perspective.

The beneficiary is one raw 20-byte address whether it is a Tempo EOA or a Tempo configurable account. Settlement proof verification neither derives that address from owners nor inspects Tempo keychain/configuration state, account code, or the deposit transaction's authorization. Makechain's separate account-genesis derivation and authority rules decide who may later control the matching Makechain account; they are not part of this monetary proof.

The exact-received check rejects fee-on-transfer, short-transfer, and false-success behavior. A Deposited log helps operations but is not settlement authority: no receipt or event proof may substitute for the contract account and mapping-slot proof.

withdraw(amount) requires the treasury caller, positive amount no greater than the current token balance, and the common reentrancy guard. It captures the current treasury as destination, transfers pathUSD only there, requires token-call success and an exact contract-balance decrease of amount, emits Withdrawn, and never reads or writes a cumulative. Withdrawn reports contract outflow; recipient net receipt relies on the fixed pathUSD semantics and is not an independent credit claim. Withdrawing the whole token balance is allowed; the mapping is a lifetime paid frontier, not an escrow balance. A direct pathUSD transfer to the contract is a donation: it raises the withdrawable balance, gives no beneficiary credit, and does not affect a later deposit because that deposit uses its own before/after delta. The ABI has no generic arbitrary-token sweep.

Contract depositsPaused and Makechain imports_paused are independent controls. Contract pause stops new Tempo deposits but does not erase cumulatives or invalidate an already captured state proof. Makechain pause rejects both credit imports and checkpoint refreshes but cannot stop Tempo deposits. Operators coordinate them during an incident; consensus never treats one flag as proof of the other.

1.4 Storage layout

Use no inherited storage and no storage gap. The compiler storage-layout artifact must match:

Slot Solidity value Proof relevance
0 mapping(address => uint256) cumulativeMicrodollars The sole monetary storage proof
1 address admin Operational only
2 address pendingAdmin Operational only
3 address treasury Operational only
4, bytes 0..19 address pendingTreasury Operational only
4, byte 20 bool depositsPaused Operational only; Solidity packs it after the address
5 uint256 reentrancy status Operational only

Byte offsets in the table use Solidity storage-layout offsets from the low-order end of the slot, not left-to-right positions in a displayed big-endian hex word.

The cumulative width is the full uint256 word. It wastes no additional mapping storage, avoids a narrowing conversion, and makes both contract and verifier overflow rules explicit. For beneficiary address b, the logical EVM storage slot is:

mapping_slot(b) = keccak256(left_pad_32(b) || uint256_be_32(0))

The Ethereum storage-trie lookup key is then keccak256(mapping_slot(b)). These are two distinct Keccak operations. The value is the canonical RLP integer stored at that trie key, not the 32-byte Solidity word or the JSON-RPC value field. Zero is represented by absence in Ethereum state; an absent/zero value can never satisfy a positive strict-advance import.

Only slot 0 participates in the monetary proof, but the complete compiled layout must still match the reviewed artifact. Slot 0, uint256, the compiler version/settings, linked-library absence, constructor scheme, exact runtime bytes, and runtime code hash must be frozen together. A getter returning the expected value does not prove the slot layout.

Makechain uses unsigned 256-bit Amount values for credited, spent, locked, fees, deposits, and signed charge ceilings. Canonical protocol encoding is exactly 32 big-endian bytes; EVM storage proofs retain their canonical RLP integer representation. All monetary arithmetic is checked, including products and aggregated charges, and conservation checks use checked sums. No narrowing conversion or truncation is permitted. This keeps every contract cumulative representable; a narrower ledger would otherwise strand a beneficiary after its external cumulative exceeded the local range. Counters, times, and collection lengths retain their separate schema widths.

2. Settlement source profile

Each Makechain chain binds one complete canonical static profile and keeps the already specified mutable anchor in consensus state. A network name or enum is insufficient. The split is:

SettlementSourceProfile {             // static, changed only by a profile release
    profile_id,
    tempo_chain_id,
    certificate_codec_id,
    header_codec_id,
    settlement_contract,
    settlement_runtime_code_hash,
    contract_layout_id,
    cumulative_mapping_base_slot,
    cumulative_value_bits,
    accepted_asset { address, currency, decimals },
    trusting_period_ms,
    max_source_clock_skew_ms,
    evidence_limits,
}

SettlementAnchor {                    // existing mutable consensus-state role
    identity { group_key, from_epoch, installed_at_makechain_height },
    checkpoint { tempo_height, tempo_timestamp_ms, accepted_at_makechain_timestamp_ms },
    imports_paused,
}

The active identity includes a fixed-size from_epoch: u64, the first Tempo epoch whose certificates are admissible under this key. Every credit or refresh certificate must have epoch >= from_epoch. Bootstrap and anchor replacement install the key and its reviewed source epoch floor together; refresh never changes either. Genesis bootstrap accepted-at time equals the explicit Makechain genesis timestamp. Release installer input excludes Makechain installation height and accepted-at time: execution derives them from the activation block. Its profile ID comes from the activated effective profile. A simultaneous import-pause delta must match the installer pause value or the descriptor is invalid. Rule resolution applies these effects before checking any command in the activation block. This matches Tempo's source identity contract and the reference codebase verifier's epoch gate. The floor is not the epoch of Makechain installation, is not inferred from a submitted proof, and does not follow Tempo autonomously.

All settlement times use Unix milliseconds: trusting_period_ms, max_source_clock_skew_ms, checkpoint timestamps, and Makechain consensus time are unsigned 64-bit millisecond values. Duration values are explicit profile inputs. All additions and conversions fail on overflow.

tempo_chain_id selects and domain-separates the configured external source; it is not a fact read from a finalization certificate or Tempo header. Tempo's consensus signing namespace is the literal TEMPO and is not network-bound. The certificate authenticates its proposal payload under the pinned group key. Therefore each deployment/anchor-replacement review must establish that the installed group key is unique to the selected Tempo network throughout the epochs accepted by that anchor. A network label or from_epoch alone cannot prevent replay if two networks reuse the same group key with overlapping eligible epochs. A profile that cannot establish that separation is not deployable under this certificate format; it needs an authenticated network discriminator and a reviewed new profile. Within this trust assumption, the identity/checkpoint and contract/code/slot pins bind the accepted source. Add a cross-network authentic-certificate negative vector to every profile qualification. Makechain's own chain_id remains in the Makechain command envelope; it is also not supplied by the Tempo proof.

The inspected Tempo revision provides these candidate network facts, but it does not provide the settlement deployment:

Profile Verified repository fact Deployment fields still absent
Moderato chain ID 42431; pathUSD 0x20C0000000000000000000000000000000000000; current compiled identity begins at epoch 51 Settlement contract address, runtime bytes/hash, deployment block/checkpoint, authentic account/storage proof
Mainnet (PRESTO in the source tree) chain ID 4217; the same pathUSD address; current compiled identity begins at epoch 0 Settlement contract address, runtime bytes/hash, deployment block/checkpoint, authentic account/storage proof

All TIP-20 tokens use six decimals at the inspected revision, so one pathUSD base unit is one microdollar. The contract address and code hash rows must remain unset until an actual reviewed deployment supplies them. No address in this document is a proposed settlement deployment address.

The pathUSD address and denomination are fixed, but token behavior remains a trust dependency on Tempo's protocol and the asset issuer; this profile does not separately prove or freeze token implementation storage/code. A Tempo change that alters transfers or that address's asset semantics requires profile requalification and pause where compatibility is uncertain. Pinning the settlement contract's code does not remove that dependency.

An identity may have rotated since the inspected source snapshot. Activation therefore verifies the identity and checkpoint against the reviewed deployment record rather than copying a repository constant blindly.

3. Canonical proof interface

The selected certificate source is Tempo 25b216a661c2cac72807174583371b582b75c286, crates/consensus/src/finalization_verifier/mod.rs:9-24,89-104, backed by Commonware 2026.9.0: Finalization<Scheme<commonware_ed25519::PublicKey, MinSig>, TempoBlockDigest>. The last name is a Makechain external wrapper with the same fixed 32-byte codec as Tempo's consensus::Digest(B256); it carries Tempo's Keccak header hash, not a Makechain block hash. The namespace is the exact bytes TEMPO (crates/consensus/src/config.rs:46). These type/profile choices are defined; compile and authentic deployed-source vectors remain required. Earlier 2026.7.1 fixtures do not qualify the settlement contract or prove the current deployed network's codec.

The credit variant carries only self-contained proof material plus canonical framing. It does not carry trusted RPC interpretations:

TempoCumulativeProof {
    settlement_profile_id,
    finalization_certificate_bytes,
    tempo_header_rlp,
    contract_account_proof_nodes[],
    beneficiary_storage_proof_nodes[],
}

The permissionless Makechain system envelope already names the beneficiary; IMPORT_CREDITS has no account signature. The verifier reconstructs the contract account key and beneficiary slot from that beneficiary and the active profile. It does not accept a relayer-supplied account address, storage root, code hash, slot, cumulative, header hash, chain ID, or beneficiary as an authoritative field. An eth_getProof response may be a capture format, but its JSON fields are not consensus facts; only the canonical proof nodes verified under the certified state root establish those facts.

3.1 Verification sequence

Verification is pure, deterministic, and performs no RPC:

  1. Before cryptography, require the exact active profile ID and enforce every byte/count bound. Reject zero, surplus, reordered, duplicate, unconsumed, or noncanonical proof nodes. Decoder selection comes from the profile; there is no in-band version sniffing.
  2. Evaluate the existing Makechain pause and prior-checkpoint dual trust deadline before accepting evidence under the identity. Decode the finalization certificate exactly, require certificate.epoch >= anchor.identity.from_epoch, and BLS-verify it under the active anchor's group key and the static profile's certificate codec. Its authenticated result is the proposal payload/block hash, not a chain ID.
  3. Decode exactly one Tempo header from tempo_header_rlp, reject trailing bytes, require canonical decode/re-encode equality, and require keccak256(tempo_header_rlp) to equal the certified payload. Extract height and EVM state root. Require timestamp_millis_part < 1000 and compute tempo_timestamp_ms = checked(inner.timestamp_seconds * 1000 + timestamp_millis_part). Do not use saturating arithmetic or compare seconds to Makechain milliseconds. Enforce the existing future-skew and checkpoint timing rules on this normalized value.
  4. Against header.state_root, verify the contract account inclusion path at keccak256(settlement_contract_bytes20). Reject account exclusion. Decode the canonical Ethereum account leaf [nonce, balance, storageRoot, codeHash], require the proved codeHash to equal the pinned immutable runtime code hash, and take only its proved storageRoot forward.
  5. Recompute mapping_slot(beneficiary) from section 1.4 and verify its inclusion against that storage root using trie key keccak256(mapping_slot). Reject a proof for another beneficiary, base slot, contract, storage root, or leaf. Decode exactly one canonical unsigned RLP integer, require it to fit the profile's 256-bit width, and call it proved_cumulative.
  6. Require proved_cumulative > AccountLedger.credited. On first import, create the ledger, set credited = proved_cumulative, charge the normal first-ledger creation fee to spent, and require the resulting ledger invariant. On later imports set credited = proved_cumulative; never add the full cumulative and never reduce credited, spent, or locked.
  7. If the proof header is strictly newer than the current Tempo checkpoint and satisfies the normal refresh rules, update the checkpoint in the same successful state batch. An older qualifying credit proof may advance a beneficiary without moving the checkpoint. Any failure leaves both ledger and checkpoint unchanged.

A refresh-only import stops after certified-header verification and the normal strict checkpoint advance. It contains no account or storage proof and has no monetary effect.

At command execution time, compute the existing dual deadline as min(checked(checkpoint.accepted_at_makechain_timestamp_ms + trusting_period_ms), checked(checkpoint.tempo_timestamp_ms + trusting_period_ms + max_source_clock_skew_ms)) and require the Makechain block timestamp to be strictly before it before authenticating the new evidence. Also require the proved tempo_timestamp_ms not to lead the Makechain block timestamp by more than max_source_clock_skew_ms. These comparisons use one width and one unit.

The reference codebase's Tempo verifier is reusable only for a qualified version of the certificate decode, BLS verification, and certified-header binding steps. Its receipt-trie inclusion, Rent event decode, per-log claim identifier, and replay scheme are deliberately absent from this interface. Its old claim result exposes only inner.timestamp seconds; this interface must instead validate and combine Tempo's separate millisecond part as above.

The account path preimage is exactly the 20 contract-address bytes. The storage mapping preimage is exactly two 32-byte ABI words, and its secure-trie key hashes the resulting 32-byte mapping slot once more. Fixtures must catch 20-versus-32-byte account hashing, address left-padding, base-slot endian, missing second Keccak, raw-32-byte versus RLP leaf interpretation, and trailing RLP bytes.

3.2 Provisional devnet evidence bounds

Bounds are consensus parameters in the profile and are checked before allocation, RLP traversal, hashing, or BLS verification. The following is the initial devnet configuration candidate, pending qualification against the first authentic settlement fixture and adversarial vectors:

Item Devnet candidate maximum
Finalization certificate 512 bytes
Tempo header RLP 4 KiB
Account proof 65 nodes, 1 KiB/node, 64 KiB total
Storage proof 65 nodes, 1 KiB/node, 64 KiB total
Complete credit evidence 136 KiB encoded, including framing

The 65-node candidates follow the 64-nibble secure-trie keys and one terminal node; the 1 KiB per-node cap is intentionally above an ordinary 17-item branch RLP. These values become fixed only after the actual deployed account/storage proof passes and worst-case canonical paths are generated. Changing a bound or proof codec requires a profile release; a node-local limit is invalid.

4. Rejected launch alternative: proxy

A proxy was considered because retaining its storage would simplify code replacement. It was rejected for launch because an upgrade authority could change deposit and cumulative semantics, and safe verification would require proving and pinning the proxy code, EIP-1967 implementation slot, and implementation account and code identity in addition to the cumulative. Even then, every allowed implementation change would require a Makechain profile release. A proxy is not an active launch fallback.

5. Successors, reset, and migration

Makechain keeps one lifetime credited frontier per beneficiary, not one frontier per settlement contract. Installing a successor therefore does not create a fresh namespace. At a frozen cutover, each successor baseline must equal the predecessor cumulative for that beneficiary. A lower/reset baseline withholds or delays credit for real post-cut deposits until the new cumulative overtakes the already imported frontier. A higher baseline creates unpaid credit. “At least the predecessor” is therefore too weak.

The cutover also must ensure that deposits accepted by the predecessor after its snapshot are not lost and that the successor adds only actual post-cut deposits. The launch ABI above has no deprecation, migration, or baseline-write call. An immutable mapping cannot prove global migration completeness in constant work, and a Makechain release installer cannot scan all beneficiaries. A future successor needs a separately reviewed cutover mechanism and qualification evidence showing exact frontier carry-forward and deposit continuity. Possible mechanisms have different trust and complexity costs; none is selected here. Governance attestation alone is the current specification's trust boundary, not an on-chain proof of completeness. Until a released migration design exists, a code defect that requires replacement is an explicit settlement-import blocker, not permission to reset the frontier silently.

A pre-mainnet Makechain reset is different. It creates a new Makechain chain_id and empty local ledger. An already captured Tempo proof may be imported again only if it independently satisfies the new chain's active settlement profile and is wrapped in a new-chain command envelope. That is a new chain deriving credit from the same external lifetime fact; it is not automatic state migration and does not make pre-reset chain signatures, receipts, or proofs valid.

6. Required contract and verifier vectors

End-to-end positive settlement qualification is never synthetic. Moderato qualification requires an actual call to the deployed settlement contract, a real successful pathUSD transfer, an authentic certified Tempo header, and authentic contract-account and beneficiary-storage proofs captured for that header. The fixture manifest also records the network/profile ID, deployment address, runtime bytes and code hash, transaction hash for provenance, beneficiary, mapping slot, old/new token balances, expected cumulative, header height and timestamp, certificate bytes, all proof nodes, capture tool revision, and capture time. Consensus uses the certificate/header/account/storage evidence, not the transaction hash or RPC assertions.

Generated local MPT/RLP fixtures remain appropriate for decoder, trie-boundary, overflow, and negative unit tests, and contract tests may use controlled token/test state. They do not qualify a live anchor or a funded end-to-end import and must be labelled as generated test vectors rather than captured Tempo evidence.

Required positive and boundary cases include:

  • payer equals beneficiary and payer differs from beneficiary;
  • first import, later strict advance, multiple deposits before one import, the largest practically constructible in-profile boundary value, donation before deposit, withdrawal before proof capture, and pause/unpause around deposits;
  • account and storage proofs at varied trie depths, including inline and hashed child forms; and
  • byte-identical decode/re-encode and equal native/wasm results under the pinned profile.

Required rejection cases include:

  • wrong profile, group key, epoch floor, certificate bytes, certified payload/header hash, checkpoint height rule, expired trust, or excessive source clock skew;
  • malformed, noncanonical, trailing, oversized, surplus, missing, reordered, or duplicated proof material;
  • excluded/wrong contract account, wrong code hash, proxy code under an immutable profile, wrong account key preimage, wrong state root, or malformed account leaf;
  • wrong beneficiary, mapping base slot, padding/endian, storage root, missing second Keccak, storage exclusion/zero, malformed leaf RLP, value width overflow, equal cumulative, or lower cumulative;
  • a valid Deposited event/receipt without the account/storage proof;
  • zero beneficiary/amount, false or short token transfer, fee-on-transfer token in contract-unit tests, arithmetic overflow, unauthorized role transition/withdrawal, and reentrancy; and
  • failed credit import attempting to refresh the checkpoint or mutate any ledger field.

Mainnet activation requires its own authentic mainnet fixture and deployment manifest. A Moderato fixture cannot establish a mainnet contract address, code hash, identity, checkpoint, or storage proof.

The evidence behind these selections is recorded in architecture evidence (external site) section 12.