Final Testnet浏览器 K_J · Final Testnet · 48359
zh-Hans

合约

0x389481ec0e06b540df6ff7856f4c1e68b2db8cce

地址
0x389481ec0e06b540df6ff7856f4c1e68b2db8cce
类型
已验证合约 FinalBundleLog
余额
0 vETH
Nonce
1
代码
11,619 字节 codehash 0x3a6c46631e4b23970130f9d098e7df5fbf51dfa496681abe92d0ad5fdcaa1cad

账户树

1 · 账户
存在
无叶
0x872cdece1607ee882a717ebc215b6e0329e180cf71641caec04f0b44353dfc1e
实时根
0xeae723253d5f6a608807aa960f2b55066f9694cd06953d238148b49b400dce61
此地址在账户树中没有叶。每个 Final Wallet — 包括服务身份 — 都有一个,因此没有叶意味着这是普通账户,而非钱包。
交易事件代币转账合约

源码 已验证

合约
FinalBundleLog 完全匹配 · immutables 已掩码
编译器
v0.8.33+commit.64118f21
优化器
已启用 · 200 次运行
EVM 版本
prague
验证时间
2026-09-10T07:09:58.770Z
来源
preverify-final-chain (forge artifact, bytecode compared against live code)

contracts/finalchain/FinalBundleLog.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy and operate this bundle log as part of a
//    Final DeFi Protocol chain, and may append to it under the quorum the
//    chain recognises.
// 2. Integrators, relayers, and node operators may read its historical roots,
//    request inclusion proofs at any past size, and independently re-verify
//    any anchor it produced, as part of their integration with the Final DeFi
//    Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this bundle log or a competing post-quantum
//    anchoring plane derived from it without permission prior to the Change
//    Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

import {FinalMmr} from "../utils/FinalMmr.sol";
import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalIntentLog} from "./FinalIntentLog.sol";
import {FinalBundleTree} from "../utils/FinalBundleTree.sol";
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalPlaneSweep} from "./FinalPlaneSweep.sol";

/**
 * @title FinalBundleLog
 * @notice The PQ bundle append-only log, on Final Chain.
 *
 * Deployed on **Final Chain**, one of the logs the state plane publishes, and
 * the one the PQ anchor depends on.
 *
 * ## Why this exists at all
 *
 * `PqAnchorModule` has always said where this log lives: the head
 * (`masterRoot` @ `mmrSize`) is "advanced by the Final-chain validator set as
 * it folds newly-finalized bundles into the MMR". The log itself was never on
 * a chain. It lived in a Redis list maintained by a single `mmr-index` worker,
 * because Final Chain did not exist and the backend was standing in for it.
 *
 * That stand-in is what this replaces. The MMR anchor is the **sole
 * authorization for PQ execution** — a bundle executes because its root is
 * proven under `masterRoot`, and nothing else vouches for it — so the question
 * "what is leaf 41?" is a question about custody, and it was being answered by
 * a list in a cache that one worker held a lock on.
 *
 * ## The log answers; nothing folds an MMR to ask it
 *
 * The obvious cheaper design is to emit leaves and let each reader fold them
 * into an MMR itself. It is wrong for this log. Every co-signer would derive
 * the root it is about to attest to, so a quorum of correct signers would be
 * attesting to their own agreement about a computation rather than to a fact
 * the chain states — and a subtly divergent folder in one implementation is
 * indistinguishable from a dishonest one.
 *
 * So the chain states `(root, size)` — and it also states the PROOF. `proofAt`
 * and `rootAt` answer for any historical size, so a consumer that needs "leaf
 * 41 under the root the gateway anchored" makes one `eth_call` and holds no
 * MMR of its own. That closes the last place two implementations of this
 * construction had to agree: the backend used to replay `BundleAppended`,
 * re-fold every leaf in JavaScript and build the audit path there, which is a
 * second folder whose divergence from this one presents as a proof that
 * verifies nowhere with both sides internally consistent.
 *
 * A historical proof is derivable because an MMR node is written ONCE, when
 * its perfect subtree completes, and is never revised. Truncating the log to
 * `atSize` therefore does not change any node the proof reads — it only
 * changes which nodes the border bags — so `proofAt(i, atSize)` is exact for
 * every `atSize <= size`, which is what `advanceMmrRoot` lagging behind the
 * log requires.
 *
 * ## Storage: every node, not just the peaks
 *
 * `_node[level][index]` holds each completed perfect subtree, `_payload[i]`
 * the pre-image at each position. That is ~2 slots per leaf and it is what
 * makes the two views above possible; the peak bag is derived from `size`
 * rather than stored, because a bag kept alongside the nodes would be a second
 * representation of one fact.
 *
 * An append is `O(log n)` worst case and amortised `O(1)`: a leaf carries
 * while the two highest peaks are equal-sized, which is binary increment on
 * `size`, so the carry count is the number of trailing ones. It is the
 * cheapest thing in the PQ path by a wide margin, and on Final Chain — one
 * block every 100 ms at a 1 wei base fee — the extra `SSTORE`s cost nothing
 * that matters.
 *
 * ## The layout is exactly `FinalMmr`'s
 *
 * The log is an RFC-6962 append-only log: perfect subtrees ("peaks") in
 * strictly decreasing size, folded right-to-left into the root.
 *
 * Leaf hashing and the proof's shape come from `FinalMmr` rather than being
 * restated — including `bitLength`/`popcount`, which are `internal` there
 * precisely so the proof this contract BUILDS and the proof
 * `PqAnchorModule.verifyInclusion` CHECKS decompose with one implementation.
 * A proof built to one shape and verified against another reverts on the
 * gateway naming neither side.
 *
 * ## Append-only is enforced, not documented
 *
 * There is no setter for a leaf, no way to shorten the log, and no owner path
 * that rewrites one. The only mutation is `append`. A log whose operator can
 * revise leaf 41 does not constrain a PQ dispatch at all — it only records
 * what the operator was willing to admit, which is what an authorization root
 * must not be.
 *
 * ## Who may append: a PQ quorum, not a role
 *
 * `FinalPqQuorum` over `ROLE_MMR_COSIGNER`, which is the same roster that
 * anchors the head on the execution chains — deliberately, since splitting them
 * would create an authority that can admit bundles nobody anchors, or anchor a
 * head over leaves nobody admitted.
 *
 * It was a single `FINAL_MANAGER` role, and that was the largest hole left in
 * the PQ lane. Admission IS the authorization: a bundle executes because its
 * intent root proves under `masterRoot`, and nothing downstream re-checks it.
 * So one compromised manager key admitted an arbitrary bundle and the chain
 * then *stated* that root as fact, with every co-signer and every gateway
 * correctly deferring to it. A K-of-N quorum of post-quantum signatures, each
 * verified by this chain's own precompiles against keys read from the registry
 * rather than from calldata, is what makes the log's contents cost more than
 * one key.
 *
 * The whole authority plane moved with it. The log used to answer to a
 * `FinalAccessManager`, which does not exist on Final Chain — the registry is
 * the only role plane here, so `configure` is gated by it exactly as
 * `FinalStateTrees.configureTree` is, and the access-manager pointer and its
 * rotation setter are gone rather than left dangling.
 */
contract FinalBundleLog is FinalPlaneSweep {
    /// @notice Commitment space. Must equal `PqAnchorModule.DOMAIN_PQ_MMR`, so
    /// a proof against this log's root is accepted by the gateway and a proof
    /// from any other log is not.
    ///
    /// Restated rather than imported because the gateway declares it
    /// `internal` and lives on a different chain — there is no import that
    /// would make this one value. `test_DomainMatchesTheGateway` pins the two
    /// together; without it, a `_v3` bump on one side would produce a log whose
    /// every proof is rejected, and the first symptom would be a PQ bundle that
    /// will not dispatch.
    bytes32 public constant DOMAIN = keccak256("FINAL_PQ_MMR_NODE_v01");

    /// @dev What a co-signer's approval authorizes. Per-action, so an approval
    /// to append cannot be replayed as one for any other quorum on this chain.
    /// @dev `.v2`: an append is approved over each bundle's INTENT LEAVES now,
    /// not over a claimed root. The shapes differ under `abi.encode`, so a v1
    /// approval could not be replayed in any case — but the action is what a
    /// signer reads to know what it is approving, and it is now approving a
    /// different thing.
    /// @dev `.v02`: each bundle is approved over its bundle-TERMS leaf as well
    /// as its intent leaves. The terms leaf is folded at position 0
    /// ahead of the intents, so the anchored root commits to the payout terms
    /// the co-signers admitted — dispatch method, recipient, fee cap, flags,
    /// includer tip/cap, committed submitter, searcher bids — and a submitter
    /// who alters any of them on the execution chain fails `BundleNotAnchored`.
    bytes32 private constant ACTION_APPEND = keccak256("FinalBundleLog.append.v02");
    /// @dev Standalone leaves: staged-envelope and
    /// settlement-credit leaves are MMR entries of their own, appended under
    /// the same quorum but consuming nothing from `FinalIntentLog` — they are
    /// not intents. A distinct action so an approval to append bundles can
    /// never be replayed as one to append payloads, and vice versa.
    bytes32 private constant ACTION_APPEND_PAYLOADS = keccak256("FinalBundleLog.appendPayloads.v01");
    /// @dev Registrar-quorum action, verified by the registry with this log as
    /// the verifying contract.
    bytes32 public constant ACTION_CONFIGURE = keccak256("FINAL_BUNDLE_LOG_CONFIGURE_v01");
    /// @notice Action tag for the one-shot seeding call.
    /// @dev Distinct from the append tag, so an approval collected to seed a fresh log can never be replayed as
    ///       an ordinary append.
    bytes32 public constant ACTION_SEED = keccak256("FINAL_BUNDLE_LOG_SEED_v01");
    /// @dev Registrar-quorum action: a fresh log taking over the previous log's
    /// LEAVES, payload by payload (the small-log form of {seed}).
    bytes32 public constant ACTION_SEED_PAYLOADS = keccak256("FINAL_BUNDLE_LOG_SEED_PAYLOADS_v01");

    /// @notice Where every signer, key and role is resolved. Immutable, so the
    /// quorum can never be pointed at a registry that arrived in calldata.
    FinalIdentityRegistry public immutable registry;

    /// @notice The posting log every anchored intent must already appear in.
    ///
    /// @dev Immutable for the same reason as `registry`, and for a sharper one:
    /// this is the whole of Final-Chain-first admission. A log that could be
    /// repointed could be repointed at one that says yes to everything, and the
    /// gate would be gone with nothing on chain looking different.
    FinalIntentLog public immutable intentLog;

    /// @notice The role a member must hold to approve an append.
    uint256 public writerRole;
    /// @notice How many approvals one append needs. Zero means unconfigured,
    /// and an unconfigured log refuses every write.
    uint256 public threshold;
    /// @notice Bound into every quorum digest. One per successful `append`
    /// call, not per leaf — a batch is one authorization.
    uint64 public nonce;

    /// @dev Every completed perfect subtree, by level and by index at that
    /// level. Written once and never revised — that immutability is the whole
    /// reason `proofAt` can answer for a historical `size`.
    mapping(uint256 level => mapping(uint256 index => bytes32)) private _node;

    /// @dev The pre-image at each position: the bundle's intent-Merkle root,
    /// untagged. Stored rather than left to the event log so a consumer needs
    /// one `eth_call` and no `getLogs` range — the chain caps that range at
    /// 100,000 blocks and mints one every 100 ms.
    mapping(uint256 index => bytes32) private _payload;

    /// @dev First position a payload took, plus one. Zero means never
    /// appended. First-occurrence-wins: a duplicate bundle root is a duplicate
    /// bundle, and either position proves the same root under the same head,
    /// so the earlier one is the one a lagging anchor can already reach.
    mapping(bytes32 payload => uint256) private _firstIndexPlusOne;

    /// @notice Leaf count. The `size` half of the head the execution chains'
    /// `advanceMmrRoot` is called with.
    uint256 public size;

    /// @notice Current log root over all `size` leaves — the `root` half of
    /// that head. Zero exactly when `size == 0`: an empty log commits to no
    /// leaves, matching `FinalMmr.MmrEmptyLog`.
    bytes32 public root;

    /// @notice A bundle root was folded in at `index`.
    /// @dev Carries the payload AND the resulting head, so a reader that wants
    /// the log replays this event and needs no other source. `payload` is the
    /// pre-image (the bundle's intent-Merkle root), not the tagged leaf — a
    /// consumer proving inclusion re-tags it via `FinalMmr.hashLeaf`, and
    /// emitting the tagged form would invite proving against the wrong one.
    event BundleAppended(uint256 indexed index, bytes32 indexed payload, bytes32 root, uint256 size);

    /// @notice The writer role or the threshold moved.
    event LogConfigured(uint256 writerRole, uint256 threshold);
    /// @notice A fresh log took over the previous log's frontier.
    event Seeded(uint256 size, uint256 peaks);
    /// @notice A fresh log took over the previous log's leaves, whole.
    event SeededPayloads(uint256 size);

    /// @notice `payload` is zero. A zero leaf is almost always an uninitialised
    /// read upstream, and it is unrecoverable here: the log cannot be shortened.
    error ZeroBundleRoot();
    /// @notice The frontier can be seeded only into an empty log.
    error NotFresh();
    /// @notice Thrown when a supplied peak set does not match the peaks the log currently holds.
    /// @dev The peaks are what a new root folds from, so accepting a mismatched set would publish a root that
    ///       describes a history this log never had.
    /// @param expected The number of peaks the log holds.
    /// @param given The number supplied.
    error PeaksMismatch(uint256 expected, uint256 given);
    /// @notice A view was asked about more leaves than the log holds.
    error SizeAhead(uint256 asked, uint256 held);
    /// @notice The audit-path walk and the `(index, size)` decomposition
    /// disagreed about how many siblings a proof has.
    error ProofShapeMismatch(uint256 expected, uint256 walked);
    /// @notice `append` with no leaves. A quorum round that admits nothing is
    /// always a caller bug, and burning a nonce for it would invalidate every
    /// approval already collected for the real batch.
    error EmptyBatch();
    /// @notice `threshold` is zero: no quorum has been configured yet.
    error LogNotConfigured();
    /// @notice A threshold no live roster can meet. Register the members first.
    error ThresholdUnreachable(uint256 live, uint256 required);
    /// @notice Not the bootstrap admin and not a registrar.
    error NotAuthorized(address caller);
    /// @dev A bundle with no intents has no root to fold and nothing to consume.
    error EmptyBundleLeaves(uint256 bundleIndex);
    /// @notice Thrown when the zero address is offered as the intent log.
    error ZeroIntentLog();
    /// @notice `append` was handed a different number of terms leaves than
    /// bundles — every bundle carries exactly one.
    error TermsLeafCountMismatch(uint256 termsLeaves, uint256 bundles);
    /// @notice A bundle's terms leaf was zero. The terms are what the
    /// co-signers admitted; a bundle with none has no admitted payout terms.
    error ZeroTermsLeaf(uint256 bundleIndex);

    /**
     * @param registry_ The identity registry. Every signer, key and role comes
     *        from it, and it is fixed at deployment for the same reason the
     *        keys are read from storage: a registry supplied per call is a
     *        registry the caller chooses.
     *
     * @dev A constructor, unlike the salt-only contracts in `contracts/`. This
     * one is deployed by ordinary CREATE alongside `FinalIdentityRegistry` and
     * `FinalStateTrees` — it lives on exactly one chain, so an address that is
     * identical across chains buys nothing, and the vanity path costs a
     * `FinalDeployer` bootstrap Final Chain has no other use for.
     *
     * The precompile probe is the point of having one at all: a log deployed
     * where ML-DSA cannot be verified would accept no approval it was ever
     * given, and the first symptom would be a PQ lane that silently never
     * admits a bundle.
     */
    constructor(FinalIdentityRegistry registry_, FinalIntentLog intentLog_) {
        FinalChainPrecompiles.assertAvailable();
        if (address(intentLog_) == address(0)) revert ZeroIntentLog();
        registry = registry_;
        intentLog = intentLog_;
    }

    /**
     * @notice Set which role may approve an append, and how many approvals.
     * @dev The registry's bootstrap admin alone while its window is open, the
     * sealed `ROLE_REGISTRAR` quorum afterwards — the same window and the same
     * quorum the registry and the trees use, because a threshold is membership
     * by another name. `approvals` is empty during bootstrap.
     *
     * Deliberately re-callable. A co-signer set that grows or shrinks has to be
     * able to move its threshold, and the alternative is a log that must be
     * redeployed — which for an append-only log means abandoning its contents.
     */
    function configure(
        uint256 role,
        uint256 k,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (registry.bootstrapSealed() || msg.sender != registry.bootstrapAdmin()) {
            registry.requireRegistrarQuorum(
                ACTION_CONFIGURE, keccak256(abi.encode(role, k)), anchorBlock, approvals
            );
        }
        // Refuse a threshold nobody can meet. A 4-of-5 configured against three
        // registered co-signers is a log that reverts on every append, and the
        // revert would name the threshold rather than the roster.
        if (k != 0) {
            uint256 live = registry.liveMemberCount(role);
            if (live < k) revert ThresholdUnreachable(live, k);
        }
        writerRole = role;
        threshold = k;
        emit LogConfigured(role, k);
    }

    /**
     * @notice Take over the previous log's frontier — its `peaks()` and `size`
     *         — so appends continue at the old positions and the accumulator
     *         (roots, `masterRoot @ mmrSize` on every gateway) never runs
     *         backwards across a redeploy. A redeploy does NOT wipe.
     * @dev The peaks are stored as the nodes they are (level = bit, index =
     *      leaves-before >> level), which is all a future append or `peaksAt`
     *      ever reads. Leaves BELOW the seed are the old log's: `payloadAt`
     *      and proofs for them answer from there, not here. Same authority as
     *      {configure}; only while this log holds nothing.
     */
    function seed(
        bytes32[] calldata peaks_,
        uint256 size_,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (registry.bootstrapSealed() || msg.sender != registry.bootstrapAdmin()) {
            registry.requireRegistrarQuorum(
                ACTION_SEED, keccak256(abi.encode(peaks_, size_)), anchorBlock, approvals
            );
        }
        if (size != 0) revert NotFresh();
        uint256 expected;
        for (uint256 x = size_; x != 0; x >>= 1) {
            if (x & 1 == 1) expected++;
        }
        if (expected != peaks_.length) revert PeaksMismatch(expected, peaks_.length);
        uint256 prefix;
        uint256 p;
        for (uint256 level = 255; ; level--) {
            if ((size_ >> level) & 1 == 1) {
                if (peaks_[p] == bytes32(0)) revert ZeroBundleRoot();
                _node[level][prefix >> level] = peaks_[p++];
                prefix += (uint256(1) << level);
            }
            if (level == 0) break;
        }
        size = size_;
        // `head()` answers (root, size) as one pair; a seeded size beside a
        // zero root is a torn head until the first append recomputes it.
        root = size_ == 0 ? bytes32(0) : rootAt(size_);
        emit Seeded(size_, peaks_.length);
    }

    /**
     * @notice Take over the previous log's LEAVES — every payload, in order —
     *         so this log answers `payloadAt`, `indexOf`, `proofAt` and
     *         `peaksAt` for every position the old one did, with the same
     *         roots at every size. The small-log form of {seed}: a peak-seeded
     *         log holds no payload below its seed, and the first live one
     * was seeded one leaf ABOVE what the gateways had
     *         anchored — the proposer could not read that leaf's payload here,
     *         could not build a verifiable proposal, and every anchor stalled
     *         behind it. Copying the leaves has no such edge; it costs one
     *         fold per leaf, which on this chain is cheap for a log of
     *         thousands. Same authority as {configure}; only while this log
     *         holds nothing.
     */
    function seedPayloads(
        bytes32[] calldata payloads,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (registry.bootstrapSealed() || msg.sender != registry.bootstrapAdmin()) {
            registry.requireRegistrarQuorum(
                ACTION_SEED_PAYLOADS, keccak256(abi.encode(payloads)), anchorBlock, approvals
            );
        }
        if (size != 0) revert NotFresh();
        if (payloads.length == 0) revert EmptyBatch();
        for (uint256 i = 0; i < payloads.length; i++) {
            _append(payloads[i]);
        }
        emit SeededPayloads(payloads.length);
    }

    /**
     * @notice Fold one or more bundles in as the next leaves, under a
     *         post-quantum quorum.
     * @param termsLeaves One bundle-terms leaf per bundle —
     *        `keccak256(abi.encode(DOMAIN_BUNDLE_TERMS, chainId, method,
     *        recipient, maxFeeWei, bundleFlags, tipPerGas, capPerGas,
     *        submitter, bidsHash, systemCallsHash))`, exactly as the execution
     *        chain's gateway rebuilds it. Folded at position 0, ahead of the
     *        intents; never consumed from the intent log, because it is not an
     *        intent.
     *
     *        `systemCallsHash` is the eleventh word: the left fold
     *        `hᵢ₊₁ = keccak256(hᵢ ‖ targetᵢ ‖ bindingᵢ)` over the bundle's
     *        carried system calls, zero when it carries none. It binds the
     *        count and the destinations of the calls the co-signers admitted,
     *        so a submitter cannot append one and be compensated for its gas.
     *        `bindingᵢ` is `keccak256(dataᵢ)` for a call the execution chain's
     *        gateway makes to ITSELF under a selector that is not
     *        self-anchoring, and zero otherwise: every other target's payload
     *        carries its own K-of-N or inclusion proof, while the gateway's
     *        refill self-calls are authorized by the caller alone and were
     *        rewritable by whoever submitted the bundle. The exclusion covers
     *        `advanceMmrRoot(bytes32,uint256)` only — binding that would close
     *        a loop on the bundle carrying the advance that anchors it.
     * @param bundles One inner array per bundle: that bundle's intent leaves,
     *        in bundle order. The root is RECOMPUTED from `[terms, leaves…]`.
     * @param approvals At least `threshold` of them, ascending by signer.
     * @return firstIndex The 0-based position the FIRST leaf occupies, forever.
     *
     * @dev **Leaves, not a root.** A claimed root is a claim about intents
     * nobody on this chain checked. Taking the leaves lets the contract do two
     * things it could not do before: recompute the payload with the same fold
     * the execution chain's gateway uses, and require every leaf to be an open
     * posting in `FinalIntentLog`.
     *
     * That is what makes Final-Chain-first admission a mechanism instead of a
     * quorum policy. A bundle cannot be anchored unless every intent in it was
     * posted here first, and since the execution chains' whole authorization is
     * this log's head, an intent that was never posted can never execute. The
     * co-signers already recomputed the root off-chain in
     * `validateBundleForAnchor`; this moves the check to where a compromised
     * quorum cannot skip it.
     *
     * Consumption is also the anti-replay gate for intents. The execution chain
     * keeps a consumed-seqId set that stops a BUNDLE re-executing; it cannot see
     * one intent being re-anchored inside a second bundle. This can.
     *
     * A batch rather than a leaf, because the quorum round — not the
     * `O(log n)` carry — is what an append costs. One round per bundle would
     * put a K-of-N collection on the critical path of every PQ dispatch; one
     * round per run of bundles is the same authorization over more work. A
     * batch of one is the degenerate case and is exactly as safe.
     *
     * The digest binds the nonce AND the pre-append `size`, so a co-signer
     * approves the POSITIONS as well as the contents. `size` is redundant for
     * replay — the nonce already covers that — and it is not redundant for what
     * this log is: "what is leaf 41?" is the question the whole contract exists
     * to answer, and an approval that named the leaves but not where they
     * land would leave that answer to whoever assembled the transaction.
     *
     * ML-DSA-87 is required rather than accepted, AND every approval carries a
     * seal. Admission is the authorization for execution — a root that lands
     * here executes — so it takes both key classes: the transaction key's
     * ML-DSA-87 signature and the seal key's SLH-DSA-SHAKE-256s over the same
     * digest. A break in either family leaves the log unmovable rather than
     * taken. `anchorBlock` is the tree-1 view the members decided the roster
     * against; `FinalPqQuorum.require_` bounds how stale it may be.
     */
    function append(
        bytes32[] calldata termsLeaves,
        bytes32[][] calldata bundles,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external returns (uint256 firstIndex) {
        if (bundles.length == 0) revert EmptyBatch();
        if (termsLeaves.length != bundles.length) revert TermsLeafCountMismatch(termsLeaves.length, bundles.length);
        uint256 k = threshold;
        if (k == 0) revert LogNotConfigured();

        uint64 n = nonce;
        firstIndex = size;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this),
                ACTION_APPEND,
                anchorBlock,
                keccak256(abi.encode(n, firstIndex, termsLeaves, bundles))
            ),
            writerRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            true
        );
        nonce = n + 1;

        for (uint256 i = 0; i < bundles.length; i++) {
            bytes32[] calldata leaves = bundles[i];
            if (leaves.length == 0) revert EmptyBundleLeaves(i);
            if (termsLeaves[i] == bytes32(0)) revert ZeroTermsLeaf(i);

            // Consume BEFORE folding. Either order works today, but consuming
            // first means a bundle that names an unposted or already-spent leaf
            // reverts on the leaf itself rather than after doing the fold — and
            // the revert names which leaf, which is the thing an operator needs.
            // The terms leaf sits at position 0 and is NOT consumed: it is the
            // admitted payout terms, not a posting.
            bytes32[] memory layer = new bytes32[](leaves.length + 1);
            layer[0] = termsLeaves[i];
            for (uint256 j = 0; j < leaves.length; j++) {
                intentLog.consume(leaves[j]);
                layer[j + 1] = leaves[j];
            }

            _append(FinalBundleTree.foldLeaves(layer));
        }
    }

    /**
     * @notice Append standalone leaves — staged-envelope and settlement-credit
     *         commitments — under the same post-quantum
     *         quorum, consuming nothing.
     * @param payloads The leaf preimages, already domain-separated by their
     *        own kind (`DOMAIN_STAGED_ENVELOPE`, `DOMAIN_SETTLEMENT_CREDIT` on
     *        the gateway side). Each becomes one position in the log and is
     *        provable to any gateway exactly as a bundle root is.
     * @return firstIndex The 0-based position the FIRST payload occupies.
     *
     * @dev These are not intents and were never posted, so nothing is consumed
     * from `FinalIntentLog`; what authorizes them is what authorizes a bundle
     * root — K of N co-signers, each of whom recomputed the leaf from the facts
     * it commits to (a settlement credit from the collecting chain's recorded
     * charge and the terminal execution fact; a staged envelope from the
     * admitted intent's anchored bound) before signing. A separate action id
     * keeps a bundle approval from being replayed here and vice versa; the
     * digest binds the nonce and the pre-append size, so positions are approved
     * along with contents, as for `append`.
     */
    function appendPayloads(
        bytes32[] calldata payloads,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external returns (uint256 firstIndex) {
        if (payloads.length == 0) revert EmptyBatch();
        uint256 k = threshold;
        if (k == 0) revert LogNotConfigured();

        uint64 n = nonce;
        firstIndex = size;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this),
                ACTION_APPEND_PAYLOADS,
                anchorBlock,
                keccak256(abi.encode(n, firstIndex, payloads))
            ),
            writerRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            true
        );
        nonce = n + 1;

        for (uint256 i = 0; i < payloads.length; i++) {
            _append(payloads[i]);
        }
    }

    /// @dev The fold itself. Separated from authorization so the batch loop is
    /// one concern and the quorum is the other.
    function _append(bytes32 payload) private {
        if (payload == bytes32(0)) revert ZeroBundleRoot();

        uint256 index = size;
        _payload[index] = payload;
        // First occurrence wins, so `indexOf` is stable for the life of the
        // log. A later duplicate is still appended and still provable at its
        // own position; it just is not the one this lookup names.
        if (_firstIndexPlusOne[payload] == 0) _firstIndexPlusOne[payload] = index + 1;

        // Push the new leaf as a size-1 peak, then carry while the top two
        // peaks are equal-sized. Equal size is exactly "the low bits of the
        // pre-increment count are set", so the carry count is the number of
        // trailing ones — binary increment, and the reason this is amortised
        // O(1) rather than O(log n) per append.
        bytes32 carry = FinalMmr.hashLeaf(DOMAIN, payload);
        _node[0][index] = carry;

        uint256 completed = index; // leaves already in the log
        uint256 level = 0;
        while (completed & 1 == 1) {
            // The left sibling is the perfect subtree that ends where this one
            // begins, and it is already stored — `_append` is the only writer
            // and it wrote that node on the append that completed it.
            //
            // Positional: the earlier subtree is always the LEFT child. Sorting
            // the pair here would make two different logs hash alike and is the
            // second-preimage hole `FinalMmr`'s tags exist to close.
            bytes32 left = _node[level][completed - 1];
            carry = _hashNode(left, carry);
            unchecked {
                completed >>= 1;
                ++level;
            }
            _node[level][completed] = carry;
        }

        unchecked {
            size = index + 1;
        }
        root = rootAt(size);
        emit BundleAppended(index, payload, root, size);
    }

    // ------------------------------------------------------------------ reads

    /// @notice The pre-image at one position — the bundle's intent-Merkle root.
    /// @dev Untagged, as the event carries it. A consumer proving inclusion
    /// re-tags through `FinalMmr.hashLeaf`; returning the tagged form here
    /// would invite proving against the wrong one.
    function payloadAt(uint256 index) public view returns (bytes32) {
        if (index >= size) revert FinalMmr.MmrIndexOutOfRange(index, size);
        return _payload[index];
    }

    /// @notice The pre-images at `[from, to)`, in order.
    /// @dev Batched because the proposer needs every unanchored leaf between
    /// the gateway's head and this log's, and one `eth_call` per leaf turns a
    /// pass into `size` round trips on a chain that mints a block every 100 ms.
    function payloadsBetween(uint256 from, uint256 to) external view returns (bytes32[] memory out) {
        if (to > size) revert SizeAhead(to, size);
        if (from > to) revert FinalMmr.MmrIndexOutOfRange(from, to);
        out = new bytes32[](to - from);
        for (uint256 i = from; i < to; ) {
            out[i - from] = _payload[i];
            unchecked { ++i; }
        }
    }

    /// @notice Where a bundle root first landed.
    /// @return index Its position. @return found False when it never landed,
    /// which is a state — a proposed bundle that has not been admitted yet —
    /// rather than an error.
    function indexOf(bytes32 payload) external view returns (uint256 index, bool found) {
        uint256 plusOne = _firstIndexPlusOne[payload];
        if (plusOne == 0) return (0, false);
        return (plusOne - 1, true);
    }

    /// @notice The peak bag backing the current root.
    /// @dev Derived from `size` rather than stored: a bag kept alongside the
    /// nodes would be a second representation of one fact, and the interesting
    /// property — `length == popcount(size)` — is then a consequence rather
    /// than something to maintain.
    function peaks() external view returns (bytes32[] memory) {
        return peaksAt(size);
    }

    /// @notice The peak bag as of `atSize` leaves.
    function peaksAt(uint256 atSize) public view returns (bytes32[] memory bag) {
        if (atSize > size) revert SizeAhead(atSize, size);
        bag = new bytes32[](FinalMmr.popcount(atSize));
        uint256 n = 0;
        uint256 offset = 0;
        uint256 level = FinalMmr.bitLength(atSize);
        // MSB first, so the peaks come out largest-first — the order
        // `_rootFromPeaks` and every reader expect.
        while (level > 0) {
            unchecked { --level; }
            if ((atSize >> level) & 1 == 1) {
                bag[n] = _node[level][offset >> level];
                unchecked {
                    ++n;
                    offset += (1 << level);
                }
            }
        }
    }

    /// @notice The head the execution chains' `advanceMmrRoot` should be called
    /// with, read in one call so the pair can never be torn across two.
    function head() external view returns (bytes32 root_, uint256 size_) {
        return (root, size);
    }

    /**
     * @notice The root over the FIRST `atSize` leaves.
     * @dev Exact for every `atSize <= size`, not only the current one, because
     * an MMR node is written once and never revised. That is what lets a
     * consumer prove against the head the gateway actually anchored, which
     * lags this log by design — a proof built against a larger size is
     * well-formed and verifies nowhere.
     */
    function rootAt(uint256 atSize) public view returns (bytes32) {
        if (atSize > size) revert SizeAhead(atSize, size);
        if (atSize == 0) return bytes32(0);
        return _rangeRoot(0, atSize);
    }

    /**
     * @notice The audit path proving leaf `leafIndex` under `rootAt(atSize)`.
     * @param leafIndex 0-based position, `< atSize`.
     * @param atSize The head to prove against — `gateway.mmrSize()`, not this
     *        log's size, whenever the two differ.
     *
     * @dev The walk is the Trillian formulation: rise level by level and record
     * the sibling, which is a stored perfect subtree unless it is the ragged
     * last node of its level, in which case it is the bag of the peaks that
     * remain. Both cases resolve to nodes this contract wrote, so there is
     * nothing to recompute from leaves and nothing for a caller to fold.
     *
     * The result feeds `FinalMmr.computeRoot` verbatim on the gateway. Its
     * length is derived here from the same `(index, size)` decomposition that
     * verifier uses, and the walk's own count is checked against it — a
     * disagreement is a proof rejected on another chain naming neither side,
     * so it is caught where both derivations are visible.
     */
    function proofAt(uint256 leafIndex, uint256 atSize) public view returns (bytes32[] memory proof) {
        if (atSize > size) revert SizeAhead(atSize, size);
        if (atSize == 0) revert FinalMmr.MmrEmptyLog();
        if (leafIndex >= atSize) revert FinalMmr.MmrIndexOutOfRange(leafIndex, atSize);

        uint256 inner = FinalMmr.bitLength(leafIndex ^ (atSize - 1));
        uint256 border = FinalMmr.popcount(leafIndex >> inner);
        proof = new bytes32[](inner + border);

        uint256 idx = leafIndex;
        uint256 last = atSize - 1; // index of the last node at the current level
        uint256 level = 0;
        uint256 n = 0;
        while (last != 0) {
            uint256 sibling = idx ^ 1;
            if (sibling < last) {
                // A complete perfect subtree, stored when it completed.
                proof[n] = _node[level][sibling];
                unchecked { ++n; }
            } else if (sibling == last) {
                // The ragged right edge: this level's last node may cover fewer
                // than `2 ** level` leaves, so it is the bag of the peaks in
                // `[sibling << level, atSize)` rather than a node of its own.
                proof[n] = _rangeRoot(sibling << level, atSize);
                unchecked { ++n; }
            }
            // `sibling > last` — no sibling at this level. Rise without
            // recording, which is what makes an imperfect tree's path shorter
            // than its depth.
            unchecked {
                idx >>= 1;
                last >>= 1;
                ++level;
            }
        }
        if (n != proof.length) revert ProofShapeMismatch(proof.length, n);
    }

    /**
     * @notice Everything needed to prove one bundle, in one call.
     * @dev One call rather than three, because payload, proof and root are only
     * meaningful together: fetched separately, an append landing between two of
     * them yields a proof against a root the caller did not read, and the
     * dispatch reverts on another chain with three individually-correct values.
     */
    function bundleAt(uint256 leafIndex, uint256 atSize)
        external
        view
        returns (bytes32 payload, bytes32[] memory proof, bytes32 root_)
    {
        proof = proofAt(leafIndex, atSize);
        payload = payloadAt(leafIndex);
        root_ = rootAt(atSize);
    }

    /**
     * @dev The Merkle-Tree-Hash of leaves `[lo, hi)`, where `lo` is aligned to
     * the largest perfect subtree the range can hold.
     *
     * Every part is a COMPLETE perfect subtree and therefore a stored node,
     * which is the property that makes a historical proof derivable at all.
     * The parts come out largest-first and fold right-to-left, which is the
     * same peak bag `FinalMmr.computeRoot` walks as a proof's border — and is
     * what makes a proof against this root verify on the gateway.
     */
    function _rangeRoot(uint256 lo, uint256 hi) private view returns (bytes32) {
        uint256 remaining = hi - lo;
        bytes32[] memory bag = new bytes32[](FinalMmr.popcount(remaining));
        uint256 n = 0;
        uint256 offset = lo;
        uint256 level = FinalMmr.bitLength(remaining);
        while (level > 0) {
            unchecked { --level; }
            if ((remaining >> level) & 1 == 1) {
                bag[n] = _node[level][offset >> level];
                unchecked {
                    ++n;
                    offset += (1 << level);
                }
            }
        }
        bytes32 acc = bag[n - 1];
        for (uint256 i = n - 1; i > 0; ) {
            unchecked { --i; }
            acc = _hashNode(bag[i], acc);
        }
        return acc;
    }

    /// @dev `FinalMmr` keeps its node hash private (proof verification is its
    /// only caller there), so the one construction that must not fork is
    /// restated once, here, against its published spec:
    /// `keccak256(0x01 ‖ domain ‖ left ‖ right)`. `test_LayoutMatchesFinalMmr`
    /// pins it against a proof the library itself verifies, so a change to
    /// either side fails rather than producing a log the gateway rejects.
    function _hashNode(bytes32 left, bytes32 right) private pure returns (bytes32) {
        return keccak256(abi.encodePacked(uint8(0x01), DOMAIN, left, right));
    }

    // ------------------------------------------------------------------ sweep

    /// @dev This contract's configuration gate reads the membership registry it
    /// was constructed against, so the sweep authority reads the same one.
    function _sweepRegistry() internal view override returns (FinalIdentityRegistry) {
        return registry;
    }

    /// @dev Nothing is reserved because nothing is owed: this contract has no
    /// payable entrypoint and no custody line — it records, it does not hold.
    /// Anything it carries arrived by accident and is sweepable in full.
}

contracts/finalchain/FinalCertificate.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may link against and call this certificate reader,
//    and may encode certificates that it accepts, as part of the Final DeFi
//    Protocol.
// 2. Operators, integrators, and end users may have their certificates parsed,
//    self-checked, and verified through any Final DeFi surface that links it.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this certificate reader or a competing identity
//    certificate format derived from it without permission prior to the
//    Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalChainTime} from "./FinalChainTime.sol";

/**
 * @title Final Certificate
 * @notice Reads a Final Certificate on chain and self-checks it, so a certificate's keys can never be
 *         anything other than the keys it declares.
 * @dev Deployed only as part of this project's own reth-based state plane, and only on the reth-based chains
 *      that carry the precompiles it calls: SHA3-256 at `0x0202`, ML-DSA-87 at `0x0204` and
 *      SLH-DSA-SHAKE-256s at `0x0205`, each address being that primitive's FIPS number. The contracts it is
 *      linked into probe those precompiles at construction and refuse to exist where they are absent, so
 *      this library never runs somewhere its verdicts would be meaningless. It takes part in no CREATE2
 *      derivation, and nothing outside this directory imports it.
 *
 *      The SHA3 precompile is not a convenience: the certificate format hashes with FIPS-202 SHA3 and the
 *      EVM's `keccak256` is a DIFFERENT function, so a digest computed with the wrong one matches no
 *      certificate any issuer ever wrote.
 *
 *      ## Why the chain parses this at all
 *
 *      The alternative is taking the TBS bytes and the public keys as separate arguments and deriving
 *      `certHash` from the bytes. That looks like verification and is not: nothing compares the keys to the
 *      certificate, so a registrar could bind any certificate to any keypair, the registry would hold a key
 *      the certificate does not contain, and every signature that key produced would verify against a
 *      certificate that never authorised it.
 *
 *      So the keys are read OUT of the certificate. There is one input, and no pair of arguments that can
 *      disagree.
 *
 *      Gas is deliberately not a design constraint on the chain this runs on and must not be optimised for.
 *      Parsing and re-hashing on chain costs more than trusting a parse done elsewhere and buys a verdict
 *      that is re-derivable from public state, which is the trade this whole plane is built on.
 *
 *      ## The key-identifier check
 *
 *      A certificate declares `SubjectKeyId` as the SHA3-256 digest of its `PublicKeyBlock`. Having parsed
 *      that block, {parse} recomputes the digest and compares. The field sits inside the TBS, so it is
 *      covered by the issuer's signatures — which makes the check a statement about what the issuer
 *      attested, not merely about internal consistency of bytes the caller supplied.
 *
 *      ## Deploy-linked, not inlined
 *
 *      {parseLive}, {parseRecovery}, {parseCa} and {verifyIssuerSignatures} are `external`, so the identity
 *      registry calls them across a link boundary rather than carrying them in its own bytecode, which it
 *      has no room for. The link target is fixed at deployment: a linked library is code, not a pointer
 *      anyone can move afterwards.
 *
 *      ## What this library deliberately does not do
 *
 *      It does not verify an issuer's signatures over the TBS as part of parsing, and it does not walk a
 *      certificate chain to the root. On the registration path there is nothing to walk — a chain-attested
 *      certificate is admitted by this chain against pinned issuer constants and the holder's own proof of
 *      possession, so an issuer signature is not what makes it valid. {verifyIssuerSignatures} is here for
 *      callers verifying an off-chain issuance, and it verifies exactly what it is handed.
 *
 *      It also does not check an encapsulation key's length or structure. Those are checked where they are
 *      REGISTERED, by the precompiles that own the answer, because two checks of one thing in two shapes is
 *      how one of them ends up weaker and nobody notices which.
 */
library FinalCertificate {
    /// @notice The four magic bytes every certificate opens with, `"PQCF"`.
    uint32 internal constant MAGIC = 0x50514346;
    /// @notice The current wire generation, which encoders write.
    /// @dev A generation this parser does not know fails to parse rather than being reinterpreted: the
    ///      folded key commitment, and therefore every wallet address, derives from this exact layout, so a
    ///      layout read under the wrong generation would produce a self-consistent digest that matches
    ///      nothing.
    uint32 internal constant VERSION = 2;
    /// @notice The previous wire generation, still accepted on parse.
    /// @dev Reading an older artifact is not the same as admitting it. Whether such a certificate may be
    ///      REGISTERED is settled at admission, by the holder's proof of possession and the chain-issuer
    ///      pins, rather than by refusing to decode it.
    uint32 internal constant VERSION_V4 = 1;

    /// @notice The institution identity extension, which carries an issuer's legal name, registration
    ///         number and jurisdiction.
    uint16 internal constant EXT_INSTITUTION = 0x0102;

    /// @notice ML-KEM-1024 (FIPS 203), the lattice half of the encapsulation pair.
    /// @dev Algorithm identifiers ARE the FIPS numbers, in one space shared by signatures and encapsulation
    ///      — the same identifiers the quorum wire format uses, and the numbers the precompile addresses end
    ///      in. One space rather than two means an identifier can never be read against the wrong table.
    uint16 internal constant ALG_ML_KEM_1024 = 0x0003;
    /// @notice ML-DSA-87 (FIPS 204). Transaction class.
    uint16 internal constant ALG_ML_DSA_87 = 0x0004;
    /// @notice SLH-DSA-SHAKE-256s (FIPS 205). Access class, and the seal.
    uint16 internal constant ALG_SLH_DSA_SHAKE_256S = 0x0005;
    /// @notice FN-DSA (FIPS 206). Reserved: there is no implementation behind it and it is never accepted in
    ///         a slot.
    uint16 internal constant ALG_FN_DSA = 0x0006;
    /// @notice HQC-5 (FIPS 207), the code-based half of the encapsulation pair.
    uint16 internal constant ALG_HQC_5 = 0x0007;

    /// @notice Certificate signing, for both of an issuer's keys.
    /// @dev Says which key to verify WITH; it grants nothing on its own — capability to issue comes from the
    ///      depth pair.
    uint16 internal constant PURPOSE_CERT_SIGNING = 0x0004;

    /// @notice The live stage's transaction-class slot, ML-DSA-87.
    /// @dev A wallet holds four slots in two stages of two, and a certificate carries ONE stage, never all
    ///      four. The stage is what is issued, rotated and revoked as a unit, and a holder presenting a live
    ///      certificate presents both of that stage's keys or neither — splitting them per slot would let
    ///      half a stage be presented as if it were whole.
    /// @dev This applies to services exactly as it applies to a user's wallet. A co-signer is a Final
    ///      Wallet: same four slots, same split, same algorithms. There is no second kind of identity in
    ///      this system.
    uint16 internal constant PURPOSE_ACTIVE_TX = 0x0010;
    /// @notice The live stage's access-class slot, SLH-DSA-SHAKE-256s.
    uint16 internal constant PURPOSE_ACTIVE_ACCESS = 0x0011;
    /// @notice The recovery stage's transaction-class slot, ML-DSA-87.
    uint16 internal constant PURPOSE_RECOVERY_TX = 0x0012;
    /// @notice The recovery stage's access-class slot, SLH-DSA-SHAKE-256s.
    uint16 internal constant PURPOSE_RECOVERY_ACCESS = 0x0013;
    /// @notice The live stage's encapsulation slot.
    /// @dev Each stage's encapsulation pair is resolved alongside its signing pair, and the identity
    ///      registry stores both halves, so a sender can encapsulate to a registered party without a second
    ///      lookup somewhere less authoritative. Both halves sit under ONE purpose and are told apart by
    ///      algorithm, which is why the key loop matches on the `(purpose, algorithm)` pair.
    uint16 internal constant PURPOSE_ACTIVE_KEM = 0x0014;
    /// @notice The recovery stage's encapsulation slot, carrying the same two algorithms.
    uint16 internal constant PURPOSE_RECOVERY_KEM = 0x0015;
    /// @notice The seal purpose: a second SLH-DSA-SHAKE-256s key that co-signs execution-class quorum
    ///         decisions.
    /// @dev Distinct from the access key, and carried by SERVICE certificates only — a user's wallet never
    ///      seals. Optional in the format, so a certificate without it parses unchanged.
    /// @dev Outside the folded key commitment: a seal is operational, rotated by issuing a new live
    ///      certificate, and it must not move a wallet address it plays no part in deriving.
    uint16 internal constant PURPOSE_ACTIVE_SEAL = 0x0016;

    /// @notice A sentinel purpose no certificate can carry.
    /// @dev Lets {parse} be told "this stage has no encapsulation slot" without a second boolean argument.
    ///      `0xffff` is outside the purpose registry and is reserved by being used here.
    uint16 internal constant NO_KEM_PURPOSE = 0xffff;

    /// @notice Nanoseconds per millisecond, the conversion from a certificate's validity fields to this
    ///         chain's clock.
    /// @dev A certificate stamps validity in NANOseconds and this chain's clock is MILLIseconds, so the
    ///      parser divides by 1e6 on the way in and nothing downstream ever compares across units. Getting
    ///      the divisor wrong does not fail loudly: it shifts every window by three orders of magnitude, so
    ///      every certificate reads as already valid, including one issued for the future.
    uint64 internal constant NS_PER_MILLISECOND = FinalChainTime.NS_PER_MILLISECOND;

    /**
     * @title Parsed
     * @notice What the chain keeps out of one certificate.
     * @dev Every field is read OUT of the TBS. Nothing here can be supplied alongside the bytes, which is
     *      what makes it impossible for a caller to bind a certificate to material the certificate does not
     *      contain.
     */
    struct Parsed {
        /// `SHA3-256` of the TBS bytes: the certificate's own identity, and the handle revocation is keyed
        /// on.
        bytes32 certHash;
        /// The certificate's 32-byte serial. A serial is per certificate SET, so the two stages of one
        /// wallet share it and two stages that disagree are two different wallets.
        bytes32 serial;
        /// keccak256 of the issuer-name bytes, for the chain-issuer pin: a chain-attested certificate
        /// carries the chain's own constant issuer name, and the registry compares one hash rather than two
        /// strings.
        bytes32 issuerDnHash;
        /// The subject-name bytes verbatim. Kept whole rather than hashed because the jurisdiction rule
        /// reads its country component at issuer registration.
        bytes subjectDn;
        /// The institution extension's VALUE, when present; empty otherwise. Issuer registration parses
        /// the declared jurisdiction out of it and requires it to match the subject name's country.
        bytes institutionExt;
        /// SHA3-256 of the ISSUER's public key block. Zero-length — and so
        /// `bytes32(0)` here — for exactly one certificate in the hierarchy,
        /// which is what terminates chain validation.
        bytes32 authorityKeyId;
        /// SHA3-256 of this certificate's own public key block. The child's
        /// `authorityKeyId` must equal it, which is what links the two.
        bytes32 subjectKeyId;
        /// Position on the delegation axis; 0 is the chain's own root.
        uint8 depth;
        /// Deepest level this key may issue to. `== depth` means it signs no certificates at all, which is
        /// every end entity. The pair is immutable per certificate, which is why consumers discriminate
        /// record kinds by it rather than by a role bit.
        uint8 maxDelegationDepth;
        /// MILLISECONDS, converted from the schema's nanoseconds — this chain's clock.
        uint64 notBefore;
        /// Milliseconds. Zero means never expires, which the schema allows.
        uint64 notAfter;
        /// The stage's transaction-class key. ML-DSA-87 — spending, and every
        /// high-cadence protocol action.
        bytes transactionKey;
        /// The stage's access-class key. SLH-DSA-SHAKE-256s — identity,
        /// rotation, recovery-pair promotion. A different hardness assumption,
        /// so a lattice break leaves the key that governs identity standing.
        bytes accessKey;
        /// The stage's ML-KEM-1024 encapsulation key. Empty on a CA, which has
        /// no encapsulation stage, and on any v4 certificate issued without
        /// one — see `parse` for why that is tolerated rather than refused.
        bytes kemMlKem;
        /// The stage's HQC-5 encapsulation key. Carried under the SAME purpose
        /// as the lattice half and distinguished only by algorithm, which is
        /// why the parser matches on the `(purpose, algorithm)` pair.
        bytes kemHqc;
        /// The service's seal key (`PURPOSE_ACTIVE_SEAL`, SLH-DSA-SHAKE-256s).
        /// Empty on every certificate that does not carry one — a user wallet,
        /// a recovery stage, a CA.
        bytes sealKey;
        /// Where the TBS ends, so a caller holding the whole certificate can
        /// find the `SignatureBlock` without parsing forward again.
        uint256 tbsLength;
    }

    /// @notice The bytes do not open with the certificate magic, so they are not a certificate at all.
    /// @param got The four bytes that were present.
    error BadMagic(uint32 got);
    /// @notice The wire generation is one this parser does not read.
    /// @param got The generation the certificate declares.
    error BadVersion(uint32 got);
    /// @notice The TBS ends before a field the parser was about to read.
    /// @param needed The offset the read required.
    /// @param got The length actually supplied.
    error Truncated(uint256 needed, uint256 got);
    /// @notice The recomputed key-block digest does not equal the one the certificate declares, so the keys
    ///         present are not the keys the issuer attested.
    /// @param derived The digest recomputed from the key block.
    /// @param declared The digest the certificate carries.
    error SubjectKeyIdMismatch(bytes32 derived, bytes32 declared);
    /// @notice A stage is missing a key it must carry, or carries half of a pair that is issued whole.
    /// @param purpose The purpose whose slot is unfilled.
    error MissingSlot(uint16 purpose);
    /// @notice A slot carries a key of the wrong scheme. It would verify cryptographically and mean
    ///         something else entirely, which is exactly what splitting the classes exists to prevent.
    /// @param purpose The slot's purpose.
    /// @param algorithm The algorithm identifier that was present.
    error WrongAlgorithmForSlot(uint16 purpose, uint16 algorithm);
    /// @notice Two key entries share one `(purpose, algorithm)` pair, so one would silently shadow the
    ///         other.
    /// @param purpose The repeated purpose.
    /// @param algorithm The repeated algorithm identifier.
    error DuplicateKey(uint16 purpose, uint16 algorithm);
    /// @notice The key entries are not in ascending `(purpose, algorithm)` order. The schema requires that
    ///         order so `certHash` is reproducible across implementations.
    error KeysNotSorted();
    /// @notice A signing key whose length is not the one its algorithm defines.
    /// @param algorithm The algorithm identifier the entry declares.
    /// @param length The key length that was present.
    error BadKeyLength(uint16 algorithm, uint256 length);
    /// @notice A delegation bound shallower than the certificate's own depth, which admits nothing.
    /// @param depth The certificate's position on the delegation axis.
    /// @param maxDelegationDepth The deepest level it claims to issue to.
    error InvalidDepth(uint8 depth, uint8 maxDelegationDepth);
    /// @notice A certificate that expires no later than it begins.
    /// @param notBefore The declared start, in the schema's nanoseconds.
    /// @param notAfter The declared end, in the schema's nanoseconds.
    error ValidityInverted(uint64 notBefore, uint64 notAfter);

    /**
     * @notice Parse and self-check a `TBSCertificate`.
     * @dev Checking for a CAPABILITY rather than a type is the certificate schema's own rule, and the reason
     *      there is no type field to check instead. Passing the LIVE purposes to a recovery certificate
     *      finds neither key and reverts — which is what stops a recovery certificate being registered as a
     *      live one and handing the recovery pair everyday authority.
     *
     *      Self-check means the declared `SubjectKeyId` is recomputed from the key block that follows it and
     *      compared. That field is inside the TBS and therefore covered by the issuer's signatures, so the
     *      comparison turns "these bytes decode" into "the issuer attested these exact keys". Doing it on
     *      chain costs one precompile call and buys a verdict any reader can recompute; gas is not a design
     *      constraint on the chain this runs on, and must not be traded for a check that would then have to
     *      be taken on trust from whichever process ran it.
     *
     *      A stage is issued as a unit, so both of a stage's signing keys must be present, and its
     *      encapsulation pair must be present in full or absent in full.
     * @param tbs the TBS bytes, verbatim. Not the whole certificate.
     * @param txPurpose the transaction-class purpose this stage should carry.
     * @param accessPurpose the access-class purpose for the same stage.
     * @param kemPurpose the encapsulation purpose for the same stage, or {NO_KEM_PURPOSE} for a stage that
     *        has none.
     * @return out The parsed certificate: digest, serial, names, key identifiers, depth pair, validity
     *         window, and every key slot the stage carries.
     */
    function parse(bytes calldata tbs, uint16 txPurpose, uint16 accessPurpose, uint16 kemPurpose)
        internal
        view
        returns (Parsed memory out)
    {
        _need(tbs, 58);
        if (uint32(bytes4(tbs[0:4])) != MAGIC) revert BadMagic(uint32(bytes4(tbs[0:4])));
        // Both live wire generations parse. An artifact issued under the older one is read rather than
        // refused; whether it may be ADMITTED is a separate question, settled at registration by the
        // holder's proof of possession and the chain-issuer pins.
        uint32 wireVersion = uint32(bytes4(tbs[4:8]));
        if (wireVersion != VERSION && wireVersion != VERSION_V4) revert BadVersion(wireVersion);

        out.certHash = FinalChainPrecompiles.sha3_256(tbs);
        out.serial = bytes32(tbs[8:40]);
        out.depth = uint8(tbs[40]);
        out.maxDelegationDepth = uint8(tbs[41]);

        uint64 notBeforeNs = uint64(bytes8(tbs[42:50]));
        uint64 notAfterNs = uint64(bytes8(tbs[50:58]));
        if (out.maxDelegationDepth < out.depth) {
            revert InvalidDepth(out.depth, out.maxDelegationDepth);
        }
        if (notAfterNs != 0 && notAfterNs <= notBeforeNs) {
            revert ValidityInverted(notBeforeNs, notAfterNs);
        }
        out.notBefore = notBeforeNs / NS_PER_MILLISECOND;
        out.notAfter = notAfterNs == 0 ? 0 : notAfterNs / NS_PER_MILLISECOND;

        // Four length-prefixed fields: IssuerDN, SubjectDN, AuthorityKeyId,
        // SubjectKeyId. Every field before them is fixed width, which is the
        // whole reason the schema orders them this way.
        uint256 p = 58;
        uint256 issuerDnLen;
        (p, issuerDnLen) = _skipLengthPrefixed(tbs, p);
        out.issuerDnHash = keccak256(tbs[p - issuerDnLen:p]);
        uint256 subjectDnLen;
        (p, subjectDnLen) = _skipLengthPrefixed(tbs, p);
        out.subjectDn = tbs[p - subjectDnLen:p];
        uint256 akidLen;
        (p, akidLen) = _skipLengthPrefixed(tbs, p);
        out.authorityKeyId = _bytes32At(tbs, p - akidLen, akidLen);
        uint256 skidLen;
        (p, skidLen) = _skipLengthPrefixed(tbs, p);
        uint256 skidStart = p - skidLen;

        _need(tbs, p + 2);
        uint16 keyCount = uint16(bytes2(tbs[p:p + 2]));
        p += 2;
        // AFTER the count word. `SubjectKeyId` is SHA3-256 of the KeyEntry
        // array alone — `encodeTbs` writes `PublicKeyCount` as its own field and
        // `encodePublicKeyBlock` returns only the entries. Hashing the count in
        // produces a digest that is self-consistent and matches no certificate
        // any issuer ever wrote.
        uint256 blockStart = p;

        uint32 previousSort = 0;
        for (uint256 i = 0; i < keyCount; i++) {
            _need(tbs, p + 8);
            uint16 alg = uint16(bytes2(tbs[p:p + 2]));
            uint16 purpose = uint16(bytes2(tbs[p + 2:p + 4]));
            uint32 keyLen = uint32(bytes4(tbs[p + 4:p + 8]));
            p += 8;
            _need(tbs, p + keyLen);

            // Ascending by (purpose, algorithm), duplicates invalid. The schema
            // requires the order so `certHash` is reproducible across
            // implementations; enforcing it here also means a second entry for
            // one slot cannot quietly shadow the first.
            uint32 sortKey = (uint32(purpose) << 16) | uint32(alg);
            if (i > 0) {
                if (sortKey == previousSort) revert DuplicateKey(purpose, alg);
                if (sortKey < previousSort) revert KeysNotSorted();
            }
            previousSort = sortKey;

            // The algorithm is pinned per CLASS, not merely recorded. A
            // transaction slot carrying an access-class key would verify
            // cryptographically and mean something entirely different — an
            // identity key must never authorize a transaction, or splitting the
            // classes buys nothing.
            // Matched on the PAIR, not on the purpose alone. A CA carries two
            // keys under one purpose (`0x0004`) distinguished only by
            // algorithm, so matching on purpose first would find the first of
            // them twice and the second never.
            if (purpose == txPurpose && alg == ALG_ML_DSA_87) {
                if (keyLen != FinalChainPrecompiles.ML_DSA_87_PUBLIC_KEY_LEN) {
                    revert BadKeyLength(alg, keyLen);
                }
                out.transactionKey = tbs[p:p + keyLen];
            } else if (purpose == accessPurpose && alg == ALG_SLH_DSA_SHAKE_256S) {
                if (keyLen != FinalChainPrecompiles.SLH_DSA_SHAKE_256S_PUBLIC_KEY_LEN) {
                    revert BadKeyLength(alg, keyLen);
                }
                out.accessKey = tbs[p:p + keyLen];
            } else if (purpose == kemPurpose && alg == ALG_ML_KEM_1024) {
                out.kemMlKem = tbs[p:p + keyLen];
            } else if (purpose == kemPurpose && alg == ALG_HQC_5) {
                out.kemHqc = tbs[p:p + keyLen];
            } else if (purpose == PURPOSE_ACTIVE_SEAL && alg == ALG_SLH_DSA_SHAKE_256S) {
                if (keyLen != FinalChainPrecompiles.SLH_DSA_SHAKE_256S_PUBLIC_KEY_LEN) {
                    revert BadKeyLength(alg, keyLen);
                }
                out.sealKey = tbs[p:p + keyLen];
            } else if (purpose == PURPOSE_ACTIVE_SEAL) {
                // The seal is hash-based by definition — it exists to stand on
                // the OTHER assumption from the transaction key it co-signs
                // with. A lattice seal would be two signatures on one bet.
                revert WrongAlgorithmForSlot(purpose, alg);
            } else if (purpose == txPurpose || purpose == accessPurpose) {
                // A slot the caller asked for, carrying the wrong scheme. It
                // would verify cryptographically and mean something else
                // entirely — an identity key must never authorize a
                // transaction, or splitting the classes buys nothing.
                revert WrongAlgorithmForSlot(purpose, alg);
            } else if (purpose == kemPurpose) {
                // Same rule for the encapsulation slot. A third KEM appearing
                // under this purpose is a hybrid whose second family nobody
                // agreed on, and admitting it silently is how a pair becomes a
                // trio that one reader honours and another ignores.
                revert WrongAlgorithmForSlot(purpose, alg);
            }

            // NO length check on the KEM keys here, and that is deliberate.
            // The signing slots are checked against a constant because the
            // parser's own callers depend on the length; an encapsulation key
            // is checked by `0x0203` / `0x0207` at the moment it is REGISTERED,
            // where the answer is a well-formedness verdict rather than a
            // parse failure. Two checks of the same thing in two shapes is how
            // one of them ends up weaker and nobody notices which.
            p += keyLen;
        }

        // `SubjectKeyId` is SHA3-256 of the KeyEntry array, count word
        // EXCLUDED — `blockStart` is taken after the count is consumed, for the
        // reason given where it is set. Recomputing it is what turns "these
        // bytes decode" into "the CA signed these exact keys"; the field is
        // inside the TBS, so it is covered by the signatures.
        out.subjectKeyId = FinalChainPrecompiles.sha3_256(tbs[blockStart:p]);
        bytes32 declared = _bytes32At(tbs, skidStart, skidLen);
        if (out.subjectKeyId != declared) revert SubjectKeyIdMismatch(out.subjectKeyId, declared);

        // Both or neither. A stage is issued as a unit, so a certificate
        // carrying one of its two keys is not a partial certificate — it is a
        // certificate for a stage that does not exist.
        if (out.transactionKey.length == 0) revert MissingSlot(txPurpose);
        if (out.accessKey.length == 0) revert MissingSlot(accessPurpose);

        // The encapsulation pair is both-or-neither for the same reason, and
        // the reason is louder here: a hybrid quietly reduced to one family is
        // identical on the wire, so a certificate carrying only the lattice
        // half would seal successfully and silently drop the code-based hedge.
        // Neither is the CA case and the pre-v4 case, both legitimate.
        if ((out.kemMlKem.length == 0) != (out.kemHqc.length == 0)) {
            revert MissingSlot(kemPurpose);
        }

        _need(tbs, p + 2);
        uint16 extCount = uint16(bytes2(tbs[p:p + 2]));
        p += 2;
        for (uint256 i = 0; i < extCount; i++) {
            _need(tbs, p + 7);
            uint16 extType = uint16(bytes2(tbs[p:p + 2]));
            uint32 valueLen = uint32(bytes4(tbs[p + 3:p + 7]));
            p += 7;
            _need(tbs, p + valueLen);
            // The Institution extension's VALUE, kept for the issuer
            // profile's jurisdiction rule. Everything else is skipped as
            // before — extensions are structural to certHash, semantic to
            // whichever consumer knows them.
            if (extType == EXT_INSTITUTION) out.institutionExt = tbs[p:p + valueLen];
            p += valueLen;
        }
        out.tbsLength = p;
    }

    /// @notice Parse a LIVE-stage certificate: the live transaction and access keys.
    /// @dev `external`, like the other three entry points below. The identity registry sits against the
    ///      deployed-code ceiling and this parser is its single largest inlined dependency, so the four doors
    ///      it calls are DEPLOY-LINKED: the library is one more contract in the state plane's fixed deploy
    ///      order, and its address is baked immutably into the registry's bytecode. A linked library is code,
    ///      not a key — nothing can repoint it after deployment, so the split costs a call boundary and no
    ///      trust.
    /// @param tbs The TBS bytes, verbatim.
    /// @return The parsed and self-checked certificate.
    function parseLive(bytes calldata tbs) external view returns (Parsed memory) {
        return parse(tbs, PURPOSE_ACTIVE_TX, PURPOSE_ACTIVE_ACCESS, PURPOSE_ACTIVE_KEM);
    }

    /// @notice Parse a RECOVERY-stage certificate.
    /// @dev The recovery pair authorizes rotating the wallet's own credentials and NOTHING else. Acting as a
    ///      guardian is an ordinary action for that account and uses the live access key, so keeping the two
    ///      stages in separate certificates is what makes that boundary something a verifier can see.
    /// @param tbs The TBS bytes, verbatim.
    /// @return The parsed and self-checked certificate.
    function parseRecovery(bytes calldata tbs) external view returns (Parsed memory) {
        return parse(tbs, PURPOSE_RECOVERY_TX, PURPOSE_RECOVERY_ACCESS, PURPOSE_RECOVERY_KEM);
    }

    /// @notice Parse a certificate authority's certificate, whose two keys are both cert-signing.
    /// @dev Both classes resolve to the same purpose, which is why {parse} matches on the
    ///      `(purpose, algorithm)` PAIR: an authority carries two keys under one purpose and matching on the
    ///      purpose alone would find the first of them twice and the second never.
    /// @dev No encapsulation purpose. An authority signs and is never sealed to, so {NO_KEM_PURPOSE} is
    ///      passed as a value the key loop can never match. An authority certificate carrying encapsulation
    ///      keys would parse them into slots the registry then discards, which is a shape worth refusing to
    ///      have at all.
    /// @param tbs The TBS bytes, verbatim.
    /// @return The parsed and self-checked certificate.
    function parseCa(bytes calldata tbs) external view returns (Parsed memory) {
        return parse(tbs, PURPOSE_CERT_SIGNING, PURPOSE_CERT_SIGNING, NO_KEM_PURPOSE);
    }

    /**
     * @notice Verify an issuer's dual signature over a TBS.
     * @dev Both must verify, not either. Two signatures under two different hardness assumptions is the
     *      entire reason a certificate carries two, and accepting one would collapse that to whichever
     *      family breaks first.
     *
     *      Provided for callers that verify an off-chain issuance against keys they already trust. The
     *      caller supplies the issuer's keys, so it is the caller's job to have taken them from a registered
     *      record rather than from its own calldata — a key handed in with the signature proves nothing.
     * @param tbs The signed TBS bytes.
     * @param issuerMlDsaKey The issuer's registered ML-DSA-87 cert-signing key.
     * @param issuerSlhDsaKey The issuer's registered SLH-DSA-SHAKE-256s cert-signing key.
     * @param mlDsaSignature The lattice signature over `tbs`.
     * @param slhDsaSignature The hash-based signature over `tbs`.
     * @return Whether both signatures verify.
     */
    function verifyIssuerSignatures(
        bytes memory tbs,
        bytes memory issuerMlDsaKey,
        bytes memory issuerSlhDsaKey,
        bytes memory mlDsaSignature,
        bytes memory slhDsaSignature
    ) external view returns (bool) {
        return FinalChainPrecompiles.verifyMlDsa87(issuerMlDsaKey, tbs, mlDsaSignature)
            && FinalChainPrecompiles.verifySlhDsa(issuerSlhDsaKey, tbs, slhDsaSignature);
    }

    /// @notice Refuse a TBS that is shorter than the parser is about to read.
    /// @dev Called before every read rather than once at the top, because the layout is variable-length: a
    ///      certificate can be well-formed up to its key block and truncated inside it, and a parser that
    ///      only checked the fixed header would read whatever calldata followed.
    /// @param tbs The TBS bytes.
    /// @param upto The offset the next read needs to be valid.
    function _need(bytes calldata tbs, uint256 upto) private pure {
        if (tbs.length < upto) revert Truncated(upto, tbs.length);
    }

    /// @notice Step over one four-byte-length-prefixed field and report where it was.
    /// @dev Bounds-checks the prefix before reading it and the value before returning, so a truncated
    ///      certificate cannot make the cursor run past the end of calldata. The caller recovers the value's
    ///      slice as `tbs[next - length:next]`.
    /// @param tbs The TBS bytes.
    /// @param p Offset of the length prefix.
    /// @return next Offset just past the field's value.
    /// @return length The field's declared length.
    function _skipLengthPrefixed(bytes calldata tbs, uint256 p)
        private
        pure
        returns (uint256 next, uint256 length)
    {
        _need(tbs, p + 4);
        length = uint32(bytes4(tbs[p:p + 4]));
        next = p + 4 + length;
        _need(tbs, next);
    }

    /// @notice Read a key identifier out of the TBS as one word.
    /// @dev Answers `bytes32(0)` for any length other than 32 rather than reverting. A key identifier that
    ///      is not 32 bytes is not a SHA3-256 digest, so it cannot match the value it is compared against,
    ///      and the comparison at the call site produces the correct refusal with no separate error to
    ///      define. The one legitimate short case is a zero-length authority key identifier, which the
    ///      caller must reject on its own terms.
    /// @param tbs The TBS bytes.
    /// @param start Offset of the field's value.
    /// @param length The field's declared length.
    /// @return The 32-byte value, or zero when the field is not 32 bytes long.
    function _bytes32At(bytes calldata tbs, uint256 start, uint256 length)
        private
        pure
        returns (bytes32)
    {
        // A SubjectKeyId that is not 32 bytes is not a SHA3-256 digest, so it
        // cannot match and the comparison will fail — which is the correct
        // outcome and needs no separate error.
        if (length != 32) return bytes32(0);
        return bytes32(tbs[start:start + 32]);
    }
}

contracts/finalchain/FinalChainPrecompiles.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may link this library into contracts deployed on a
//    Final DeFi Protocol chain in order to reach that chain's hash and
//    post-quantum signature-verification precompiles.
// 2. Integrators, node operators, and auditors may use it to reproduce and
//    independently re-verify any verdict those precompiles produced, as part of
//    their integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this library or a competing state plane derived
//    from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

/**
 * @title Final Chain Precompiles
 * @notice The three primitives Final Chain adds to the EVM, and the only
 *         supported way to reach them.
 *
 * @dev **These exist ONLY on Final Chain (chain id 48359).** They are provided
 * by this chain's own node binary, and
 * nothing at these addresses on Ethereum, Optimism or any other chain will
 * answer. A contract that calls them must be one that only ever runs here;
 * `assertAvailable` below is the cheap way to fail loudly rather than treat an
 * empty return as a verified signature.
 *
 * The addresses are the FIPS numbers, which is the whole allocation rule —
 * there is no local registry to consult and no way for two implementations to
 * disagree about where a primitive lives:
 *
 * | address | primitive | FIPS |
 * |---|---|---|
 * | `0x…0202` | SHA3-256 | 202 |
 * | `0x…0203` | ML-KEM-1024 key validation | 203 |
 * | `0x…0204` | ML-DSA-87 verify | 204 |
 * | `0x…0205` | SLH-DSA-SHAKE-256s verify | 205 |
 * | `0x…0207` | HQC-5 key validation | 207 |
 *
 * The two KEM addresses VALIDATE keys and do nothing else, for one reason:
 * encapsulation is a SENDER operation and decapsulation needs the secret key,
 * so neither belongs on a chain at all. Checking that a registered public key
 * is well-formed is hardening rather than a dependency, and nothing in this
 * system waits on it.
 *
 * HQC's number is 207. It had none when the KEM pair was chosen, which was the
 * one thing separating it from ML-KEM here — a primitive with no standard
 * number has no address under this rule, and inventing one would have been a
 * local convention masquerading as the global one.
 *
 * **No AEAD precompile, at any number.** The chain must never be able to
 * decrypt an intent, and checking a revealed body against its commitment is a
 * hash compare that `0x0202` already serves.
 *
 * ## Why this library refuses to take a public key from its caller
 *
 * It does take one — the primitives are pure functions and cannot do otherwise.
 * The rule lives one level up, in `FinalPqQuorum`: a key passed as an argument
 * proves nothing, because anyone holding a keypair can produce a valid
 * signature under it. Only a key read from `FinalIdentityRegistry` is evidence
 * about WHO signed. Every call site here must be able to answer "where did this
 * key come from" with "storage", never "calldata".
 *
 * ## `success` is not the answer
 *
 * A `staticcall` to a verifier returns two things and both matter. `success`
 * false means the call was malformed — usually a length bug in the caller — and
 * `success` true with a zero word means the signature did not verify. The
 * helpers below collapse both to `false` for the caller's convenience, which is
 * safe in that direction and only in that direction: treating a failed call as
 * a valid signature would be the whole security of the system.
 */
library FinalChainPrecompiles {
    /// @notice SHA3-256 (FIPS 202). NOT `keccak256`, which is the
    /// pre-standardisation padding and produces a different digest.
    address internal constant SHA3_256 = address(0x0202);
    /// @notice ML-DSA-87 verification (FIPS 204). Transaction-class keys.
    address internal constant ML_DSA_87 = address(0x0204);
    /// @notice SLH-DSA-SHAKE-256s verification (FIPS 205). Access-class keys.
    address internal constant SLH_DSA_SHAKE_256S = address(0x0205);

    /// @notice ML-KEM-1024 encapsulation-key validation (FIPS 203).
    /// @dev VALIDATES; it does not encapsulate. Runs FIPS 203 §7.2's own
    /// encapsulation-key check — the type check and the modulus check — and
    /// nothing else. Encapsulation is a sender operation and decapsulation
    /// needs the secret key, so neither belongs on a chain.
    address internal constant ML_KEM_1024 = address(0x0203);

    /// @notice HQC-5 public-key validation (FIPS 207).
    /// @dev Structural only: the length, and the three padding bits the
    /// encoding leaves beyond `n = 57637`. HQC has no cheap key-validity
    /// predicate and this does not pretend to one.
    address internal constant HQC_5 = address(0x0207);

    /// @notice ML-DSA-87 public key length. Round-3 Dilithium5 shares it.
    uint256 internal constant ML_DSA_87_PUBLIC_KEY_LEN = 2592;
    /// @notice ML-DSA-87 signature length. Round-3 Dilithium5 is 4595.
    uint256 internal constant ML_DSA_87_SIGNATURE_LEN = 4627;
    /// @notice SLH-DSA-SHAKE-256s public key length (`PK.seed ‖ PK.root`).
    uint256 internal constant SLH_DSA_SHAKE_256S_PUBLIC_KEY_LEN = 64;
    /// @notice SLH-DSA-SHAKE-256s signature length. The `f` set is 49,856.
    uint256 internal constant SLH_DSA_SHAKE_256S_SIGNATURE_LEN = 29792;

    /// @notice Thrown when a precompile is absent, i.e. this is not Final Chain
    /// or the node is stock reth rather than `final-reth`.
    error PrecompileUnavailable(address precompile);

    /**
     * @notice Reverts unless all five precompiles answer.
     * @dev Call this from a constructor. A contract whose security rests on PQ
     * verification must not deploy onto a chain that cannot perform it — the
     * failure mode otherwise is a quorum that reaches threshold with zero valid
     * signatures, discovered at the worst possible moment.
     *
     * The probe is SHA3-256 of the empty string, whose value is a published
     * FIPS 202 constant. It cannot be produced by an address with no code
     * (which returns empty) nor by `keccak256` (which gives a different digest
     * for the same input), so it distinguishes "the right precompile" from both
     * "nothing here" and "the wrong hash function".
     */
    function assertAvailable() internal view {
        bytes32 expected = 0xa7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a;
        (bool ok, bytes memory out) = SHA3_256.staticcall("");
        if (!ok || out.length != 32 || bytes32(out) != expected) {
            revert PrecompileUnavailable(SHA3_256);
        }
        // The two signature verifiers are probed by shape rather than by a
        // known-answer vector: a KAT here would put a 29,792-byte signature in
        // this contract's bytecode. A deliberately short input is a
        // *precompile error* by contract, so a FAILED call is the pass and a
        // silent success would mean something else is answering at the address.
        _probeRejectsShortInput(ML_DSA_87);
        _probeRejectsShortInput(SLH_DSA_SHAKE_256S);
        // The two KEM validators are probed the other way round, because they
        // are total by contract: a wrong length is a malformed KEY, which is
        // the question being asked, so they ANSWER rather than error. A
        // one-byte input must therefore come back as a well-formed `false`, and
        // a failed call means nothing is there.
        _probeAnswersFalse(ML_KEM_1024);
        _probeAnswersFalse(HQC_5);
    }

    /**
     * @dev A short input must make the precompile ERROR. The gas budget is the
     * whole subtlety.
     *
     * A reverting CONTRACT refunds the gas it did not use. A precompile that
     * returns an error consumes **everything forwarded to it** — and Solidity
     * forwards 63/64 of what is left by default. Two such probes in a
     * constructor therefore burn all but 1/4096 of the deployment's gas, and
     * the deploy fails with no revert data at all.
     *
     * That is not hypothetical: it is what happened the first time this ran
     * against a real `final-reth`, and no Foundry test could have caught it.
     * A mocked precompile is a contract, and a contract's `require` hands the
     * gas back.
     *
     * 5,000 is generous for a call that fails on a length check before any
     * cryptography runs, and small enough that both probes together are noise
     * against a deployment.
     */
    function _probeRejectsShortInput(address precompile) private view {
        bool ok;
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore8(ptr, 0x00)
            ok := staticcall(5000, precompile, ptr, 0x01, 0x00, 0x00)
        }
        if (ok) revert PrecompileUnavailable(precompile);
    }

    /**
     * @dev A one-byte input must come back as a well-formed zero word.
     *
     * The inverse of `_probeRejectsShortInput`, and the inversion is the point:
     * these two precompiles are TOTAL. Every byte string has an answer to "is
     * this a well-formed key", and for one byte the answer is no. A precompile
     * that errored here would be one that treats a malformed key as a caller
     * bug, which is the opposite of what a registry wants.
     *
     * Gas is bounded for the same reason as the other probe — an erroring
     * precompile consumes everything forwarded — even though the pass case
     * returns normally and refunds.
     */
    function _probeAnswersFalse(address precompile) private view {
        bool ok;
        bytes32 answer;
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore8(ptr, 0x00)
            ok := staticcall(5000, precompile, ptr, 0x01, ptr, 0x20)
            answer := mload(ptr)
        }
        if (!ok || answer != bytes32(0)) revert PrecompileUnavailable(precompile);
    }

    /**
     * @notice Is `encapsulationKey` a well-formed ML-KEM-1024 key?
     *
     * @dev The check a registry owes a sender. A malformed encapsulation key
     * stored on chain is an account whose intents cannot be sealed, and the
     * discovery happens at the first attempt to seal one — on the hybrid path,
     * as a pair silently reduced to one family, which is the failure with no
     * error attached.
     *
     * False rather than reverting on any shape, including the wrong length,
     * because the caller is asking a question and every input has an answer.
     */
    function isWellFormedMlKem1024(bytes memory encapsulationKey) internal view returns (bool) {
        return _validatesKey(ML_KEM_1024, encapsulationKey);
    }

    /// @notice Is `publicKey` a well-formed HQC-5 key?
    /// @dev Structural, and honestly partial — see the precompile. It catches a
    /// truncated key, a key from the wrong parameter set, and a tail carrying
    /// smuggled bytes, which are the three ways this goes wrong in practice.
    function isWellFormedHqc5(bytes memory publicKey) internal view returns (bool) {
        return _validatesKey(HQC_5, publicKey);
    }

    /// @dev A failed CALL is not a false answer. It means nothing is at the
    /// address — this is not Final Chain, or the node is stock reth — and
    /// reading it as "the key is malformed" would silently disable the check on
    /// exactly the deployment where it cannot run.
    function _validatesKey(address precompile, bytes memory key) private view returns (bool) {
        (bool ok, bytes memory out) = precompile.staticcall(key);
        if (!ok || out.length != 32) revert PrecompileUnavailable(precompile);
        return bytes32(out) != bytes32(0);
    }

    /// @notice FIPS 202 SHA3-256 over `data`.
    /// @dev The certificate schema hashes `TBSCertificate`, `SubjectKeyId` and
    /// `AuthorityKeyId` with this, so it is the only function that can check a
    /// `certHash` against the bytes it claims to summarise.
    function sha3_256(bytes memory data) internal view returns (bytes32 digest) {
        (bool ok, bytes memory out) = SHA3_256.staticcall(data);
        if (!ok || out.length != 32) revert PrecompileUnavailable(SHA3_256);
        digest = bytes32(out);
    }

    /// @notice Verify an ML-DSA-87 signature. False on any failure, including
    /// a malformed call.
    function verifyMlDsa87(bytes memory publicKey, bytes memory message, bytes memory signature)
        internal
        view
        returns (bool)
    {
        if (
            publicKey.length != ML_DSA_87_PUBLIC_KEY_LEN
                || signature.length != ML_DSA_87_SIGNATURE_LEN
        ) return false;
        return _verify(ML_DSA_87, publicKey, signature, message);
    }

    /// @notice Verify an SLH-DSA-SHAKE-256s signature. False on any failure.
    function verifySlhDsa(bytes memory publicKey, bytes memory message, bytes memory signature)
        internal
        view
        returns (bool)
    {
        if (
            publicKey.length != SLH_DSA_SHAKE_256S_PUBLIC_KEY_LEN
                || signature.length != SLH_DSA_SHAKE_256S_SIGNATURE_LEN
        ) return false;
        return _verify(SLH_DSA_SHAKE_256S, publicKey, signature, message);
    }

    /// @dev `publicKey ‖ signature ‖ message`, in that order. Both fixed-length
    /// fields come first so the message is unambiguously the remainder — the
    /// same reason the precompile takes no length prefix.
    function _verify(
        address precompile,
        bytes memory publicKey,
        bytes memory signature,
        bytes memory message
    ) private view returns (bool) {
        (bool ok, bytes memory out) =
            precompile.staticcall(abi.encodePacked(publicKey, signature, message));
        return ok && out.length == 32 && bytes32(out) != bytes32(0);
    }
}

contracts/finalchain/FinalChainTime.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may link this time library into contracts deployed on
//    a Final DeFi Protocol chain, and may read its constants to interpret the
//    timestamps and durations that chain publishes.
// 2. Integrators, indexers, and operators may use it to convert between this
//    chain's clock and the units their own systems keep, as part of their
//    integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this library or a competing state plane derived
//    from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

/**
 * @title Final Chain Time
 * @notice **On this chain, `block.timestamp` is MILLISECONDS, not seconds.**
 * @dev Every other EVM chain stamps seconds. This one cannot. It mints a block every 100 ms, and the protocol
 * requires block timestamps to strictly increase, so a second-denominated clock would exhaust its distinct
 * values ten times over per second. Milliseconds is the deliberate consequence, and it is a property of the
 * CHAIN itself rather than of any contract here — nothing in this library can change it, and nothing deployed
 * beside this library may assume otherwise.
 *
 * Every duration and every instant on this chain is therefore in milliseconds. This library exists so that fact
 * is stated in one place and converted in one place, instead of being assumed independently everywhere a
 * deadline or a delay is written.
 *
 * ## The naming rule, which is a safety rule
 *
 * A field or constant carrying a duration or an instant on this chain ends in `Ms`. This is not decoration. A
 * delay field named for seconds while holding milliseconds elapses a thousand times too fast: a one-day
 * recovery delay would mature in about eighty-six seconds, and a two-year dormancy threshold in under a day.
 * Those delays are the whole of what stands between a stolen credential and an account, so a name that states
 * the wrong unit is not a cosmetic defect — it is the defect, wearing a disguise. `Seconds`-suffixed names do
 * not appear in this directory and must not be introduced.
 *
 * A test harness is not a check on this. Standard EVM tooling stamps `block.timestamp` in seconds, so a suite
 * can agree with the contracts under test and both be wrong about the chain they deploy to. The unit has to be
 * carried by the names.
 *
 * Solidity's `hours` and `days` suffixes remain the clearest way to write a duration, so durations are written
 * as `24 hours * MS_PER_SECOND` rather than as a bare literal: the intent stays readable and the unit stays
 * explicit at the point of use.
 */
library FinalChainTime {
    /// @notice Milliseconds per second — the whole conversion between this chain's clock and ordinary time,
    ///         named once.
    /// @dev Multiply a `seconds`-denominated Solidity duration literal by this to express it in this chain's
    ///      units. It is deliberately the only place the factor appears.
    uint64 internal constant MS_PER_SECOND = 1_000;

    /// @notice Nanoseconds per millisecond — the divisor for values that arrive stamped in nanoseconds.
    /// @dev The certificate schema stamps validity windows in nanoseconds, so a certificate converts DOWN to
    ///      this chain's clock. Dividing rather than multiplying is the direction that cannot overflow, and it
    ///      truncates toward the past, which for a validity window is the conservative rounding.
    uint64 internal constant NS_PER_MILLISECOND = 1_000_000;

    /// @notice This chain's current time, in milliseconds.
    /// @dev A function rather than a bare `block.timestamp` read so the unit is visible at every call site.
    ///      It performs no arithmetic and exists purely so that reading the clock is self-describing, where
    ///      `block.timestamp` on this chain is silently a thousand times what a reader would assume.
    /// @return nowInMs The current block's timestamp, in milliseconds.
    function nowMs() internal view returns (uint64) {
        return uint64(block.timestamp);
    }
}

contracts/finalchain/FinalIdentityRegistry.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy this identity registry as part of a Final
//    DeFi Protocol state plane, and may register, rotate, and revoke identity
//    records in it under the authority this contract enforces.
// 2. Operators, integrators, and end users may read the certificates, public
//    keys, role bits, and signer bindings it holds, and may call its views to
//    resolve an identity, a sender, or a quorum roster.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this identity registry or a competing certificate
//    authority derived from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

import {FinalCertificate} from "./FinalCertificate.sol";
import {FinalChainTime} from "./FinalChainTime.sol";
import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalSweep} from "../utils/FinalSweep.sol";

/// @dev Commitment space for one stage's encapsulation pair.
///      Byte-equal to `FinalWalletFactory.DOMAIN_KEM_BUNDLE` and to the certificate issuer's own preimage
/// constant. Three independent derivations of one word: a mismatch in any of them is a certificate that
/// verifies nowhere, so the value is pinned by test against the other two rather than imported.
bytes32 constant DOMAIN_KEM_BUNDLE = keccak256("FINAL_KEM_BUNDLE_v01");

/// @dev Commitment space for the identity tree's wallet leaf.
///      Byte-equal to `IdentityRootModule.DOMAIN_IDENTITY_LEAF` on every execution chain. Restated rather
/// than imported because that module lives on other chains and no import would make the two one value; a
/// cross-contract parity test pins the pair. The spelling is FROZEN: the premined certificates were mined
/// against this exact constant, and the leaf it derives is the `certHash` inside a wallet's address
/// derivation, so changing a byte here moves addresses that already exist.
bytes32 constant DOMAIN_IDENTITY_LEAF = keccak256("FINAL_IDENTITY_LEAF_PQ_v01");

/// @dev Commitment space for the identity tree's ISSUER leaf.
///      An issuer projects under its own domain — `DOMAIN_ISSUER_LEAF ‖ certHash ‖ version ‖
/// issuerTreeRoot` — so an issuer record is stapleable for offline licence verification while the distinct
/// domain keeps it out of wallet admission: an execution chain's gateway folds with the wallet domain, so an
/// issuer leaf can never satisfy an identity-certificate check there. `issuerTreeRoot` is a RESERVED word,
/// zero until an issuer's own certificate-tree anchor is wired — the only clean path to offline licence
/// revocation, since fixed-depth insertion-ordered state trees cannot prove non-inclusion.
bytes32 constant DOMAIN_ISSUER_LEAF = keccak256("FINAL_ISSUER_LEAF_v01");

/// @dev The issuer name every chain-attested certificate carries, as a keccak digest.
///      The chain is the issuer but holds no keypair, so a chain-attested certificate carries this named
/// value in its issuer field: required by the wire format, verifying nothing on its own, and covered by
/// `certHash`. The name is deliberately environment-agnostic and jurisdiction-silent — the issuer is the
/// worldwide network rather than a legal entity, and an environment-specific name would fork `certHash` per
/// environment. Compared as a hash rather than as a string, so the check costs one word.
bytes32 constant CHAIN_ISSUER_DN_HASH = keccak256("CN=Final Chain,O=Final DeFi");

/// @dev The authority key identifier every chain-attested certificate names.
///      `SHA3-256(utf8("FINAL_CHAIN_AUTHORITY_v01"))` — a DOMAIN constant rather than the digest of a key,
/// because the chain issues certificates and holds no public key block to hash. Precomputed rather than
/// derived at construction: the harness the unit tests run under does not implement the real SHA3 function,
/// and the literal is pinned by test against a reference implementation. A zero-length authority key
/// identifier is reserved and is admitted nowhere.
bytes32 constant CHAIN_AUTHORITY_KEY_ID =
    0x9a6a5d8139ad2d28957698330aaa691017dba7dc80eb7cbec585239fb680bbab;

/**
 * @title Identity Leaf Sink
 * @notice The identity tree's projection door on the state-trees contract.
 * @dev A narrow interface rather than an import, because the trees contract imports THIS file — the
 *      dependency runs that way, and this is the one call that runs the other. Declaring the single method
 *      here keeps the cycle away from the compiler without duplicating either contract's surface.
 */
interface IIdentityLeafSink {
    /// @notice Recompute and store the identity-tree leaf for each named account.
    /// @dev Called inside the same transaction as every identity mutation, so an execution chain's admission
    ///      set sees a registration, rotation or revocation the moment this chain does. The leaf VALUE is
    ///      derived by the trees contract from the registry's post-mutation state, so the caller supplies
    ///      accounts and never a leaf.
    /// @param accounts The accounts whose leaves are stale.
    function syncIdentityLeaves(address[] calldata accounts) external;
}

/**
 * @title Revocation Recorder
 * @notice The revocation log's recording door.
 * @dev Same narrow-interface reasoning as the leaf sink above. `recorded` is read first, so a fingerprint
 *      somebody already recorded through the log's permissionless door cannot revert the registry mutation
 *      that feeds it.
 */
interface IRevocationRecorder {
    /// @notice Fold a permanently retired signer fingerprint into the revocation log.
    /// @dev The log applies its own permanence gate, reading this registry back; the call states nothing the
    ///      registry has not already decided.
    /// @param signerId The fingerprint that has lost standing for good.
    function record(bytes32 signerId) external;
    /// @notice Whether the log already holds `signerId`.
    /// @param signerId The fingerprint to look up.
    /// @return Whether a leaf for it exists.
    function recorded(bytes32 signerId) external view returns (bool);
}

/**
 * @title Final Identity Registry
 * @notice Who every party in the system is, on chain: one record per party, carrying its certificate and its
 *         actual public keys.
 * @dev Every service, every co-signer, every certificate authority and every operator has one record here.
 *      The record holds the party's public keys in full rather than commitments to them, and this contract is
 *      the certificate authority as well as the roster.
 *
 *      ## Where this runs
 *
 *      Only on this project's own reth-based chains. Verification happens inside precompiles that exist
 *      nowhere else: SHA3-256 at `0x0202`, ML-DSA-87 at `0x0204` and SLH-DSA-SHAKE-256s at `0x0205`, each
 *      address being that primitive's FIPS number. The constructor probes them and refuses to deploy where
 *      they are absent, so a registry of keys the chain cannot check never comes into existence. This
 *      contract takes part in no CREATE2 derivation — its address is per chain, and nothing derives an
 *      address from it — and nothing outside this directory imports it.
 *
 *      Gas is deliberately NOT a design constraint on that chain and must not be optimised for. Where a
 *      choice below trades gas for a verdict that is re-derivable from public state, the verdict wins: a
 *      signature checked in a precompile is a fact anyone can recompute, where the same check run in a
 *      library by whichever process happened to hold the keys is only a claim.
 *
 *      ## Keys are read from STORAGE, never from calldata
 *
 *      A commitment would be a quarter of the storage and would be enough to CHECK a key someone hands you.
 *      It is not enough to VERIFY A SIGNATURE, because verification needs the key itself — and a key that
 *      arrives in calldata proves nothing, since anyone holding a keypair can produce a valid signature under
 *      it. A quorum built on caller-supplied keys is a quorum of one: whoever built the calldata.
 *
 *      So the keys live here in full. `FinalPqQuorum` resolves a member through this registry and reads that
 *      member's key from this registry's storage, and "which key is co-signer three" has exactly one answer,
 *      in exactly one place. That is the load-bearing rule of every quorum on the chain, not an optimisation.
 *
 *      ## The certificate is the record, not a pointer to one
 *
 *      `certHash` is `SHA3-256(TBSCertificate)`: the certificate's own identity, and the handle revocation is
 *      keyed on. {registerWallet} and {registerIssuer} take the certificate's TBS bytes and read everything
 *      out of them — the digest, the serial, the key identifiers, the depth pair, the validity window and
 *      every public key. Neither takes a key argument, so no two arguments can disagree and no registrar can
 *      bind a certificate to a keypair that certificate does not contain.
 *
 *      ## The root is the first record here, not a self-signed file
 *
 *      This chain is the only root certificate authority, and the root is pinned as an entry in this registry
 *      rather than distributed as a self-signed certificate somebody has to install. Chain validation
 *      terminates here BY IDENTITY. Everything registered after the root is verified on chain, inside the
 *      precompiles, against what this registry already holds: the holder's own two signatures over the
 *      admission digest, the pinned chain-issuer constants, and — for a nested issuer — lineage to a
 *      registered parent whose depth admits it. There is no path by which a key enters this registry
 *      unattested; a registrar cannot register anything else.
 *
 *      ## Roles are a bitmask
 *
 *      One party is legitimately several things: a co-signer that also publishes, an operator that is also a
 *      guardian. A single enum would force either duplicate records for one key, which is two sources of
 *      truth about one party, or a role hierarchy nobody agrees on. A mask has neither problem, and a quorum
 *      asks whether an account CARRIES a capability rather than whether it IS a type.
 *
 *      ## Membership is hybrid-gated
 *
 *      Who is in this registry, and with which roles, is the root of every quorum on the chain, so it is the
 *      one thing no single key may decide. Once bootstrap is sealed, every membership mutation — register,
 *      roles, revoke, a hash-based signing key, the registrar threshold itself — and every state-plane
 *      configuration change routed through {requireRegistrarQuorum} takes a `ROLE_REGISTRAR` quorum whose
 *      approvals carry BOTH families: the ML-DSA-87 vote and the SLH-DSA seal. A lattice break cannot then
 *      rewrite the roster, and neither can a hash-function break; only both at once.
 *
 *      The bootstrap window is the only exception. While it is open the bootstrap admin writes alone, because
 *      every roster has to be installed by someone before it can install itself. {sealBootstrap} closes it
 *      irreversibly, and refuses to close it onto a registrar quorum that cannot be met.
 *
 *      ## The sender is not the account
 *
 *      Transactions on this chain are signed by ML-DSA-87, and the node derives `msg.sender` from the key as
 *      `keccak256(0x04 ‖ publicKey)[12:]`. That address pays gas and holds no authority. {accountOfSender}
 *      binds it to the identity whose live transaction key it derives from, so a `msg.sender` gate anywhere
 *      on this chain asks {senderHasRole} and resolves to the identity — and a key rotation moves the binding
 *      instead of the roster.
 *
 *      ## What this contract deliberately does not do
 *
 *      It never un-revokes: a revoked certificate is finished, and reversing that would reopen every past
 *      verification. It never enumerates a mapping inside a mutation — the registrars supply the chain list a
 *      revocation touches, and a fingerprint an incomplete list missed stays permanently recordable through
 *      the revocation log's own permissionless door. It holds no funds, exposes no payable entrypoint, and
 *      reserves nothing against a sweep. And it grants no capability by parsing one: a certificate says which
 *      keys a party holds, `roles` says what the party may do, and the two arrive as different arguments on
 *      purpose.
 */
contract FinalIdentityRegistry is FinalSweep {
    // ---------------------------------------------------------------- roles

    /// @notice May co-sign account-state rounds (tree 1).
    uint256 public constant ROLE_ACCOUNT_COSIGNER = 1 << 0;
    /// @notice May co-sign MMR / bundle-log advances.
    uint256 public constant ROLE_MMR_COSIGNER = 1 << 1;
    /// @notice May publish PHI ledger state (tree 2).
    uint256 public constant ROLE_PHI_PUBLISHER = 1 << 2;
    /// @notice May publish vAsset state (tree 3).
    uint256 public constant ROLE_VASSET_PUBLISHER = 1 << 3;
    /// @notice May publish oracle data (tree 4).
    uint256 public constant ROLE_ORACLE_PUBLISHER = 1 << 4;
    /// @notice May publish settlement / asset registry roots (trees 5 and 6).
    uint256 public constant ROLE_REGISTRY_PUBLISHER = 1 << 5;
    /// @notice May act as a wallet guardian.
    uint256 public constant ROLE_GUARDIAN = 1 << 6;
    /// @notice May submit transactions on behalf of the protocol.
    uint256 public constant ROLE_RELAYER = 1 << 7;
    /// @notice May register and revoke identities once bootstrap is sealed.
    uint256 public constant ROLE_REGISTRAR = 1 << 8;
    /// @notice A certificate authority — the root, or an intermediate under it.
    uint256 public constant ROLE_CERTIFICATE_AUTHORITY = 1 << 9;
    /// @notice May co-sign `FinalSettlementLog` appends — the cross-chain
    /// settlement quorum, the same members whose LMS keys satisfy the
    /// execution chains' settlement set. A role of its own rather than a
    /// second use of `ROLE_REGISTRY_PUBLISHER`: the registries (trees 5/6)
    /// change on listing cadence and settlement leaves release custody, and
    /// one role for both would put the value plane behind the listing roster.
    uint256 public constant ROLE_SETTLEMENT_COSIGNER = 1 << 10;

    // ----------------------------------------------------- action domains

    /// @notice Action domain for registering or rotating a wallet identity.
    /// @dev One domain per membership mutation, so an approval to grant a role can never be replayed as one
    ///      to revoke. This registry is its own verifying contract for all of these, and the digest also
    ///      binds a per-contract counter, so an approval authorises exactly one action once.
    bytes32 public constant DOMAIN_REGISTER_WALLET = keccak256("FINAL_REGISTRY_REGISTER_WALLET_v01");
    /// @notice Action domain for registering or rotating an issuer.
    bytes32 public constant DOMAIN_REGISTER_ISSUER = keccak256("FINAL_REGISTRY_REGISTER_ISSUER_v01");
    /// @notice The admission proof-of-possession digest domain.
    /// @dev The HOLDER signs `keccak256(abi.encode(domain, chainid, registry, certHash, recoveryCertHash,
    ///      gateNonce))` with the live transaction key (ML-DSA-87) AND the live access key
    ///      (SLH-DSA-SHAKE-256s) — both families, in the admission transaction, verified by the precompiles.
    ///      Possession lives in the TRANSACTION, never in the artifact, so holding a copy of somebody's
    ///      public certificate admits nothing.
    bytes32 public constant DOMAIN_IDENTITY_ADMISSION = keccak256("FINAL_IDENTITY_ADMISSION_v01");
    /// @notice Action domain for root-plane global certificate revocation, by handle.
    bytes32 public constant DOMAIN_REVOKE_CERTIFICATE =
        keccak256("FINAL_REGISTRY_REVOKE_CERTIFICATE_v01");
    /// @notice Digest domain for an issuer revoking a certificate it signed off chain.
    /// @dev Signed by the issuer's own registered cert-signing keys rather than approved by a quorum, and
    ///      bound to the issuer's own gate nonce, so one issuer's revocations cannot be replayed as
    ///      another's.
    bytes32 public constant DOMAIN_ISSUER_CERT_REVOCATION =
        keccak256("FINAL_ISSUER_CERT_REVOCATION_v01");
    /// @notice Action domain for recording an account's hash-based signing key.
    bytes32 public constant DOMAIN_REGISTER_LMS_KEY = keccak256("FINAL_REGISTRY_REGISTER_LMS_KEY_v01");
    /// @notice Action domain for replacing an identity's capability bitmask.
    bytes32 public constant DOMAIN_SET_ROLES = keccak256("FINAL_REGISTRY_SET_ROLES_v01");
    /// @notice Action domain for retiring an identity.
    bytes32 public constant DOMAIN_REVOKE = keccak256("FINAL_REGISTRY_REVOKE_v01");
    /// @notice Action domain for moving the registrar threshold itself.
    bytes32 public constant DOMAIN_SET_REGISTRAR_THRESHOLD =
        keccak256("FINAL_REGISTRY_SET_REGISTRAR_THRESHOLD_v01");

    /// @notice The algorithm identifier the sender derivation is domain-separated by.
    /// @dev ML-DSA-87, FIPS 204 — the only algorithm this chain's transaction envelope admits. Prefixing it
    ///      means a key of another family can never derive the same sender address.
    uint8 private constant ENVELOPE_ALG_ML_DSA_87 = 4;

    // ------------------------------------------------------------- storage

    /**
     * @title Identity
     * @notice One party's on-chain identity.
     * @dev `version` increments on every mutation, and that increment is what a rotation IS: the record is
     *      replaced rather than appended to, and the version is how a reader on another chain knows which of
     *      two copies it has seen is newer.
     */
    struct Identity {
        /// SHA3-256 of the LIVE certificate's TBS bytes. The revocation handle.
        bytes32 certHash;
        /// SHA3-256 of the RECOVERY certificate's TBS bytes.
        bytes32 recoveryCertHash;
        /// The certificate's 32-byte serial, `16 B entropy ‖ 16 B counter`.
        bytes32 serial;
        /// SHA3-256 of this certificate's public key block. A child names it in
        /// its own `AuthorityKeyId`, which is how the chain links the two.
        bytes32 subjectKeyId;
        /// Capability bitmask. Zero for a registered-but-idle party.
        uint256 roles;
        /// Position on the delegation axis; 0 is the Final Chain root.
        uint8 depth;
        /// Deepest level this key may issue to. `== depth` means it signs no
        /// certificates at all, which is every end entity.
        uint8 maxDelegationDepth;
        /// Milliseconds since the epoch, on this chain's clock. The certificate schema stamps validity in
        /// nanoseconds and the parser converts on the way in, so nothing here ever compares across units.
        uint64 notBefore;
        /// Milliseconds since the epoch, or 0 for "never expires" — which the certificate schema allows and
        /// personal identity certificates use. The bound is exclusive.
        uint64 notAfter;
        /// Monotonic. A rotation that does not advance it is refused.
        uint64 version;
        /// Set by `revoke`. Never unset: a revoked certificate is finished, and
        /// an un-revoke would make every past verification re-openable.
        bool revoked;
        /// Distinguishes "no record" from "a record whose fields are all zero".
        bool registered;
    }

    /**
     * @title Lms Key
     * @notice A hash-based (LMS) signing key held by a registered account.
     * @dev The execution chains' quorums verify LMS rather than ML-DSA, because those chains have no
     *      post-quantum precompiles and check a keccak hash chain instead. Those keys are the authority over
     *      the post-quantum anchor, and therefore over post-quantum execution — which makes "who holds this
     *      fingerprint?" a question the state plane has to be able to answer, exactly as it answers it for
     *      every other key.
     *
     *      Recorded against an account that is ALREADY registered, so an LMS key is a capability of a known
     *      identity rather than a standalone credential. It inherits that identity's revocation: a revoked
     *      account's signer is a revoked signer, with nothing extra to remember to do.
     */
    struct LmsKey {
        /// `I`, hashed into every step of the signature.
        bytes16 keyId;
        /// Merkle tree height. Bound into the fingerprint, because the leaf
        /// commits to node `2^h + q` and a signer who could vary it could vary
        /// the numbering.
        uint8 height;
        /// `T[1]`, the LMS public key.
        bytes32 root;
        /// Monotonic. A rotation that does not advance it is refused, so a
        /// replayed registration cannot reinstate a superseded key.
        uint64 version;
        /// Distinguishes "no key" from "a key whose fields are all zero".
        bool registered;
    }

    /// @notice The hash-based (LMS) signing key an account holds, per chain.
    /// @dev One slot per account AND chain. A single-use hash-based counter is a complete defence only while
    ///      the key it names signs for ONE chain, so the roster is stored the way it is armed: the same
    ///      operator is a different signer on every chain, and a rotation on one says nothing about another.
    mapping(address account => mapping(uint64 chainId => LmsKey)) private _lmsKey;
    /**
     * @title Lms Binding
     * @notice What a signer fingerprint is bound to: the account holding it and the chain it signs for.
     * @dev Two fields in one slot, deliberately. This contract sits within a few bytes of the deployed-code
     *      ceiling, so anything added to this surface has to pay for itself in bytecode first — which is why
     *      checks that no authority consults, such as refusing a zero chain identifier, are left to the
     *      publisher off chain rather than spent here.
     */
    struct LmsBinding {
        /// The account that registered the fingerprint. Zero means no account ever did.
        address account;
        /// The chain that registration was for. Zero alongside a zero account, for a fingerprint never
        /// registered.
        uint64 chainId;
    }

    /// @notice Which account a signer fingerprint belongs to, and which chain it signs for.
    /// @dev The lookup the whole LMS record exists for: an execution chain's roster names fingerprints and
    ///      nothing else, so without this the keys behind those names are unattributable. Written once at
    ///      registration and left in place when the key is superseded, because attribution is history — a
    ///      signature made under a retired key was still made by that operator.
    ///
    ///      The chain it names is what selects the slot {lmsSignerIsLive} resolves the fingerprint against.
    mapping(bytes32 signerId => LmsBinding) private _lmsBinding;

    /// @notice The identity record for an account.
    mapping(address account => Identity) private _identity;
    /// @notice The live transaction key, ML-DSA-87: spending, and every high-cadence protocol action.
    /// @dev All four key slots are stored in FULL rather than as commitments, because the precompiles verify
    ///      against a KEY and a key that arrived in calldata proves nothing about who signed. This is the
    ///      rule every quorum on this chain rests on.
    /// @dev A certificate authority has two keys rather than four, and they live in the two active slots.
    ///      One storage shape rather than two, because every reader would otherwise have to know which kind
    ///      of party it was looking at before it could look.
    mapping(address account => bytes) private _activeTransactionKey;
    /// @notice The live access key, SLH-DSA-SHAKE-256s: identity, rotation and guardianship.
    mapping(address account => bytes) private _activeAccessKey;
    /// @notice The pre-committed recovery transaction key, ML-DSA-87. Empty for a certificate authority.
    mapping(address account => bytes) private _recoveryTransactionKey;
    /// @notice The pre-committed recovery access key, SLH-DSA-SHAKE-256s. Empty for a certificate
    ///         authority.
    mapping(address account => bytes) private _recoveryAccessKey;
    /// @notice The seal key: a service's second SLH-DSA-SHAKE-256s key, which co-signs execution-class
    ///         quorum decisions.
    /// @dev Empty for every identity whose certificate carries no seal slot, which is every user wallet and
    ///      every certificate authority. An identity with no seal can never contribute to a sealed quorum,
    ///      so {sealableMemberCount} counts this rather than counting role bits.
    mapping(address account => bytes) private _activeSealKey;
    /// @notice The live stage's ML-KEM-1024 encapsulation key, the lattice half of the pair.
    /// @dev Two algorithms per stage — ML-KEM-1024 and HQC-5 — so a break in either family leaves the other
    ///      standing, the same reasoning that pairs the two signature families. The pair is written and
    ///      cleared together, so an account holds both or neither.
    /// @dev Stored as the RAW keys, like the signing keys, because a registry that held only commitments
    ///      could not answer "encapsulate to this party" without a second lookup somewhere less
    ///      authoritative.
    mapping(address account => bytes) private _activeKemMlKem;
    /// @notice The live stage's HQC-5 encapsulation key, the code-based half of the pair.
    mapping(address account => bytes) private _activeKemHqc;
    /// @notice The recovery stage's ML-KEM-1024 encapsulation key. Empty when the account has no recovery
    ///         stage.
    mapping(address account => bytes) private _recoveryKemMlKem;
    /// @notice The recovery stage's HQC-5 encapsulation key. Empty when the account has no recovery stage.
    mapping(address account => bytes) private _recoveryKemHqc;
    /// @notice Reverse index. A certificate identifies exactly one account, so
    /// presenting a `certHash` is enough to find who it belongs to.
    mapping(bytes32 certHash => address account) public accountOfCertificate;
    /// @notice Revocation by certificate, independent of the account record.
    /// A certificate stays revoked even if its account is later re-registered
    /// under a new one.
    mapping(bytes32 certHash => bool) public certificateRevoked;
    /// @notice Who revoked a certificate through the ISSUER half of the lane.
    /// Scoped by the verifier: the entry binds only when the recorded revoker
    /// is the certificate's own issuer. Never gates registration.
    mapping(bytes32 certHash => address) public certificateRevokedBy;

    /// @notice Every registered account, in registration order. Small by
    /// construction — this is services and co-signers, not wallets.
    address[] private _accounts;

    /// @notice Bootstrap authority. Zero once `sealBootstrap` has run.
    address public bootstrapAdmin;
    /// @notice Whether registration still accepts the bootstrap admin.
    bool public bootstrapSealed;

    /// @notice Where identity mutations project the tree-8 leaf, same-tx.
    /// Zero only before {wireStatePlane} — the deploy tooling wires it before
    /// the first registration, and the projection is skipped while unset so
    /// the wiring transaction itself can be ordered freely in the bootstrap
    /// window.
    address public stateTrees;
    /// @notice Where the PERMANENT standing losses — revocation and LMS-key
    /// supersession — are recorded, same-tx. Zero only before {wireStatePlane}.
    address public revocationLog;

    /// @notice Sealed `ROLE_REGISTRAR` approvals a membership mutation needs.
    /// @dev Zero until set, and bootstrap cannot be sealed while it is zero or
    /// unreachable: a registry sealed behind a threshold nobody can meet is a
    /// registry nobody can ever write to again.
    uint256 public registrarThreshold;
    /// @notice Replay counter per verifying contract — this registry for its
    /// own mutations, each state-plane contract for its configuration. Bound
    /// into every registrar digest, so an approval is for exactly one action.
    mapping(address caller => uint64) private _gateNonce;
    /// @notice The identity a Final Chain sender belongs to. See the contract
    /// notes: a sender is derived from the `activeTransaction` key and is not
    /// the account.
    mapping(address sender => address account) public accountOfSender;

    // -------------------------------------------------------------- events

    /// @notice An identity was registered, or an existing one rotated onto a new certificate set.
    /// @param account The identity written.
    /// @param certHash The live certificate's handle.
    /// @param roles The capability bitmask now in force.
    /// @param version The record's monotonic version.
    event IdentityRegistered(
        address indexed account, bytes32 indexed certHash, uint256 roles, uint64 version
    );
    /// @notice An identity's capability bitmask was replaced.
    /// @param account The identity whose roles changed.
    /// @param previousRoles The mask before the change.
    /// @param newRoles The mask now in force.
    event IdentityRolesChanged(address indexed account, uint256 previousRoles, uint256 newRoles);
    /// @notice An account's hash-based signing key for one chain was recorded or rotated.
    /// @param account The identity that holds the key.
    /// @param signerId The fingerprint an execution chain's roster names.
    /// @param chainId The chain the key is armed for.
    /// @param keyId The LMS key identifier.
    /// @param height The Merkle tree height.
    /// @param root The LMS public key.
    /// @param version The lineage counter for this account and chain.
    event LmsKeyRegistered(
        address indexed account,
        bytes32 indexed signerId,
        uint64 indexed chainId,
        bytes16 keyId,
        uint8 height,
        bytes32 root,
        uint64 version
    );
    /// @notice An identity was retired. Irreversible, and its roles are cleared in the same transaction.
    /// @param account The identity that was revoked.
    /// @param certHash The certificate it held at the time.
    event IdentityRevoked(address indexed account, bytes32 indexed certHash);
    /// @notice One revocation-lane entry.
    /// @param certHash The certificate that was revoked.
    /// @param revoker Zero for a root-plane revocation, the issuing identity for an issuer's own.
    event CertificateRevoked(bytes32 indexed certHash, address indexed revoker);
    /// @notice The bootstrap window closed. After this there is no single-caller write path left.
    /// @param sealedBy The bootstrap admin that closed it, immediately before being cleared.
    event BootstrapSealed(address indexed sealedBy);
    /// @notice The one-shot state-plane wiring landed. Emitted at most once in this contract's lifetime.
    /// @param stateTrees The state-trees contract that owns the identity tree.
    /// @param revocationLog The append-only log of retired signer fingerprints.
    event StatePlaneWired(address stateTrees, address revocationLog);
    /// @notice The number of sealed registrar approvals a membership mutation needs was set.
    /// @param threshold The new threshold.
    event RegistrarThresholdSet(uint256 threshold);
    /// @notice A registrar quorum authorized an action.
    /// @param verifyingContract The contract the approvals were collected for, and whose counter was burned.
    /// @param actionDomain The action domain the approvals bound.
    /// @param nonce The counter value the approvals were made over; the next action needs the next one.
    /// @param valid How many approvals verified.
    event RegistrarQuorumApproved(
        address indexed verifyingContract, bytes32 indexed actionDomain, uint64 nonce, uint256 valid
    );

    // -------------------------------------------------------------- errors

    /// @notice The caller holds none of the authority the entry point requires.
    /// @param caller The address that called.
    error NotAuthorized(address caller);
    /// @notice The bootstrap window is already closed. Closing it is irreversible.
    error BootstrapAlreadySealed();
    /// @notice No record claims this account, or a zero address was offered as one.
    /// @param account The address that was named.
    error UnknownAccount(address account);
    /// @notice A certificate's encapsulation key failed the chain's own well-formedness check.
    /// @dev Names the algorithm, because the pair is stored together and "one of these two" is not an
    ///      actionable answer.
    /// @param account The account being registered.
    /// @param algorithmId The algorithm whose key was malformed.
    error MalformedEncapsulationKey(address account, uint16 algorithmId);
    /// @notice The certificate is already bound to a different account. One certificate identifies exactly
    ///         one party.
    /// @param certHash The certificate's handle.
    /// @param boundTo The account that already holds it.
    error CertificateAlreadyBound(bytes32 certHash, address boundTo);
    /// @notice The certificate has been revoked, or the account's own certificate has. Revocation is never
    ///         undone, so this is terminal for that handle.
    /// @param certHash The revoked certificate's handle.
    error CertificateIsRevoked(bytes32 certHash);
    /// @notice A registration or rotation did not advance the record's version. Monotonicity is what stops a
    ///         replayed transaction reinstating credentials their holder has moved off.
    /// @param current The version on record.
    /// @param offered The version the caller presented.
    error VersionNotNewer(uint64 current, uint64 offered);
    /// @notice The named account does not carry `ROLE_CERTIFICATE_AUTHORITY`, or does not currently stand.
    /// @param issuer The account that was named.
    error IssuerNotACertificateAuthority(address issuer);
    /// @notice The named parent has reached its own delegation bound and may issue nothing further.
    /// @param issuer The parent account.
    /// @param depth The parent's depth.
    /// @param maxDelegationDepth The deepest level the parent may issue to.
    error IssuerMayNotSign(address issuer, uint8 depth, uint8 maxDelegationDepth);
    /// @notice A certificate sits at a depth its lineage does not put it at. Levels cannot be skipped,
    ///         because skipping one is how an issuer escapes its own delegation bound.
    /// @param got The depth the certificate declares.
    /// @param want The depth its lineage requires.
    error WrongDepth(uint8 got, uint8 want);
    /// @notice A child certificate claims a deeper delegation bound than the parent that admits it.
    /// @param child The child's `maxDelegationDepth`.
    /// @param issuer The parent's `maxDelegationDepth`.
    error DelegationWidened(uint8 child, uint8 issuer);
    /// @notice The certificate names an authority key that is not its declared parent's subject key.
    /// @param got The authority key identifier the certificate carries.
    /// @param want The parent's subject key identifier.
    error AuthorityKeyIdMismatch(bytes32 got, bytes32 want);
    /// @notice The live and recovery certificates carry different serials, so they describe two different
    ///         certificate sets rather than two stages of one.
    /// @param liveSerial The live certificate's serial.
    /// @param recoverySerial The recovery certificate's serial.
    error StagesDisagree(bytes32 liveSerial, bytes32 recoverySerial);
    /// @notice An LMS tree height outside 1 through 24, the range the verifier admits.
    /// @param height The height offered.
    error LmsHeightOutOfRange(uint8 height);
    /// @notice A zero LMS root commits to no tree and is refused.
    error LmsRootIsZero();
    /// @notice This signer fingerprint already belongs to a different account.
    /// @param signerId The fingerprint offered.
    /// @param boundTo The account that already holds it.
    error LmsKeyAlreadyBound(bytes32 signerId, address boundTo);
    /// @notice Two identities cannot share a transaction key: the sender it derives would be attributable to
    ///         both.
    /// @param sender The derived sender address.
    /// @param boundTo The account that already claims it.
    error SenderAlreadyBound(address sender, address boundTo);
    /// @notice Fewer registrars able to seal than the threshold asks for.
    /// @param sealable How many standing registrars hold a seal key.
    /// @param threshold How many approvals a membership mutation needs.
    error RegistrarThresholdUnreachable(uint256 sealable, uint256 threshold);
    /// @notice A zero registrar threshold was offered, or a quorum was demanded before one was set. A zero
    ///         threshold is a registry with no authority behind its membership.
    error RegistrarThresholdIsZero();
    /// @notice {wireStatePlane} has already run. Both pointers are trust topology and are written once.
    error StatePlaneAlreadyWired();
    /// @notice {wireStatePlane} was handed a zero address for the trees or for the revocation log.
    error ZeroStatePlane();
    /// @notice The holder's proof of possession did not verify: one family failed, or the digest was built
    ///         over the wrong nonce.
    /// @param account The account the admission was for.
    error AdmissionProofInvalid(address account);
    /// @notice The certificate does not name the chain's authority key, so it is not chain-attested.
    /// @param authorityKeyId The authority key identifier that was presented.
    error NotChainAttested(bytes32 authorityKeyId);
    /// @notice The certificate's issuer name is not the chain's own.
    /// @param issuerDnHash The digest of the name that was presented.
    error WrongIssuerDn(bytes32 issuerDnHash);
    /// @notice A chain-attested end entity sits at depth 1 with `maxDelegationDepth == depth`; anything else
    ///         is not an end entity.
    /// @param depth The certificate's position on the delegation axis.
    /// @param maxDelegationDepth The deepest level it may issue to.
    error NotAnEndEntity(uint8 depth, uint8 maxDelegationDepth);
    /// @notice An issuer that cannot sign is an end entity wearing an issuer profile, and belongs in
    ///         {registerWallet}.
    /// @param depth The certificate's position on the delegation axis.
    /// @param maxDelegationDepth The deepest level it may issue to.
    error IssuerCannotSign(uint8 depth, uint8 maxDelegationDepth);
    /// @notice A registered issuer's certificate never expires.
    /// @dev Expiry is the passive half of an issuer's lifecycle, so a zero `NotAfter` is refused here even
    ///      though the certificate schema allows one for an end entity.
    error IssuerMustExpire();
    /// @notice An issuer validity window past {MAX_ISSUER_VALIDITY_MS}.
    /// @param notBefore The certificate's start, in this chain's milliseconds.
    /// @param notAfter The certificate's end, in this chain's milliseconds.
    error IssuerValidityTooLong(uint64 notBefore, uint64 notAfter);
    /// @notice An institution registration whose subject name carries no ISO 3166 country component, or
    ///         whose institution extension is too short to hold one.
    /// @dev Only the trust root is jurisdiction-silent; a registered institution names where it answers for
    ///      itself.
    error JurisdictionMissing();
    /// @notice The subject name's country and the institution extension's `jurisdiction` field disagree, or
    ///         the extension's jurisdiction is not a two-byte country code.
    error JurisdictionMismatch();

    // --------------------------------------------------------- constructor

    /**
     * @notice Deploy the registry with a bootstrap registrar in place.
     * @dev The precompile probe is the point of the constructor. This contract is meaningless on a chain
     *      that cannot verify post-quantum signatures, and deploying it there would produce a registry full
     *      of keys nothing on that chain can check — so it refuses to exist where the precompiles are
     *      absent rather than existing and being trusted.
     *
     *      The admin is the whole authority until {sealBootstrap} runs, because every roster has to be
     *      installed by someone before it can install itself.
     * @param admin The bootstrap registrar. Genesis names the chain deployer.
     */
    constructor(address admin) {
        FinalChainPrecompiles.assertAvailable();
        bootstrapAdmin = admin;
    }

    // ----------------------------------------------------------- authority

    /**
     * @notice The authority gate on every membership mutation this registry performs.
     * @dev Bootstrap is a real window, not a formality: every roster in this system has to be installed by
     *      someone before it can install itself, and a design that pretends otherwise ends up with a roster
     *      that cannot be brought into existence at all. It is closed by {sealBootstrap}, irreversibly.
     *
     *      While the window is open the admin writes alone. Once it is closed there is no single-caller path
     *      left — not for a registrar, not for anyone — and every mutation goes through the sealed registrar
     *      quorum, whose approvals carry both signature families.
     * @param actionDomain One of the `DOMAIN_*` constants naming the mutation.
     * @param payloadDigest The mutation's own arguments, folded.
     * @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
     * @param approvals The sealed registrar quorum. Empty while bootstrap is open.
     */
    function _requireMembershipAuthority(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) private {
        if (!bootstrapSealed && msg.sender == bootstrapAdmin) return;
        _requireRegistrarQuorum(address(this), actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /**
     * @notice The sealed registrar quorum, for the other contracts in the state plane.
     * @dev `msg.sender` — the calling contract — is the verifying contract the digest binds and the counter
     *      it burns, so an approval collected for one contract's configuration cannot be spent on another's.
     *      The caller decides its own bootstrap exemption before calling; this function knows no caller's
     *      admin and applies none.
     *
     *      Anyone may SUBMIT such a transaction. Authority is the approvals, not the sender, which is the
     *      whole point of a quorum.
     * @param actionDomain The caller's own action domain for the change being authorised.
     * @param payloadDigest The change's arguments, folded by the caller.
     * @param anchorBlock The block the registrars read the roster at.
     * @param approvals The registrar approvals, each carrying both families.
     */
    function requireRegistrarQuorum(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireRegistrarQuorum(msg.sender, actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /// @notice Burn one gate nonce and require a sealed registrar quorum over the action.
    /// @dev The digest is `FinalPqQuorum.digest(verifyingContract, actionDomain, anchorBlock,
    ///      keccak256(abi.encode(nonce, payloadDigest)))`. The counter is burned BEFORE verification, so an
    ///      approval set is spent whether or not it turns out to be sufficient.
    ///
    ///      The seal is required rather than optional: membership is the hybrid class, and an approval
    ///      carrying only the lattice vote is not an approval here.
    /// @param verifyingContract The contract the approvals are for, and whose counter is burned.
    /// @param actionDomain One of the `DOMAIN_*` constants, so an approval to grant cannot be replayed to
    ///        revoke.
    /// @param payloadDigest The action's own arguments, folded.
    /// @param anchorBlock The block the registrars read the roster at.
    /// @param approvals The registrar approvals, each carrying both families.
    function _requireRegistrarQuorum(
        address verifyingContract,
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) private {
        if (registrarThreshold == 0) revert RegistrarThresholdIsZero();
        uint64 nonce = _gateNonce[verifyingContract];
        _gateNonce[verifyingContract] = nonce + 1;
        bytes32 quorumDigest = FinalPqQuorum.digest(
            verifyingContract, actionDomain, anchorBlock, keccak256(abi.encode(nonce, payloadDigest))
        );
        uint256 valid = FinalPqQuorum.require_(
            this,
            approvals,
            quorumDigest,
            ROLE_REGISTRAR,
            registrarThreshold,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            true
        );
        emit RegistrarQuorumApproved(verifyingContract, actionDomain, nonce, valid);
    }

    /**
     * @notice Set how many sealed registrar approvals a membership mutation needs.
     * @dev The bootstrap admin while the window is open; the current registrar quorum afterwards, so a
     *      registrar set that grows or shrinks can move the threshold to match itself.
     *
     *      Refuses a threshold the sealable registrars cannot meet, and refuses zero. Both are a registry
     *      that can never be written to again, and the way that presents is every membership mutation
     *      reverting forever with nothing naming the threshold as the cause.
     * @param threshold How many sealed approvals a mutation needs. Must be reachable and non-zero.
     * @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
     * @param approvals The sealed registrar quorum. Empty while bootstrap is open.
     */
    function setRegistrarThreshold(
        uint256 threshold,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireMembershipAuthority(
            DOMAIN_SET_REGISTRAR_THRESHOLD, keccak256(abi.encode(threshold)), anchorBlock, approvals
        );
        if (threshold == 0) revert RegistrarThresholdIsZero();
        uint256 sealable = sealableMemberCount(ROLE_REGISTRAR);
        if (sealable < threshold) revert RegistrarThresholdUnreachable(sealable, threshold);
        registrarThreshold = threshold;
        emit RegistrarThresholdSet(threshold);
    }

    /// @notice The replay counter the next registrar approval for `caller` must be made over.
    /// @dev One counter per verifying contract, so an approval collected for one contract's configuration
    ///      cannot be spent on another's. A caller reads this to build the digest its registrars will sign.
    /// @param caller The verifying contract the approvals will name — this registry for its own mutations.
    /// @return The value the next approval must bind.
    function gateNonceOf(address caller) external view returns (uint64) {
        return _gateNonce[caller];
    }

    // -------------------------------------------------------- LMS signers

    /**
     * @notice The roster identity of an LMS public key.
     * @dev Byte-identical to `FinalRootAuthority.signerId` on the execution chains. Restated rather than
     *      imported because the two live on different chains and no import would make them one value —
     *      which is precisely why a test pins them together. A drift here would make every lookup miss while
     *      looking perfectly well-formed.
     *
     *      The height is bound into the fingerprint as well as the root, because a leaf commits to a node
     *      number derived from it, so a signer free to vary the height could vary the numbering.
     * @param keyId The LMS key identifier.
     * @param height The Merkle tree height.
     * @param root The LMS public key.
     * @return The fingerprint an execution chain's roster names.
     */
    function lmsSignerId(bytes16 keyId, uint8 height, bytes32 root) public pure returns (bytes32) {
        return keccak256(abi.encode(keyId, height, root));
    }

    /**
     * @notice Record the hash-based (LMS) signing key an already-registered account holds for one chain.
     * @dev Membership-gated, like every other write here.
     *
     *      Deliberately NOT a certificate: an LMS key is a capability of an existing identity, not an
     *      identity of its own. Binding it to an account means it inherits that account's revocation, so
     *      retiring a compromised operator is one action rather than one action per key they hold.
     *
     *      A rotation records the SUPERSEDED fingerprint into the revocation log in the same transaction, so
     *      the execution chains' suspension lane never depends on someone noticing. The superseded
     *      fingerprint is left BOUND to this account rather than cleared, because attribution is history.
     *
     *      A zero `chainId` is a tooling mistake rather than an attack — the slot it occupies is
     *      self-consistent and no authority consults it — so the publisher refuses it off chain and this
     *      contract spends no bytecode on the check.
     * @param account Must already be registered and not revoked.
     * @param chainId The execution chain this key is armed for.
     * @param keyId The LMS key identifier, hashed into every step of a signature under it.
     * @param height The Merkle tree height, 1 through 24.
     * @param root The LMS public key. Zero commits to no tree and is refused.
     * @param version Strictly increasing per account and chain. A rotation that does not advance it is
     *        refused, so a replayed registration cannot reinstate a key the operator has moved off.
     * @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
     * @param approvals The sealed registrar quorum. Empty while bootstrap is open.
     */
    function registerLmsKey(
        address account,
        uint64 chainId,
        bytes16 keyId,
        uint8 height,
        bytes32 root,
        uint64 version,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireMembershipAuthority(
            DOMAIN_REGISTER_LMS_KEY,
            keccak256(abi.encode(account, chainId, keyId, height, root, version)),
            anchorBlock,
            approvals
        );
        Identity storage id = _identity[account];
        if (!id.registered) revert UnknownAccount(account);
        if (id.revoked) revert CertificateIsRevoked(id.certHash);
        // A zero chain id is a tooling mistake, not an attack: the slot it
        // would occupy is self-consistent and no authority consults it. The
        // publisher refuses it; EIP-170 pressure keeps the check off-chain.
        if (height == 0 || height > 24) revert LmsHeightOutOfRange(height);
        if (root == bytes32(0)) revert LmsRootIsZero();

        // Version lineage is PER account and chain: the same operator is a different signer on every chain,
        // so one chain starting at version 1 says nothing about another already being at version 3.
        LmsKey storage existing = _lmsKey[account][chainId];
        // An empty slot holds version 0, so this alone also refuses a version-0
        // registration — versions start at 1.
        if (version <= existing.version) {
            revert VersionNotNewer(existing.version, version);
        }

        bytes32 signerId = lmsSignerId(keyId, height, root);
        address boundTo = _lmsBinding[signerId].account;
        if (boundTo != address(0) && boundTo != account) {
            revert LmsKeyAlreadyBound(signerId, boundTo);
        }

        // The fingerprint being superseded, captured before the slot moves —
        // `existing` is a storage pointer and reads the NEW key afterwards.
        bytes32 superseded = existing.registered
            ? lmsSignerId(existing.keyId, existing.height, existing.root)
            : bytes32(0);

        // The superseded fingerprint is left bound to this account rather than
        // cleared. It is history: a signature made under the old key was made
        // by this operator, and a lookup that stopped resolving would make that
        // unprovable after the fact.
        _lmsKey[account][chainId] = LmsKey(keyId, height, root, version, true);
        _lmsBinding[signerId] = LmsBinding(account, chainId);
        emit LmsKeyRegistered(account, signerId, chainId, keyId, height, root, version);

        // Supersession is a PERMANENT transition — the old fingerprint stops
        // being this slot's current key and nothing re-registers it (a
        // re-registration of the same material is the same fingerprint, which
        // the guard below leaves alone). Recorded same-tx so the execution
        // chains' suspension lane never depends on someone noticing.
        if (superseded != bytes32(0) && superseded != signerId) {
            _recordRevokedSigner(superseded);
        }
        _projectIdentity(account);
    }

    /// @notice The LMS key an account holds for one chain, if any.
    /// @dev Keyed per account AND per chain, because a single-use hash-based counter is only complete while
    ///      the key it names signs for one chain. `registered` is the field to branch on; the zero struct
    ///      means no key rather than a key of zeroes.
    /// @param account The identity to read.
    /// @param chainId The chain the key is armed for.
    /// @return The stored key, copied to memory.
    function lmsKeyOf(address account, uint64 chainId) external view returns (LmsKey memory) {
        return _lmsKey[account][chainId];
    }

    /// @notice What a fingerprint is bound to: the account that registered it and the chain it signs for.
    /// @dev The binding survives supersession, because attribution is history: a signature made under a
    ///      retired key was still made by that operator, and a lookup that stopped resolving would make that
    ///      unprovable after the fact. Standing is a separate question, answered by {lmsSignerIsLive}.
    ///
    ///      The revocation log's permanence gate reads this to find the slot a fingerprint belongs to; that
    ///      slot's current key is what separates a superseded fingerprint, which is permanent and
    ///      recordable, from a merely lapsed one, which renewal undoes.
    /// @param signerId The fingerprint to resolve.
    /// @return account The account that registered it, or zero for a fingerprint never registered.
    /// @return chainId The chain that registration was for, or zero alongside a zero account.
    function lmsBindingOf(bytes32 signerId) external view returns (address account, uint64 chainId) {
        LmsBinding storage binding = _lmsBinding[signerId];
        return (binding.account, binding.chainId);
    }

    /**
     * @notice Whether a signer fingerprint is held by a standing, unrevoked account.
     * @dev The question a verifier actually has. An execution chain's authority roster names fingerprints
     *      and learns nothing else about them, so without this the keys behind those names are
     *      unanswerable from the state plane.
     *
     *      Standing is asked through {isActive} rather than by spelling the conditions out again, because a
     *      second spelling is how two answers drift: an expired identity already holds no role, and a signer
     *      lookup that disagreed would leave a roster satisfiable by an operator the rest of the registry
     *      has stopped honouring.
     *
     *      Live means the CURRENT key of the fingerprint's own account-and-chain slot, not merely one this
     *      account ever held. A superseded fingerprint stays attributable but stops being live, and a
     *      rotation on one chain says nothing about the same operator's key on another.
     * @param signerId The fingerprint an authority roster names.
     * @return live Whether the fingerprint is that slot's current key and the account still stands.
     * @return account The account the fingerprint is bound to, or zero when none ever registered it.
     */
    function lmsSignerIsLive(bytes32 signerId) external view returns (bool live, address account) {
        LmsBinding storage binding = _lmsBinding[signerId];
        account = binding.account;
        if (account == address(0)) return (false, address(0));
        // `isActive`, not a registered/revoked pair spelled out here. The
        // certificate validity window is part of standing: an expired identity
        // already holds no role, and a signer lookup that disagreed would leave
        // a roster satisfiable by an operator the rest of the registry has
        // stopped honouring. Spelling the condition out a second time is how
        // the two drift apart.
        if (!isActive(account)) return (false, account);
        // The CURRENT key of the fingerprint's own (account, chain) slot, not
        // merely one this account ever held: a superseded fingerprint stays
        // attributable but stops being live, and a rotation on one chain says
        // nothing about the same operator's key on another.
        LmsKey storage k = _lmsKey[account][binding.chainId];
        live = k.registered && lmsSignerId(k.keyId, k.height, k.root) == signerId;
    }

    /// @notice Close the bootstrap window. Irreversible.
    /// @dev Refuses while the registrar quorum is unset or unreachable, because sealing then would leave a
    ///      registry nobody can ever write to again — including to fix the threshold that locked it. The
    ///      count is of registrars that can SEAL: a certificate authority carrying the registrar role is
    ///      registered from a certificate with no seal slot and can never contribute an approval, so
    ///      counting role bits alone would seal onto a quorum that looks reachable and is not.
    ///
    ///      Clears the admin as well as setting the flag, so no single-caller path survives the seal.
    function sealBootstrap() external {
        if (msg.sender != bootstrapAdmin) revert NotAuthorized(msg.sender);
        if (bootstrapSealed) revert BootstrapAlreadySealed();
        if (registrarThreshold == 0) revert RegistrarThresholdIsZero();
        uint256 sealable = sealableMemberCount(ROLE_REGISTRAR);
        if (sealable < registrarThreshold) {
            revert RegistrarThresholdUnreachable(sealable, registrarThreshold);
        }
        bootstrapSealed = true;
        bootstrapAdmin = address(0);
        emit BootstrapSealed(msg.sender);
    }

    // ------------------------------------------------- state-plane wiring

    /**
     * @notice Wire the state trees and the revocation log, once, inside the bootstrap window.
     * @dev One-shot because both pointers are TRUST TOPOLOGY: the trees pointer decides where the
     *      wallet-creation admission set is written, and the log pointer decides where permanent standing
     *      losses are recorded. A re-wireable pointer would be a key over both.
     *
     *      It cannot be a constructor argument, because both of those contracts take THIS registry as one of
     *      theirs. The deploy tooling calls it in the same nonce-fixed block that deploys them, before any
     *      identity is registered, which is why the projection is silently skipped while the pointers are
     *      zero rather than reverting.
     * @param stateTrees_ The state-trees contract that owns tree 8. Zero is refused.
     * @param revocationLog_ The append-only log of retired signer fingerprints. Zero is refused.
     */
    function wireStatePlane(address stateTrees_, address revocationLog_) external {
        if (bootstrapSealed || msg.sender != bootstrapAdmin) revert NotAuthorized(msg.sender);
        if (stateTrees != address(0) || revocationLog != address(0)) revert StatePlaneAlreadyWired();
        if (stateTrees_ == address(0) || revocationLog_ == address(0)) revert ZeroStatePlane();
        stateTrees = stateTrees_;
        revocationLog = revocationLog_;
        emit StatePlaneWired(stateTrees_, revocationLog_);
    }

    /// @notice Refresh `account`'s tree-8 leaf in the state trees, same transaction.
    /// @dev Skipped while the plane is unwired, which is a bootstrap-window state the deploy tooling closes
    ///      before the first registration, and never otherwise. The leaf VALUE is derived by the trees
    ///      contract from this registry's post-mutation state, so there is nothing here to get wrong beyond
    ///      forgetting to call it — which is why every mutation calls it, including the one that cannot
    ///      change the leaf.
    /// @param account The identity whose leaf is stale.
    function _projectIdentity(address account) private {
        address trees = stateTrees;
        if (trees == address(0)) return;
        address[] memory one = new address[](1);
        one[0] = account;
        IIdentityLeafSink(trees).syncIdentityLeaves(one);
    }

    /// @notice Record a permanently retired signer fingerprint into the revocation log, same transaction.
    /// @dev Skipped while the log is unwired, and skipped when somebody already recorded the fingerprint
    ///      through the log's permissionless door — the log refuses a duplicate, and a membership mutation
    ///      must not be revertible by a stranger who front-ran its bookkeeping.
    /// @param signerId The fingerprint that has lost standing for good.
    function _recordRevokedSigner(bytes32 signerId) private {
        address log = revocationLog;
        if (log == address(0)) return;
        if (IRevocationRecorder(log).recorded(signerId)) return;
        IRevocationRecorder(log).record(signerId);
    }

    // -------------------------------------------------------- registration

    /**
     * @title Admission Proof
     * @notice The holder's proof of possession at admission: both live-stage families over the admission
     *         digest.
     * @dev There is no root keypair and no issuer signature on this path. The chain admits, and the two
     *      signatures presented at creation are the HOLDER's, verified by the precompiles inside the same
     *      transaction that writes the record. Possession lives in the TRANSACTION, never in the artifact:
     *      a public certificate is a document anyone may hold, so presenting one proves nothing.
     */
    struct AdmissionProof {
        /// The holder's ML-DSA-87 signature under the live TRANSACTION key, over the admission digest.
        bytes mlDsaSignature;
        /// The holder's SLH-DSA-SHAKE-256s signature under the live ACCESS key, over the same digest. Two
        /// families over one message, so neither a lattice break nor a hash-function break alone admits an
        /// identity.
        bytes slhDsaSignature;
    }

    /**
     * @notice Register or rotate a Final Wallet identity from its two public certificates.
     * @dev **Both stages, together.** A wallet has four keys in two stages and the recovery pair is
     *      PRE-COMMITTED — written at wallet initialization from the same certificate set that determined
     *      the wallet's address, which is why enabling post-quantum mode later takes no key arguments. The
     *      two certificates must share a serial: a serial is per certificate SET, so two stages that
     *      disagree about it are two different wallets.
     *
     *      **Chain-attested means pinned, per stage:** the chain's issuer name and authority key, depth
     *      exactly 1 so the certificate hangs directly under the chain, and `maxDelegationDepth == depth` so
     *      the holder issues nothing. That immutable pair is what {identityTreeLeafOf} discriminates record
     *      kinds by.
     *
     *      Issuance authority is the registrar quorum and possession is the holder's own proof; there is no
     *      root keypair anywhere and no certificate-authority signature over this admission.
     * @param account The wallet address the certificate set derives.
     * @param liveTbs The live certificate's TBS bytes: the live transaction and access keys.
     * @param recoveryTbs The recovery certificate's TBS bytes: the pre-committed recovery pair.
     * @param proof The holder's two signatures over the admission digest — the live transaction key
     *        (ML-DSA-87) and the live access key (SLH-DSA-SHAKE-256s), both verified in the precompiles
     *        inside this transaction.
     * @param roles Capability bitmask. The one thing the certificates do not say, because capability is this
     *        system's decision rather than the certificate's.
     * @param version Monotonic. A rotation that does not advance it is refused.
     * @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
     * @param approvals The sealed registrar quorum. Empty while bootstrap is open. The digest binds the
     *        account, both certificates' bytes, the roles and the version.
     * @return certHash The handle the live certificate is now known by.
     */
    function registerWallet(
        address account,
        bytes calldata liveTbs,
        bytes calldata recoveryTbs,
        AdmissionProof calldata proof,
        uint256 roles,
        uint64 version,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external returns (bytes32 certHash) {
        // Read BEFORE the authority check: the quorum path burns this counter
        // inside `_requireRegistrarQuorum`, and the proof must bind the value
        // the round was built over. The bootstrap path burns it explicitly in
        // `_requireAdmissionProof`, so an admission is one-shot in both regimes.
        uint64 admissionNonce = _gateNonce[address(this)];
        _requireMembershipAuthority(
            DOMAIN_REGISTER_WALLET,
            keccak256(
                abi.encode(account, keccak256(liveTbs), keccak256(recoveryTbs), roles, version)
            ),
            anchorBlock,
            approvals
        );

        FinalCertificate.Parsed memory l = FinalCertificate.parseLive(liveTbs);
        FinalCertificate.Parsed memory r = FinalCertificate.parseRecovery(recoveryTbs);
        if (l.serial != r.serial) revert StagesDisagree(l.serial, r.serial);

        _requireChainAttestedEndEntity(l);
        _requireChainAttestedEndEntity(r);
        _requireAdmissionProof(account, l, r.certHash, proof, admissionNonce);

        certHash = l.certHash;
        _write(account, l, r, roles, version, false);
    }

    /**
     * @notice Register or rotate an ISSUER: a third party, or one of this system's own intermediates, that
     *         signs certificates off chain with the keys registered here.
     * @dev Admission is chain-native like any identity — the registrar quorum authorises, and the holder's
     *      own proof of possession establishes that the party controls the keys it is claiming. The
     *      delegation rules survive as LINEAGE: a nested issuer's depth, delegation bound and
     *      `AuthorityKeyId` must chain to its registered parent. No parent signs anything; this chain's
     *      admission IS the issuance.
     *
     *      A registered issuer always expires, and its window is bounded by {MAX_ISSUER_VALIDITY_MS}.
     *
     *      An institution must carry its real ISO 3166 country in its subject name, matching the
     *      `jurisdiction` field of its institution extension. That is enforced at the door because a
     *      verifier's legal recourse starts with knowing where an issuer answers for itself.
     *
     *      `ROLE_CERTIFICATE_AUTHORITY` is added to whatever `roles` asks for, rather than being required in
     *      it: the capability is what this entry point means, so it cannot be forgotten in an argument.
     * @param account The issuer's account on this chain.
     * @param tbs The issuer certificate's TBS bytes: two cert-signing keys, ML-DSA-87 and
     *        SLH-DSA-SHAKE-256s, and no recovery stage — renewing an issuer is re-issuing, a governance act
     *        rather than a key rotation.
     * @param parent The registered parent issuer for a nested intermediate; zero for an issuer hanging
     *        directly under the chain.
     * @param proof The issuer's own two cert-signing keys over the admission digest. The recovery-handle
     *        slot in that digest is zero, because there is no recovery stage to bind.
     * @param roles Capability bitmask, over and above the certificate-authority bit this call adds.
     * @param version Monotonic. A rotation that does not advance it is refused.
     * @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
     * @param approvals The sealed registrar quorum. Empty while bootstrap is open. The digest binds the
     *        account, the certificate bytes, the parent, the roles and the version.
     * @return certHash The handle the registered certificate is now known by.
     */
    function registerIssuer(
        address account,
        bytes calldata tbs,
        address parent,
        AdmissionProof calldata proof,
        uint256 roles,
        uint64 version,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external returns (bytes32 certHash) {
        uint64 admissionNonce = _gateNonce[address(this)];
        _requireMembershipAuthority(
            DOMAIN_REGISTER_ISSUER,
            keccak256(abi.encode(account, keccak256(tbs), parent, roles, version)),
            anchorBlock,
            approvals
        );

        FinalCertificate.Parsed memory c = FinalCertificate.parseCa(tbs);
        // An issuer that cannot sign is an end entity wearing a profile —
        // and an end entity belongs in `registerWallet`.
        if (c.depth == 0 || c.maxDelegationDepth <= c.depth) {
            revert IssuerCannotSign(c.depth, c.maxDelegationDepth);
        }
        if (c.notAfter == 0) revert IssuerMustExpire();
        if (c.notAfter - c.notBefore > MAX_ISSUER_VALIDITY_MS) {
            revert IssuerValidityTooLong(c.notBefore, c.notAfter);
        }
        if (c.issuerDnHash != CHAIN_ISSUER_DN_HASH) revert WrongIssuerDn(c.issuerDnHash);
        _requireLineage(parent, c);
        _requireJurisdiction(c);
        _requireAdmissionProof(account, c, bytes32(0), proof, admissionNonce);

        certHash = c.certHash;
        _write(account, c, c, roles | ROLE_CERTIFICATE_AUTHORITY, version, true);
    }

    /// @notice The validity ceiling a registered issuer's certificate may not exceed, in this chain's
    ///         milliseconds: two 366-day years.
    /// @dev Expiry is the passive half of an issuer's lifecycle — the touchpoint that proves an issuer is
    ///      still there without anyone having to act — so a registered issuer always carries a real
    ///      `NotAfter` and a bounded window. Renewal re-issues under the same registered keys with a version
    ///      bump rather than extending a certificate in place.
    uint64 public constant MAX_ISSUER_VALIDITY_MS = 2 * 366 days * 1000;

    /// @notice Pin one stage of a chain-attested end-entity certificate.
    /// @dev Three checks, run once per stage: the certificate names the chain's authority key, it carries the
    ///      chain's issuer name, and its depth pair is exactly that of an end entity — depth 1, directly
    ///      under the chain, issuing nothing. The depth pair is immutable per version, which is why
    ///      {identityTreeLeafOf} discriminates record kinds by it rather than by a role bit.
    /// @param c The parsed certificate stage.
    function _requireChainAttestedEndEntity(FinalCertificate.Parsed memory c) private pure {
        if (c.authorityKeyId != CHAIN_AUTHORITY_KEY_ID) revert NotChainAttested(c.authorityKeyId);
        if (c.issuerDnHash != CHAIN_ISSUER_DN_HASH) revert WrongIssuerDn(c.issuerDnHash);
        if (c.depth != 1 || c.maxDelegationDepth != c.depth) {
            revert NotAnEndEntity(c.depth, c.maxDelegationDepth);
        }
    }

    /// @notice Check a nested issuer's lineage to its registered parent.
    /// @dev Delegation is governed by DEPTH, not by a boolean: a parent may sign only while
    ///      `depth < maxDelegationDepth`, a child sits exactly one level down so it cannot skip levels to
    ///      escape that bound, and its own bound may never widen past its parent's. The child's
    ///      `AuthorityKeyId` must equal the parent's `SubjectKeyId`, which is the link the chain follows.
    ///
    ///      A zero `parent` means the issuer hangs directly under the chain: it must then name the chain's
    ///      own authority key and sit at depth 1. No parent SIGNS anything here — admission by this chain is
    ///      the issuance, and lineage is what keeps the delegation bounds honest across it.
    /// @param parent The registered parent issuer, or zero for one directly under the chain.
    /// @param c The parsed issuer certificate.
    function _requireLineage(address parent, FinalCertificate.Parsed memory c) private view {
        if (parent == address(0)) {
            if (c.authorityKeyId != CHAIN_AUTHORITY_KEY_ID) {
                revert NotChainAttested(c.authorityKeyId);
            }
            if (c.depth != 1) revert WrongDepth(c.depth, 1);
            return;
        }
        Identity storage ca = _identity[parent];
        if (!hasRole(parent, ROLE_CERTIFICATE_AUTHORITY)) {
            revert IssuerNotACertificateAuthority(parent);
        }
        // Delegation is governed by depth, not by a boolean. `Depth <
        // MaxDelegationDepth` permits signing, and a child sits exactly one
        // level down — an issuer cannot skip levels to escape its own bound.
        if (ca.depth >= ca.maxDelegationDepth) {
            revert IssuerMayNotSign(parent, ca.depth, ca.maxDelegationDepth);
        }
        if (c.depth != ca.depth + 1) revert WrongDepth(c.depth, ca.depth + 1);
        if (c.maxDelegationDepth > ca.maxDelegationDepth) {
            revert DelegationWidened(c.maxDelegationDepth, ca.maxDelegationDepth);
        }
        if (c.authorityKeyId != ca.subjectKeyId) {
            revert AuthorityKeyIdMismatch(c.authorityKeyId, ca.subjectKeyId);
        }
    }

    /// @notice Refuse an issuer whose subject name carries no jurisdiction, or one that disagrees with its
    ///         institution extension.
    /// @dev An issuer that answers for itself somewhere is an issuer a verifier has recourse against, so a
    ///      registered institution must name its jurisdiction and must name it once. Only the trust root is
    ///      jurisdiction-silent, because the root is the worldwide network rather than a legal entity.
    ///
    ///      The rule is a real ISO 3166 alpha-2 `C=` component in the subject name, equal to the
    ///      `jurisdiction` field of the certificate's institution extension. The name is in canonical
    ///      comma-separated form, so `C=` matches at the start or immediately after a comma, and the
    ///      component value is exactly two bytes — a longer one is a different component that happens to
    ///      start with the same letter.
    /// @param c The parsed issuer certificate.
    function _requireJurisdiction(FinalCertificate.Parsed memory c) private pure {
        bytes memory dn = c.subjectDn;
        bytes2 country;
        bool found = false;
        for (uint256 i = 0; i + 4 <= dn.length; i++) {
            if ((i == 0 || dn[i - 1] == ",") && dn[i] == "C" && dn[i + 1] == "=") {
                // Exactly two bytes, then end-of-DN or the next component.
                if (i + 4 < dn.length && dn[i + 4] != ",") revert JurisdictionMissing();
                country = bytes2(bytes.concat(dn[i + 2], dn[i + 3]));
                found = true;
                break;
            }
        }
        if (!found) revert JurisdictionMissing();

        // Institution extension: legalNameLength ‖ legalName ‖
        // registrationNoLength ‖ registrationNo ‖ jurisdictionLength ‖
        // jurisdiction. The jurisdiction must EQUAL the DN's country.
        bytes memory ext = c.institutionExt;
        if (ext.length < 6) revert JurisdictionMissing();
        uint256 q = 2 + (uint256(uint8(ext[0])) << 8 | uint256(uint8(ext[1])));
        if (ext.length < q + 2) revert JurisdictionMissing();
        q += 2 + (uint256(uint8(ext[q])) << 8 | uint256(uint8(ext[q + 1])));
        if (ext.length < q + 2) revert JurisdictionMissing();
        uint256 jLen = uint256(uint8(ext[q])) << 8 | uint256(uint8(ext[q + 1]));
        q += 2;
        if (jLen != 2 || ext.length < q + 2) revert JurisdictionMismatch();
        if (bytes2(bytes.concat(ext[q], ext[q + 1])) != country) revert JurisdictionMismatch();
    }

    /// @notice Verify the holder's proof of possession over the admission digest.
    /// @dev Both live-stage families, in the precompiles, inside this transaction: an ML-DSA-87 signature
    ///      under the certificate's transaction key and an SLH-DSA-SHAKE-256s signature under its access
    ///      key. Possession lives in the TRANSACTION rather than in the artifact, so holding a copy of
    ///      somebody's public certificate proves nothing.
    ///
    ///      The keys come out of the certificate being admitted, not out of calldata, which is what makes
    ///      this a proof rather than a self-signed assertion.
    ///
    ///      Burns the gate nonce on the bootstrap path — the quorum path burned it already — so an admission
    ///      is one-shot in both regimes and a captured proof cannot be replayed into a second registration.
    /// @param account The account being admitted; named in the revert so a failure is attributable.
    /// @param live The parsed live-stage certificate whose keys verify the proof.
    /// @param recoveryCertHash The recovery certificate's handle, bound into the digest; zero for an issuer.
    /// @param proof The holder's two signatures.
    /// @param admissionNonce The gate-nonce value the digest was built over.
    function _requireAdmissionProof(
        address account,
        FinalCertificate.Parsed memory live,
        bytes32 recoveryCertHash,
        AdmissionProof calldata proof,
        uint64 admissionNonce
    ) private {
        bytes memory message = abi.encodePacked(
            keccak256(
                abi.encode(
                    DOMAIN_IDENTITY_ADMISSION,
                    block.chainid,
                    address(this),
                    live.certHash,
                    recoveryCertHash,
                    admissionNonce
                )
            )
        );
        if (
            !FinalChainPrecompiles.verifyMlDsa87(live.transactionKey, message, proof.mlDsaSignature)
                || !FinalChainPrecompiles.verifySlhDsa(live.accessKey, message, proof.slhDsaSignature)
        ) revert AdmissionProofInvalid(account);
        if (_gateNonce[address(this)] == admissionNonce) {
            _gateNonce[address(this)] = admissionNonce + 1;
        }
    }

    /**
     * @notice Commit one parsed certificate set to storage and project the result.
     * @dev The single write path behind both registration entry points, so a wallet record and an issuer
     *      record cannot diverge in how they are stored. Every authorization, parse and pin has already run;
     *      what is left is the ordering that keeps the record consistent with its indexes.
     *
     *      A rotation RELEASES the previous certificate's binding rather than revoking it: a superseded
     *      certificate and a compromised one are different facts, and revocation is the louder of the two.
     *      The sender binding moves with the transaction key for the same reason — a rotation is the account
     *      disowning that key, and a gate that still resolved the old sender would honour a retired key.
     *
     *      A certificate already bound to another account is refused, and so is a version that does not
     *      advance, so neither a replayed registration nor a stolen certificate can take a record over.
     * @param account The identity being written. Zero is refused.
     * @param live The parsed live-stage certificate; for an issuer, its single certificate.
     * @param recovery The parsed recovery-stage certificate; for an issuer, the same value, discarded.
     * @param roles The complete capability bitmask to store.
     * @param version Monotonic per account. Must exceed the stored value.
     * @param isCa Whether this is a certificate authority, which stores no recovery, seal or
     *        encapsulation material.
     */
    function _write(
        address account,
        FinalCertificate.Parsed memory live,
        FinalCertificate.Parsed memory recovery,
        uint256 roles,
        uint64 version,
        bool isCa
    ) private {
        if (account == address(0)) revert UnknownAccount(account);
        if (certificateRevoked[live.certHash]) revert CertificateIsRevoked(live.certHash);

        address boundTo = accountOfCertificate[live.certHash];
        if (boundTo != address(0) && boundTo != account) {
            revert CertificateAlreadyBound(live.certHash, boundTo);
        }

        Identity storage id = _identity[account];
        if (!id.registered) {
            _accounts.push(account);
            id.registered = true;
        } else {
            if (version <= id.version) revert VersionNotNewer(id.version, version);
            if (id.revoked) revert CertificateIsRevoked(id.certHash);
            // A rotation releases the previous certificate's binding. It is NOT
            // revoked — a superseded certificate and a compromised one are
            // different facts and revocation is the louder of the two.
            if (id.certHash != live.certHash) delete accountOfCertificate[id.certHash];
        }

        id.certHash = live.certHash;
        id.recoveryCertHash = recovery.certHash;
        id.serial = live.serial;
        id.subjectKeyId = live.subjectKeyId;
        id.roles = roles;
        id.depth = live.depth;
        id.maxDelegationDepth = live.maxDelegationDepth;
        id.notBefore = live.notBefore;
        id.notAfter = live.notAfter;
        id.version = version;

        // The sender binding moves with the transaction key. The old sender is
        // released rather than kept: a rotation is the account disowning that
        // key, and a gate that still resolved it would honour a retired key.
        address sender = senderFor(live.transactionKey);
        address senderBoundTo = accountOfSender[sender];
        if (senderBoundTo != address(0) && senderBoundTo != account) {
            revert SenderAlreadyBound(sender, senderBoundTo);
        }
        if (_activeTransactionKey[account].length != 0) {
            address previousSender = senderFor(_activeTransactionKey[account]);
            if (previousSender != sender) delete accountOfSender[previousSender];
        }
        accountOfSender[sender] = account;

        _activeTransactionKey[account] = live.transactionKey;
        _activeAccessKey[account] = live.accessKey;
        // A CA has no recovery pair; the two active slots are all it has.
        _recoveryTransactionKey[account] = isCa ? bytes("") : recovery.transactionKey;
        _recoveryAccessKey[account] = isCa ? bytes("") : recovery.accessKey;
        // Cleared on a rotation to a certificate without one, for the same
        // reason the encapsulation pair is: a stale seal surviving a rotation
        // would let a retired key keep co-signing execution.
        _activeSealKey[account] = isCa ? bytes("") : live.sealKey;

        // The encapsulation pair, validated before it is stored.
        //
        // **The registry is where a sender looks up "encapsulate to this
        // party", so a malformed key here is not a bad record — it is an
        // account nobody can seal an intent to.** The discovery would happen at
        // the first attempt, and on the hybrid path it would happen as a pair
        // silently reduced to one family, which is identical on the wire. The
        // precompiles make it a refusal at registration instead.
        //
        // Neither is a re-implementation of the KEM: `0x0203` runs FIPS 203
        // §7.2's own encapsulation-key check and `0x0207` runs the structural
        // check HQC-5's encoding admits. Encapsulation is a sender operation
        // and decapsulation needs the secret key, so nothing more belongs here.
        //
        // A CA is sealed to by nobody and carries no encapsulation stage, so
        // its slots are cleared rather than checked.
        _storeKemPair(account, isCa, live.kemMlKem, live.kemHqc, true);
        _storeKemPair(account, isCa, recovery.kemMlKem, recovery.kemHqc, false);

        accountOfCertificate[live.certHash] = account;

        emit IdentityRegistered(account, live.certHash, roles, version);
        // Same-tx: a registration or rotation is visible to every execution
        // chain's admission set the moment it is visible here.
        _projectIdentity(account);
    }

    /**
     * @notice Store one stage's encapsulation pair, or clear it.
     * @dev Empty is legitimate and is not the same as absent-and-wrong: a certificate authority has no
     *      encapsulation stage, and a certificate may be issued without one. The parser has already refused
     *      the half-populated case, so by here the pair is both or neither.
     *
     *      Cleared rather than left alone on a rotation to an empty pair. A stale key surviving a rotation is
     *      a sender encapsulating to a credential the account has disowned, and the message then never
     *      decrypts — the failure mode with no error attached, and the one this pairing exists to avoid.
     * @param account The identity being written.
     * @param isCa Whether the record is a certificate authority, which carries no encapsulation stage.
     * @param mlKem The stage's ML-KEM-1024 key, or empty.
     * @param hqc The stage's HQC-5 key, or empty.
     * @param isLive Whether this is the live stage; false selects the recovery slots.
     */
    function _storeKemPair(address account, bool isCa, bytes memory mlKem, bytes memory hqc, bool isLive)
        private
    {
        if (isCa || mlKem.length == 0) {
            delete (isLive ? _activeKemMlKem : _recoveryKemMlKem)[account];
            delete (isLive ? _activeKemHqc : _recoveryKemHqc)[account];
            return;
        }
        if (!FinalChainPrecompiles.isWellFormedMlKem1024(mlKem)) {
            revert MalformedEncapsulationKey(account, FinalCertificate.ALG_ML_KEM_1024);
        }
        if (!FinalChainPrecompiles.isWellFormedHqc5(hqc)) {
            revert MalformedEncapsulationKey(account, FinalCertificate.ALG_HQC_5);
        }
        if (isLive) {
            _activeKemMlKem[account] = mlKem;
            _activeKemHqc[account] = hqc;
        } else {
            _recoveryKemMlKem[account] = mlKem;
            _recoveryKemHqc[account] = hqc;
        }
    }

    /// @notice Grant or withdraw capabilities without rotating keys.
    /// @dev Separate from registration because the two have different cadences: a role changes when a
    ///      service's job changes, a key changes when it is compromised or aged out. Folding them together
    ///      would force a key rotation to express a role change, which is the more dangerous of the two
    ///      operations doing the work of the safer one.
    /// @param account Must already be registered and not revoked.
    /// @param roles The complete new capability bitmask; it replaces the old one rather than merging.
    /// @param anchorBlock The block the registrars read the roster at.
    /// @param approvals The sealed registrar quorum. Empty while bootstrap is open.
    function setRoles(
        address account,
        uint256 roles,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireMembershipAuthority(
            DOMAIN_SET_ROLES, keccak256(abi.encode(account, roles)), anchorBlock, approvals
        );
        Identity storage id = _identity[account];
        if (!id.registered) revert UnknownAccount(account);
        if (id.revoked) revert CertificateIsRevoked(id.certHash);
        uint256 previous = id.roles;
        id.roles = roles;
        _requireRegistrarQuorumReachable();
        emit IdentityRolesChanged(account, previous, roles);
        // Roles are not in the tree-8 leaf, so this rewrites the same value —
        // kept anyway so "every identity mutation projects" has no exceptions
        // to remember.
        _projectIdentity(account);
    }

    /// @notice Refuse a mutation that would leave the registrar quorum unreachable.
    /// @dev Once bootstrap is sealed, that is the one change nothing could ever undo: a registry whose
    ///      threshold exceeds its sealable membership can never be written to again, including to fix
    ///      itself. Checked AFTER the write so the count reflects the mutation being attempted.
    function _requireRegistrarQuorumReachable() private view {
        if (!bootstrapSealed) return;
        uint256 sealable = sealableMemberCount(ROLE_REGISTRAR);
        if (sealable < registrarThreshold) {
            revert RegistrarThresholdUnreachable(sealable, registrarThreshold);
        }
    }

    /// @notice Revoke an identity and its certificate. Irreversible.
    /// @dev Clears the roles as well as setting the flag. Both are checked everywhere, but leaving a revoked
    ///      record carrying roles invites a future reader that checks only one of them. The fingerprints of
    ///      the named LMS slots are recorded into the revocation log after the flag lands, so the log's own
    ///      permanence gate sees the transition it requires.
    /// @param account The identity to retire.
    /// @param chainIds The chains whose LMS-key slots this account holds. The registrars supply the list and
    ///        the approval digest binds it, because a mapping cannot enumerate its own keys. A chain with no
    ///        slot is skipped, and a fingerprint an incomplete list missed stays permanently recordable
    ///        through the revocation log's permissionless door, since a revoked account never regains
    ///        standing.
    /// @param anchorBlock The block the registrars read the roster at.
    /// @param approvals The sealed registrar quorum. Empty while bootstrap is open.
    function revoke(
        address account,
        uint64[] calldata chainIds,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireMembershipAuthority(
            DOMAIN_REVOKE, keccak256(abi.encode(account, chainIds)), anchorBlock, approvals
        );
        Identity storage id = _identity[account];
        if (!id.registered) revert UnknownAccount(account);
        id.revoked = true;
        id.roles = 0;
        certificateRevoked[id.certHash] = true;
        _requireRegistrarQuorumReachable();
        emit IdentityRevoked(account, id.certHash);
        // AFTER the flag lands, so the log's own gate sees the permanent
        // transition it requires.
        for (uint256 i = 0; i < chainIds.length; i++) {
            LmsKey storage k = _lmsKey[account][chainIds[i]];
            if (k.registered) _recordRevokedSigner(lmsSignerId(k.keyId, k.height, k.root));
        }
        _projectIdentity(account);
    }

    /**
     * @notice Root-plane GLOBAL certificate revocation, by `certHash`.
     * @dev The half of the revocation lane that gates registration and covers break-glass: any certificate —
     *      registered here, issued off chain, or never seen — can be killed by handle under the registrar
     *      quorum, because the handle is all a break-glass caller may have.
     *
     *      When the handle is a registered identity's CURRENT certificate the identity falls with it: flag,
     *      roles cleared, same-transaction projection. So revoking by handle is never weaker than {revoke};
     *      it only skips the LMS-slot enumeration, and those fingerprints stay permanently recordable
     *      through the revocation log's own permissionless door.
     * @param certHash The certificate to revoke. Need not correspond to any record.
     * @param anchorBlock The block the registrars read the roster at.
     * @param approvals The sealed registrar quorum. Empty while bootstrap is open.
     */
    function revokeCertificate(
        bytes32 certHash,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireMembershipAuthority(
            DOMAIN_REVOKE_CERTIFICATE, keccak256(abi.encode(certHash)), anchorBlock, approvals
        );
        certificateRevoked[certHash] = true;
        address bound = accountOfCertificate[certHash];
        if (bound != address(0)) {
            Identity storage id = _identity[bound];
            if (!id.revoked) {
                id.revoked = true;
                id.roles = 0;
                _requireRegistrarQuorumReachable();
                emit IdentityRevoked(bound, certHash);
                _projectIdentity(bound);
            }
        }
        emit CertificateRevoked(certHash, address(0));
    }

    /**
     * @notice The issuing identity's half of the revocation lane: a registered issuer revokes a certificate
     *         it signed off chain, by `certHash`.
     * @dev This records WHO revoked, and a verifier honours the entry only when the recorded revoker is the
     *      certificate's own issuer — which the verifier knows, because it holds the certificate. It
     *      deliberately does NOT set the global `certificateRevoked` flag: that flag gates registration, and
     *      letting any registered issuer set it for an arbitrary handle would be a griefing lane over other
     *      people's certificates.
     *
     *      Anyone may SUBMIT. Authority is the two signatures — the issuer's registered cert-signing keys
     *      over a digest binding this registry, this chain, the handle and the issuer's own gate nonce, both
     *      verified in the precompiles inside this transaction. The keys come from storage, so a submitter
     *      cannot supply the pair its own signatures verify under.
     *
     *      One-way: the first revoker of a handle is recorded and a second write is refused, because
     *      "revoked twice by two parties" is two facts where this lane models one.
     * @param issuer The registered certificate authority making the statement.
     * @param certHash The certificate being revoked.
     * @param proof The issuer's own ML-DSA-87 and SLH-DSA-SHAKE-256s signatures over the revocation digest.
     */
    function revokeIssuedCertificate(
        address issuer,
        bytes32 certHash,
        AdmissionProof calldata proof
    ) external {
        if (!hasRole(issuer, ROLE_CERTIFICATE_AUTHORITY)) {
            revert IssuerNotACertificateAuthority(issuer);
        }
        if (certificateRevokedBy[certHash] != address(0)) revert CertificateIsRevoked(certHash);
        uint64 nonce = _gateNonce[issuer];
        _gateNonce[issuer] = nonce + 1;
        bytes memory message = abi.encodePacked(
            keccak256(
                abi.encode(
                    DOMAIN_ISSUER_CERT_REVOCATION,
                    block.chainid,
                    address(this),
                    issuer,
                    certHash,
                    nonce
                )
            )
        );
        if (
            !FinalChainPrecompiles.verifyMlDsa87(
                _activeTransactionKey[issuer], message, proof.mlDsaSignature
            )
                || !FinalChainPrecompiles.verifySlhDsa(
                    _activeAccessKey[issuer], message, proof.slhDsaSignature
                )
        ) revert AdmissionProofInvalid(issuer);
        certificateRevokedBy[certHash] = issuer;
        emit CertificateRevoked(certHash, issuer);
    }

    // ---------------------------------------------------------------- views

    /// @notice The full identity record.
    /// @dev Returns the zero struct for an address no record claims, so `registered` is the field to branch
    ///      on rather than any of the hashes.
    /// @param account The identity to read.
    /// @return The stored record, copied to memory.
    function identityOf(address account) external view returns (Identity memory) {
        return _identity[account];
    }

    /// @notice The live transaction key, ML-DSA-87: what a quorum vote is verified against.
    /// @dev Read from STORAGE by every quorum on this chain, never from a caller's argument — a key supplied
    ///      as calldata proves nothing, because anyone holding a keypair can sign under it.
    /// @param account The identity to read.
    /// @return The raw public key, or empty when the account holds none.
    function activeTransactionKeyOf(address account) external view returns (bytes memory) {
        return _activeTransactionKey[account];
    }

    /// @notice The live access key, SLH-DSA-SHAKE-256s: identity, rotation, and guardianship.
    /// @dev A different hardness assumption from the transaction key, so a lattice break leaves the key that
    ///      governs identity standing intact.
    /// @param account The identity to read.
    /// @return The raw public key, or empty when the account holds none.
    function activeAccessKeyOf(address account) external view returns (bytes memory) {
        return _activeAccessKey[account];
    }

    /// @notice The seal key, SLH-DSA-SHAKE-256s: what `FinalPqQuorum` verifies an approval's seal against.
    /// @dev A service's second hash-based key, distinct from its access key, so a quorum decision carries
    ///      one signature from each hardness assumption. Empty when the identity carries no seal, in which
    ///      case it cannot take part in a sealed quorum at all — which is why {sealableMemberCount} counts
    ///      this rather than counting role bits.
    /// @param account The identity to read.
    /// @return The raw public key, or empty when the account holds no seal.
    function activeSealKeyOf(address account) external view returns (bytes memory) {
        return _activeSealKey[account];
    }

    /// @notice The recovery-stage transaction key, ML-DSA-87.
    /// @dev Authorizes rotating this account's own credentials and nothing else — acting as a guardian is an
    ///      ordinary action for an account and uses the live keys. Empty for a certificate authority.
    /// @param account The identity to read.
    /// @return The raw public key, or empty when the account holds none.
    function recoveryTransactionKeyOf(address account) external view returns (bytes memory) {
        return _recoveryTransactionKey[account];
    }

    /// @notice The recovery-stage access key, SLH-DSA-SHAKE-256s.
    /// @dev The other half of the pre-committed recovery stage. Empty for a certificate authority, which has
    ///      no recovery stage at all.
    /// @param account The identity to read.
    /// @return The raw public key, or empty when the account holds none.
    function recoveryAccessKeyOf(address account) external view returns (bytes memory) {
        return _recoveryAccessKey[account];
    }

    /// @notice The four signing-key commitments, in the order tree 1's leaf wants them.
    /// @dev keccak, not SHA3: these feed `FinalWalletFactory.accountStateLeafHash`, which every execution
    ///      chain verifies with, and that one hashes with keccak. An account missing a slot commits to the
    ///      hash of the empty string rather than reverting, so the leaf stays buildable for a certificate
    ///      authority, which holds no recovery pair.
    /// @param account The identity to commit to.
    /// @return liveAccess Commitment to the live access key.
    /// @return liveTransaction Commitment to the live transaction key.
    /// @return recoveryAccess Commitment to the recovery access key.
    /// @return recoveryTransaction Commitment to the recovery transaction key.
    function keyCommitments(address account)
        external
        view
        returns (
            bytes32 liveAccess,
            bytes32 liveTransaction,
            bytes32 recoveryAccess,
            bytes32 recoveryTransaction
        )
    {
        liveAccess = keccak256(_activeAccessKey[account]);
        liveTransaction = keccak256(_activeTransactionKey[account]);
        recoveryAccess = keccak256(_recoveryAccessKey[account]);
        recoveryTransaction = keccak256(_recoveryTransactionKey[account]);
    }

    /**
     * @notice The tree-8 leaf `account` currently earns: the execution chains' identity leaf while the
     *         identity stands, zero once it does not.
     * @dev The leaf VALUE is `keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)` — byte-identical to
     *      `IdentityRootModule.identityLeafHash`, which is also the `certHash` inside a wallet's address
     *      derivation — with `keysHash` folded exactly as the certificate issuer folds it:
     *      `keccak256(activeAccess ‖ activeTransaction ‖ recoveryAccess ‖ recoveryTransaction ‖ activeKem ‖
     *      recoveryKem)`, six commitment words packed in slot order. The issuing tooling and this function
     *      are pinned against each other by test over the premined certificate fixtures, because a wallet
     *      whose address was derived from a different fold is a wallet no chain can admit.
     *
     *      Zero — the empty slot's own value, unprovable as a leaf because no certificate hashes to it — for
     *      anything that must not admit a wallet creation: a revoked identity, one outside its validity
     *      window, and any certificate authority. The authority exclusion is STRUCTURAL rather than a role
     *      read: an end entity has `depth == maxDelegationDepth` because it issues nothing, an authority
     *      never does, and that pair is immutable per version where `roles` is not.
     *
     *      Lives here rather than on the state-trees contract that consumes it because every input is this
     *      contract's storage, and the trees contract has no bytecode headroom to spare.
     * @param account The identity to project. Reverts for an account with no record at all.
     * @return The tree-8 leaf value, or zero while the identity does not stand.
     */
    function identityTreeLeafOf(address account) external view returns (bytes32) {
        Identity storage id = _identity[account];
        if (!id.registered) revert UnknownAccount(account);
        if (id.revoked || !_withinValidity(id)) return bytes32(0);
        if (id.depth != id.maxDelegationDepth) {
            // An ISSUER exists in tree 8 under its own domain, so its record is stapleable for offline
            // licence verification while the distinct domain keeps it out of wallet admission. `certHash`
            // suffices — it covers the whole TBS and the verifier holds the certificate — `version` makes
            // supersession move the leaf, and the third word RESERVES the issuer's own certificate-tree
            // anchor, zero until one is wired. Zero-on-revoke above is load-bearing for both record kinds:
            // a fresh staple is an unrevoked statement.
            return keccak256(
                abi.encodePacked(DOMAIN_ISSUER_LEAF, id.certHash, uint64(id.version), bytes32(0))
            );
        }
        bytes32 liveKem = keccak256(
            abi.encodePacked(DOMAIN_KEM_BUNDLE, _activeKemMlKem[account], _activeKemHqc[account]));
        bytes32 recoveryKem = keccak256(
            abi.encodePacked(DOMAIN_KEM_BUNDLE, _recoveryKemMlKem[account], _recoveryKemHqc[account]));
        bytes32 keysHash = keccak256(
            abi.encodePacked(
                keccak256(_activeAccessKey[account]),
                keccak256(_activeTransactionKey[account]),
                keccak256(_recoveryAccessKey[account]),
                keccak256(_recoveryTransactionKey[account]),
                liveKem,
                recoveryKem
            )
        );
        return keccak256(abi.encodePacked(DOMAIN_IDENTITY_LEAF, id.serial, keysHash));
    }

    /// @notice Per-stage encapsulation commitments, in the order the account-state leaf wants them.
    /// @dev One word per STAGE, folded over both of that stage's encapsulation public keys under
    ///      `DOMAIN_KEM_BUNDLE`. The pair is the unit — an account holds both keys or neither — so
    ///      committing to them separately would model a state the protocol does not recognise, and every
    ///      downstream record would carry two words where one says the same thing.
    ///
    ///      An account whose certificate carries no encapsulation stage folds the empty string here rather
    ///      than reverting: the projection into the state trees must keep succeeding for it, and a leaf that
    ///      cannot be built is a party that cannot be revoked.
    /// @param account The identity to commit to.
    /// @return liveKem The live stage's encapsulation commitment.
    /// @return recoveryKem The recovery stage's encapsulation commitment.
    function kemCommitments(address account)
        external
        view
        returns (bytes32 liveKem, bytes32 recoveryKem)
    {
        liveKem = keccak256(
            abi.encodePacked(DOMAIN_KEM_BUNDLE, _activeKemMlKem[account], _activeKemHqc[account]));
        recoveryKem = keccak256(
            abi.encodePacked(DOMAIN_KEM_BUNDLE, _recoveryKemMlKem[account], _recoveryKemHqc[account]));
    }

    /// @notice The live-stage encapsulation keys themselves, for a party composing a sealed message.
    /// @dev Returns both halves of the pair together because the pair is the unit: encapsulating to one
    ///      family alone is indistinguishable on the wire from a hybrid, and silently dropping the hedge is
    ///      the failure this pairing exists to prevent. Empty for an account with no encapsulation stage.
    /// @param account The party to encapsulate to.
    /// @return activeMlKem The lattice half, ML-KEM-1024.
    /// @return activeHqc The code-based half, HQC-5.
    function kemKeysOf(address account)
        external
        view
        returns (bytes memory activeMlKem, bytes memory activeHqc)
    {
        return (_activeKemMlKem[account], _activeKemHqc[account]);
    }

    // ------------------------------------------------------------- senders

    /**
     * @notice The sender address a transaction key produces on this chain.
     * @dev `keccak256(uint8(4) ‖ publicKey)[12:]` — byte-identical to what the node derives from a
     *      post-quantum transaction envelope and to the backend's own derivation. The leading algorithm byte
     *      is what domain-separates it, so a key of another family can never derive the same address.
     *
     *      Pure, so a client can compute the address from a certificate before the identity is registered —
     *      which is what lets an admission transaction be funded and submitted from the very sender it is
     *      about to bind.
     * @param transactionKey The raw ML-DSA-87 public key.
     * @return The sender address that key signs from.
     */
    function senderFor(bytes memory transactionKey) public pure returns (address) {
        return address(uint160(uint256(keccak256(abi.encodePacked(ENVELOPE_ALG_ML_DSA_87, transactionKey)))));
    }

    /// @notice The sender `account`'s transactions arrive from.
    /// @dev The forward direction of {accountOfSender}, derived rather than stored, so it cannot disagree
    ///      with the transaction key on record.
    /// @param account The identity to resolve.
    /// @return The derived sender, or zero for an account with no transaction key on record.
    function senderOf(address account) external view returns (address) {
        bytes storage key = _activeTransactionKey[account];
        if (key.length == 0) return address(0);
        return senderFor(key);
    }

    /// @notice {hasRole} for a `msg.sender`: resolves the sender to its identity first.
    /// @dev The form every `msg.sender` gate on this chain uses. A sender is derived from a transaction key
    ///      and holds no authority itself, so asking it directly would be asking the wrong address. False for
    ///      a sender no identity claims.
    /// @param sender The address a transaction arrived from.
    /// @param roleMask The capability required.
    /// @return Whether the identity behind that sender stands and carries the whole mask.
    function senderHasRole(address sender, uint256 roleMask) external view returns (bool) {
        address account = accountOfSender[sender];
        return account != address(0) && hasRole(account, roleMask);
    }

    /// @notice How many accounts carrying `roleMask` also hold a seal key — the members that can take part
    ///         in a sealed quorum.
    /// @dev The count every membership threshold is checked against, because membership approvals are the
    ///      hybrid class and a member with no seal can never contribute one. A certificate authority
    ///      carrying `ROLE_REGISTRAR` is registered from a certificate with no seal slot, so it is counted
    ///      out here rather than being discovered at the first quorum that fails to reach its threshold.
    /// @param roleMask The capability the quorum is over.
    /// @return sealable How many standing accounts carry the mask and hold a seal key.
    function sealableMemberCount(uint256 roleMask) public view returns (uint256 sealable) {
        uint256 n = _accounts.length;
        for (uint256 i = 0; i < n; i++) {
            address a = _accounts[i];
            if (hasRole(a, roleMask) && _activeSealKey[a].length != 0) sealable++;
        }
    }

    /// @notice Number of registered accounts.
    /// @dev Never decreases: revocation clears a record's roles and sets its flag but leaves it in the list,
    ///      so an index handed out once keeps pointing at the same account for good.
    /// @return How many accounts have ever been registered.
    function accountCount() external view returns (uint256) {
        return _accounts.length;
    }

    /// @notice Registered account by index, in registration order.
    /// @dev Reverts on an out-of-range index rather than answering zero, so a caller paging the list cannot
    ///      mistake the end of it for a hole in the middle.
    /// @param index Position in the registration-ordered list, below {accountCount}.
    /// @return The account at that position.
    function accountAt(uint256 index) external view returns (address) {
        return _accounts[index];
    }

    /// @notice Every account carrying every bit in `roleMask`.
    /// @dev A view, so the linear scan over the account list costs nothing to a caller reading off chain.
    ///      Callers that need a roster inside a transaction pass the member list explicitly instead — see
    ///      `FinalPqQuorum`, which takes signers rather than searching for them, so a quorum's cost does not
    ///      grow with the size of the registry.
    /// @param roleMask The capability to filter on.
    /// @return found The matching accounts, in registration order.
    function accountsWithRole(uint256 roleMask) external view returns (address[] memory found) {
        uint256 n = _accounts.length;
        address[] memory buf = new address[](n);
        uint256 count;
        for (uint256 i = 0; i < n; i++) {
            if (hasRole(_accounts[i], roleMask)) {
                buf[count++] = _accounts[i];
            }
        }
        found = new address[](count);
        for (uint256 i = 0; i < count; i++) {
            found[i] = buf[i];
        }
    }

    /**
     * @notice How many accounts could satisfy a quorum for `roleMask` right now.
     * @dev The number a threshold has to be reachable against. A threshold above it is not a strict quorum,
     *      it is a quorum that cannot be met — and the way that presents is an operation reverting forever
     *      with nothing naming the roster as the cause. Counts standing alone; use {sealableMemberCount} for
     *      a quorum that also needs a seal.
     * @param roleMask The capability the quorum is over.
     * @return live How many standing accounts carry the whole mask.
     */
    function liveMemberCount(uint256 roleMask) public view returns (uint256 live) {
        uint256 n = _accounts.length;
        for (uint256 i = 0; i < n; i++) {
            if (hasRole(_accounts[i], roleMask)) live++;
        }
    }

    /**
     * @notice Whether `account` currently carries every bit in `roleMask`.
     * @dev Every gate in this system asks this one question, so every gate gets the same answer: registered,
     *      not revoked, inside its validity window, and holding the capability. A caller that checked only
     *      the role bit would accept an expired certificate.
     *
     *      `roleMask == 0` is false. A zero mask asks nothing and must not read as "yes" — that is the shape
     *      of an uninitialised configuration variable, and the one reading it must not be a universal pass.
     *
     *      Every bit in the mask must be present, so a mask naming two capabilities asks for both rather than
     *      either.
     * @param account The account to test.
     * @param roleMask One or more `ROLE_*` bits, OR-ed together.
     * @return Whether the account stands and carries the whole mask.
     */
    function hasRole(address account, uint256 roleMask) public view returns (bool) {
        if (roleMask == 0) return false;
        Identity storage id = _identity[account];
        if (!id.registered || id.revoked) return false;
        if (id.roles & roleMask != roleMask) return false;
        return _withinValidity(id);
    }

    /// @notice Whether `account` is registered, unrevoked and in date, regardless of capability.
    /// @dev The standing half of {hasRole}, for callers that care that a party is honoured at all rather
    ///      than that it holds a particular capability. {lmsSignerIsLive} asks this rather than spelling the
    ///      three conditions out a second time, because a second spelling is how two answers drift apart.
    /// @param account The account to test. An address no record claims answers false.
    /// @return Whether the identity currently stands.
    function isActive(address account) public view returns (bool) {
        Identity storage id = _identity[account];
        return id.registered && !id.revoked && _withinValidity(id);
    }

    /// @notice Whether a record's certificate is inside its validity window right now.
    /// @dev Both bounds are milliseconds on this chain's clock and both are optional: a zero `notBefore`
    ///      means valid from issuance and a zero `notAfter` means never expires, which the certificate
    ///      schema allows and personal identity certificates use. The upper bound is exclusive, so a
    ///      certificate stops being honoured on the millisecond it names rather than after it.
    /// @param id The record to test, taken as a storage pointer so no copy of a multi-word struct is made.
    /// @return Whether the window admits the current block time.
    function _withinValidity(Identity storage id) private view returns (bool) {
        if (id.notBefore != 0 && FinalChainTime.nowMs() < id.notBefore) return false;
        if (id.notAfter != 0 && FinalChainTime.nowMs() >= id.notAfter) return false;
        return true;
    }


    // ------------------------------------------------------------------ sweep

    /// @inheritdoc FinalSweep
    /// @dev The registry's own configuration gate, in the `msg.sender` form a no-argument seam can express:
    ///      the bootstrap admin alone while the window is open, a live registrar afterwards.
    ///
    ///      The rest of the state plane inherits this rule from `FinalPlaneSweep`, which reads it off a
    ///      registry pointer. This contract answers it from its own storage because it IS that registry, and
    ///      importing the shared mixin here would make this file import a file that imports it back.
    ///
    ///      The sealed half of the gate is a K-of-N over `ROLE_REGISTRAR` whose approvals arrive in calldata,
    ///      which `sweepAsset`'s shared signature has no room for; what survives is membership in that same
    ///      roster. The narrowing is safe because the other two gates hold regardless: a sweep moves surplus
    ///      only, this contract owes nothing, so there is nothing behind the line to reach — and the
    ///      destination is not the caller's to invent.
    function _requireSweepAuthority() internal view override {
        if (!bootstrapSealed && msg.sender == bootstrapAdmin) return;
        if (hasRole(msg.sender, ROLE_REGISTRAR)) return;
        revert SweepUnauthorized(msg.sender);
    }

    /// @inheritdoc FinalSweep
    /// @dev The bootstrap admin, and the proven authority that called. The first of those is zero once the
    ///      window is sealed, which `FinalSweep` refuses as a destination, so a sealed registry can only
    ///      sweep to the registrar that authorised the sweep.
    function _sweepDestinations() internal view override returns (address, address) {
        return (bootstrapAdmin, msg.sender);
    }

    /// @dev Nothing is reserved because nothing is owed: the registry holds
    /// certificates and role bits, has no payable entrypoint and no custody
    /// line. Anything it carries arrived by accident.
}

contracts/finalchain/FinalIntentLog.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy and operate this intent log as part of a
//    Final DeFi Protocol chain, and may record intent status in it under the
//    authority the chain recognises.
// 2. Integrators, relayers, and indexers may read the intent status it
//    mirrors and search it by its ring keys, as part of their integration
//    with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this intent log or a competing intent-status
//    plane derived from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalChainTime} from "./FinalChainTime.sol";
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalStateTrees} from "./FinalStateTrees.sol";
import {FinalPlaneSweep} from "./FinalPlaneSweep.sol";
import {SweepKind} from "../utils/FinalSweep.sol";

/**
 * @title FinalIntentLog
 * @notice Every intent is posted here before it may be anchored. Ordering is
 *         block order.
 *
 * @dev ## Why this is a log and not a tree
 *
 * Final Chain carries six trees, and they publish in ROUNDS — every root
 * together, one version bump each. That structure is for state that changes
 * slowly and must be proven elsewhere. An intent is neither: it is high
 * cadence, it lives 180 seconds, and putting it in a round-published tree would
 * bump a version on every transaction and age every outstanding proof — the
 * same objection that kept a liveness timestamp out of the tree-1 account leaf.
 *
 * It is not an MMR either. `FinalBundleLog`'s payload already IS the bundle's
 * intent-Merkle root, so a second append-only log over the same leaves would
 * prove, one level earlier, a fact the bundle anchor already proves.
 *
 * What an intent actually needs is ordering, public availability, and a
 * presence check the anchoring quorum can run objectively. Block order gives
 * the first, calldata gives the second, and a mapping gives the third. That is
 * the whole contract.
 *
 * ## What it buys
 *
 * `FinalBundleLog.append` consumes from here, so a bundle cannot be anchored
 * unless every intent in it was posted first — and the execution chains'
 * authorization is that anchor. Final-Chain-first admission stops being a
 * quorum policy and becomes bytecode. Replay protection falls out of the same
 * mapping: a leaf is consumed exactly once, ever.
 *
 * For the user it closes substitution. The intent they composed is ordered
 * publicly before anything executes, so a relayer or a frontend cannot swap it
 * for another — the anchor will only carry what this log holds.
 *
 * ## The body is encrypted and this contract cannot read it
 *
 * `header` is routing metadata: a chain reference, a deadline, KEM ciphertexts,
 * a commitment to the body, and the intent leaf. Account, destination, fee and
 * calldata are all inside `ciphertext`. That is what closes content-based
 * front-running — an operator watching this log sees a chain id and a deadline
 * and cannot build a profitable order from either.
 *
 * There is deliberately no decryption path on chain and no AEAD precompile. The
 * chain checks a hash and nothing else.
 *
 * ## The envelope is STORED, not left in the event log
 *
 * A two-KEM envelope is ~16 KB and keeping it in state is not free. It is kept
 * anyway, for the reason `FinalBundleLog` keeps its payloads: this chain caps a
 * `getLogs` range at 100,000 blocks and mints one every 100 ms, so the log is
 * reachable for **2.8 hours**. A forced-path intent lives **48**.
 *
 * An event-only envelope would therefore be unreadable long before it expired —
 * precisely for the guardians who are meant to review a forced intent, and
 * precisely in the window the forced path exists to cover. That is not a
 * degraded read, it is the escape hatch not working.
 *
 * So the event is for DISCOVERY and carries no bytes; state is for retrieval,
 * over one `eth_call` and no range. Emitting the envelope as well would be
 * ~128k gas of duplication for data the same transaction already stored.
 *
 * Measured in `FinalIntentLog.t.sol`: **11,517,049 gas** for a 16,426-byte
 * envelope, which at a 60M block gas limit is **5 intents per block** — 50 a
 * second at 100 ms. That is the throughput ceiling on this path, and it is
 * dominated by HQC-5's 14,421-byte ciphertext.
 *
 * Two levers if it ever binds. SSTORE2-style data contracts are ~3x cheaper per
 * byte, and would need chunking because a two-recipient envelope exceeds
 * EIP-170's 24,576. Pruning consumed envelopes is the other, and it trades the
 * record for the space — a decision, not a cleanup.
 *
 * **The bond becomes load-bearing when the RPC opens, not before.** Posting is
 * permissionless in this contract and the base fee is 1 wei, so the only thing
 * standing between an attacker and 60M gas a block of permanent state is that
 * Final Chain's RPC is fronted by a default-deny Cloud Armor allowlist and
 * reaches operators only. That is a perimeter, not a mechanism, and it is the
 * one that has to be removed for users to post their own intents. Nothing here
 * rate-limits; the bond is what is supposed to, and this is why it cannot ship
 * after the RPC does.
 *
 * ## Approve and cancel, without naming an account
 *
 * An approval-gated intent executes only after a second on-chain act, made
 * after the user decrypts the posted envelope and reads what the CHAIN holds —
 * the read-back that closes execution of what the user has not seen. Who may
 * approve is a **per-intent, wallet-generated ML-DSA-87 key**, committed in the
 * plaintext header: the commitment names a key, never an account, which is what
 * answers "a per-intent veto has to say who may veto, and the account is
 * encrypted". A substituted commitment strands the intent — the execution
 * signature bound into the leaf still authorizes every field — and cancel is
 * the same key, any time before consumption.
 *
 * `approve` also DELIVERS the executor section: the content key and the
 * account's KEM public keys, sealed to the executor set. Before approval the
 * executor holds ciphertext it cannot open; that is cryptography, not policy.
 * An AUTO_APPROVE posting (header flag, bit 0) skips the gate for automation
 * and scheduling and carries its executor section at posting — flipping the
 * flag skips the second look and can never change what executes.
 *
 * The account-wide veto stays `FinalAccountLedger`'s freeze; auto-approved
 * intents have no per-intent cancel because they commit to no approval key.
 *
 * ## Tree 7 is the search structure; the MMR is the record
 *
 * Every transition — posted, approved, cancelled, consumed — is mirrored into
 * `FinalStateTrees.TREE_INTENTS` through `treeWriter[7]`, keyed by a RING over
 * the posting sequence (`seq mod 2^20`), so slots recycle and the tree is an
 * index with a ~1M-posting retention window rather than a permanent record.
 * Backends enumerate it instead of scanning logs; proofs against it are proofs
 * about the current window.
 */
contract FinalIntentLog is FinalPlaneSweep {
    /// @notice Where roles are resolved. Immutable.
    ///
    /// @dev The only thing this log needs a registry for is authorizing
    ///      `setConsumer`, and that could have been a deployer check. It is the
    ///      registry instead so the wiring falls under the SAME bootstrap window
    ///      as `FinalStateTrees.setTreeWriter` and `FinalBundleLog.configure` —
    ///      one rule for who may wire Final Chain together, rather than three.
    FinalIdentityRegistry public immutable registry;

    /// @notice Where every status transition is mirrored (tree 7). Immutable:
    /// a movable index would be an index whose history can be swapped.
    FinalStateTrees public immutable trees;

    /// @dev Header layout, fixed offsets. Must match `encodeHeader` in
    ///      `FinalBackend/src/intents/intentEnvelope.js` — the cross-repo parity
    ///      suite pins it. Length-prefixed and canonical precisely so these
    ///      offsets exist: a JSON header would have no stable position to read.
    ///
    ///        0         version           uint8   = 2 | 3 (v3 salts recipientsHash; offsets identical)
    ///        1         flags             uint8   bit0 AUTO_APPROVE; rest zero
    ///        2..33     targetChainRef    bytes32
    ///        34..41    executeNotBefore  uint64  ms; 0 = immediate
    ///        42..49    deadline          uint64  ms
    ///        50..81    bodyCommitment    bytes32
    ///        82..113   intentLeaf        bytes32
    ///        114..145  approvalKeyCommit bytes32 zero iff AUTO_APPROVE
    ///        146..147  kemSet            uint16
    ///        148..149  kemKeyVersion     uint16
    ///        150..181  recipientsHash    bytes32
    ///        182..     recipients, executorSection, bond
    uint256 private constant OFF_FLAGS = 1;
    /// @dev Byte offset of `targetChainRef` in the packed intent header. The header is
    ///      read by offset rather than decoded,
    ///       so every offset here must match what the header's producers write, byte for byte.
    uint256 private constant OFF_TARGET_CHAIN_REF = 2;
    /// @dev Byte offset of `executeNotBefore` in the packed intent header. The header is
    ///      read by offset rather than decoded,
    ///       so every offset here must match what the header's producers write, byte for byte.
    uint256 private constant OFF_EXECUTE_NOT_BEFORE = 34;
    /// @dev Byte offset of `deadline` in the packed intent header. The header is
    ///      read by offset rather than decoded,
    ///       so every offset here must match what the header's producers write, byte for byte.
    uint256 private constant OFF_DEADLINE = 42;
    /// @dev Byte offset of `bodyCommitment` in the packed intent header. The header is
    ///      read by offset rather than decoded,
    ///       so every offset here must match what the header's producers write, byte for byte.
    uint256 private constant OFF_BODY_COMMITMENT = 50;
    /// @dev Byte offset of `intentLeaf` in the packed intent header. The header is
    ///      read by offset rather than decoded,
    ///       so every offset here must match what the header's producers write, byte for byte.
    uint256 private constant OFF_INTENT_LEAF = 82;
    /// @dev Byte offset of `approvalKeyCommitment` in the packed intent header. The header is
    ///      read by offset rather than decoded,
    ///       so every offset here must match what the header's producers write, byte for byte.
    uint256 private constant OFF_APPROVAL_KEY_COMMIT = 114;
    /// @dev The whole fixed prefix, including `recipientsHash` — which this
    ///      contract never reads. Requiring it anyway is the cheap half of
    ///      "well-formed": a header truncated after the leaf would parse
    ///      perfectly here and then fail to decrypt for its recipient. The
    ///      contract reads nothing past offset 145.
    uint256 private constant HEADER_MIN_BYTES = 182;

    /// @notice The oldest envelope wire version this contract reads.
    uint8 public constant HEADER_VERSION = 2;

    /// @notice The newest. v3 keeps every offset and salts `recipientsHash`
    ///         — a field this contract never reads — with a holder-derived value,
    ///         so a wallet's postings stop sharing a fingerprint in the clear.
    ///         Both versions parse identically here; the header's own AAD is what
    ///         tells them apart for the recipients.
    uint8 public constant HEADER_VERSION_MAX = 3;

    /// @notice Header flag bit 0: skip the approval gate.
    /// @dev Poster-controlled routing, honestly scoped: flipping it skips the
    /// user's second look and can never change what executes — the user's own
    /// signature over the intent fields, bound into the leaf, remains the only
    /// execution authorization.
    uint8 public constant FLAG_AUTO_APPROVE = 0x01;

    // ---------------------------------------------------- approval domains

    /// @dev Distinct per action, so an approval can never be replayed as a
    /// cancellation or vice versa. Both digests bind this chain and this log.
    bytes32 public constant DOMAIN_INTENT_APPROVE = keccak256("FINAL_INTENT_APPROVE_v01");
    /// @notice Domain tag for a cancellation digest.
    /// @dev Separate from every other tag, so a signature collected to cancel one intent cannot be replayed as
    ///       an approval of anything.
    bytes32 public constant DOMAIN_INTENT_CANCEL = keccak256("FINAL_INTENT_CANCEL_v01");

    // ------------------------------------------------------ tree-7 mirror

    /// @dev The ring key's domain. Named in `FinalStateTrees`' key.* family
    /// because that is the space it lives in; computed HERE because the log is
    /// the writer and the backend mirrors this function, not the tree.
    bytes32 public constant DOMAIN_INTENT_KEY = keccak256("FinalStateTrees.key.intent.v01");
    /// @dev The status leaf's domain.
    bytes32 public constant DOMAIN_INTENT_STATUS_LEAF = keccak256("FINAL_INTENT_STATUS_LEAF_v01");
    /// @notice Ring size: keys recycle at `CAPACITY`, so the tree can never
    /// fill however long the chain runs. Pinned equal to
    /// `FinalStateTrees.CAPACITY` by test.
    uint64 public constant INTENT_SLOT_RING = uint64(1) << 20;

    /// @notice The tree this log writes. Restated from `FinalStateTrees`
    /// because a contract-type constant is not reachable here; pinned equal to
    /// `trees.TREE_INTENTS()` by test.
    uint8 public constant TREE_INTENTS_ID = 7;
    /// @notice The branch the intent ring lives in — `FinalStateTrees.BRANCH_MAIN`,
    ///         pinned by test. Branch 0 of tree 7 is the log's configuration.
    uint8 public constant BRANCH_MAIN_ID = 1;

    /// @notice Tree-7 status values.
    uint8 public constant STATUS_POSTED = 1;
    /// @notice Status: approved and awaiting consumption.
    uint8 public constant STATUS_APPROVED = 2;
    /// @notice Status: consumed. Terminal — a consumed intent is spent and cannot return to any other state.
    uint8 public constant STATUS_CONSUMED = 3;
    /// @notice Status: cancelled. Terminal, and recorded rather than erased, so a canceller can prove the intent
    ///          was withdrawn rather than never posted.
    uint8 public constant STATUS_CANCELLED = 4;

    /// @dev Registrar-quorum actions, verified by the registry with this log as
    /// the verifying contract.
    bytes32 public constant ACTION_SET_CONSUMER = keccak256("FINAL_INTENT_LOG_SET_CONSUMER_v01");
    /// @notice Action tag for seeding the sequence of a fresh log.
    bytes32 public constant ACTION_SEED_SEQUENCE = keccak256("FINAL_INTENT_LOG_SEED_SEQUENCE_v01");
    /// @notice Action tag for setting the bond policy. Distinct from the seeding tag, so an approval collected
    ///          for one cannot perform the other.
    bytes32 public constant ACTION_SET_BOND_POLICY = keccak256("FINAL_INTENT_LOG_SET_BOND_POLICY_v01");

    /**
     * @notice The longest an intent may stay consumable past its earliest
     *         execution moment.
     *
     * @dev A cap rather than an exact value because every lane shares this log,
     *      and an uncapped deadline would let a posting sit consumable forever —
     *      which is a replay window dressed as a long-lived intent. An immediate
     *      intent (`executeNotBefore == 0`) gets exactly this from `now`; a
     *      scheduled one gets it from its own start.
     */
    /// @dev MILLISECONDS, like every duration on this chain. Its predecessor
    ///      was once 48 hours read against a millisecond clock — 48 SECONDS —
    ///      so a header whose deadline a client computed from wall time was
    ///      refused before it could ever be posted.
    uint64 public constant EXECUTION_WINDOW = 48 hours * FinalChainTime.MS_PER_SECOND;

    /// @notice How far ahead `executeNotBefore` may sit. The scheduling
    /// horizon: chosen at signing, because the deadline is inside the signed
    /// intent and cannot be extended afterwards.
    uint64 public constant MAX_SCHEDULE_HORIZON = 30 days * FinalChainTime.MS_PER_SECOND;

    /// @notice How far ahead of `executeNotBefore` a scheduled posting may be
    ///         CONSUMED when its target chain has no lead of its own — ninety
    ///         seconds, per target chain. Admission — the
    ///         co-signer round and the append that consumes the leaf — runs
    ///         before T, so the fleet can compose and broadcast the wrapper to
    ///         land in the first target-chain block at or after T rather than
    ///         minutes late. The holder's veto (`cancel`) closes at consume,
    ///         i.e. no earlier than T − lead: the lead is the last call for a
    ///         cancel. An immediate posting (`executeNotBefore == 0`) has no
    ///         lead. Per chain: a row in tree 7's CONFIGURATION branch
    ///         (`FinalStateTrees.setConfig`, key `configKey(CONFIG_SCHEDULE_LEAD_MS,
    ///         targetChainRef)`) overrides this default (`scheduleLeadMsOf`),
    ///         explicit zero included — the first tenant of the config branch.
    uint64 public constant DEFAULT_SCHEDULE_LEAD_MS = 90 * FinalChainTime.MS_PER_SECOND;

    /// @notice The config-row NAME of a target chain's schedule lead, in
    ///         milliseconds: `trees.configKey(CONFIG_SCHEDULE_LEAD_MS, targetChainRef)`
    ///         → one word holding the lead. Written under the configuration
    ///         authority (bootstrap admin, then the registrar quorum) — an
    ///         operational parameter, not a trust boundary — and PROVABLE like
    ///         every other row of the plane, which a table here was not.
    bytes32 public constant CONFIG_SCHEDULE_LEAD_MS = keccak256("FinalIntentLog.config.scheduleLeadMs.v01");

    struct Posted {
        /// @dev Zero means never posted. Non-zero and `consumedAt == 0` means open.
        uint64 postedAt;
        /// @dev From the header, in MILLISECONDS. Before this, `consume` refuses.
        uint64 executeNotBefore;
        /// @dev From the header, in MILLISECONDS. After this, `consume` refuses.
        uint64 deadline;
        /// @dev Block timestamp of the anchoring append. Non-zero means spent.
        uint64 consumedAt;
        /// @dev `keccak256(body)` — the leaf↔body binding. With the reveal
        ///      schema gone it is checked OFF-chain: the execution chain's
        ///      public calldata against this word.
        bytes32 bodyCommitment;
        /// @dev `keccak256` of the per-intent approval public key; zero iff
        ///      AUTO_APPROVE. Names a key, never an account.
        bytes32 approvalKeyCommit;
        /// @dev From the header; kept for the tree-7 status leaf.
        bytes32 targetChainRef;
        /// @dev When `approve` verified. Non-zero means approved.
        uint64 approvedAt;
        /// @dev When `cancel` verified. Non-zero means dead: `consume` refuses.
        uint64 cancelledAt;
        /// @dev Posting sequence — the tree-7 ring position (`seq mod 2^20`).
        uint64 seq;
        /// @dev Header flags, verbatim.
        uint8 flags;
    }

    /// @dev The bytes, kept apart from `Posted` on purpose. `consume` runs on
    ///      the anchoring path and reads only the packed record; it must never
    ///      pay to walk 16 KB it does not look at.
    struct Envelope {
        /// @dev Canonical header, exactly as posted.
        bytes header;
        /// @dev The sealed body. This contract cannot read it and never tries.
        bytes ciphertext;
        /// @dev The content key and the account's KEM public keys, sealed to
        ///      the executor set. Delivered by `approve` — so before approval
        ///      the executor holds ciphertext it cannot open — or present from
        ///      posting on an AUTO_APPROVE intent. Opaque here: this contract
        ///      stores it and never parses it.
        bytes executorSection;
    }

    /// @notice Keyed by the intent LEAF, not by an envelope id.
    ///
    /// @dev The leaf is what `FinalBundleLog` folds and what the execution
    ///      chain's gateway recomputes, so keying on it makes the presence check
    ///      exact. It also makes duplicate protection land on the right thing: a
    ///      second header carrying a leaf already posted is the same intent
    ///      offered twice, and is refused.
    mapping(bytes32 leaf => Posted) public postedOf;

    /// @notice The full envelope, retrievable for as long as the chain exists.
    mapping(bytes32 leaf => Envelope) private _envelopeOf;

    /// @notice Postings ever made. The next intent takes this as its `seq`.
    uint64 public postSeq;
    /// @notice The leaf posted at sequence `seq` — the enumeration the backend
    ///         walks (`postSeq` is the count) instead of a `getLogs` range.
    mapping(uint64 => bytes32) public leafAt;

    // ─────────────────────────── the per-vertex bond ───────────────────────────
    //
    // **Encrypting the account removes attribution, and attribution is what
    // rate-limiting runs on.** Posting is permissionless at a 1 wei base fee and
    // state spam does not age out the way calldata spam does, so the thing
    // holding it off today is the default-deny allowlist on this chain's RPC —
    // a perimeter, and precisely the one that has to come down for users to post
    // their own intents. A bond charges the spammer regardless of identity,
    // needs no new cryptography, and is the natural unit for operators paid per
    // unit of work.
    //
    // Refunded on valid execution, forfeited on a vertex that was never
    // anchored. Both are PULL: `consume` runs inside the anchoring append and
    // must not be able to fail, or be delayed, because of where a refund was
    // going.
    //
    // **Not in the header.** The header's `<authenticator>` field stays a
    // free-form reference for off-chain accounting and is deliberately not the
    // bond itself. Moving the bond into the header would be an encoding change
    // rippling through every reader and the cross-repo offset parity, and a
    // bond is not worth one when native value keyed by leaf says the same
    // thing.
    //
    // **Zero is the shipped state**, which is today's behaviour exactly. Arming
    // it is the step that must precede opening the RPC, not follow it.

    struct Bond {
        /// @dev Who paid, and who a refund goes back to. Not the intent's
        /// account — that is encrypted and this contract cannot know it — which
        /// is the whole reason a bond works here and a per-account quota does
        /// not.
        address payer;
        /// @dev Wei paid. Stored rather than re-read from `bondWei`, because the
        /// rate can move between posting and settlement and a refund of
        /// something other than what was paid is a fee nobody agreed to.
        /// `uint88` covers 309 million ether and packs the struct into one slot.
        uint88 amount;
        /// @dev Paid out or forfeited. Set before the transfer.
        bool settled;
    }

    /// @notice The bond posted with each intent, if any.
    mapping(bytes32 leaf => Bond) public bondOf;

    /// @notice What `post` requires. Zero disables the bond entirely.
    uint256 public bondWei;

    /// @notice Where a forfeited bond goes.
    /// @dev Zero means BURN — the value stays in this contract and nothing can
    /// move it. That is the safe default rather than an oversight: a forfeit
    /// destination is a revenue stream, and one set by accident is worse than
    /// one that does not exist. Arming a bond without naming a destination is
    /// refused, so this can never be reached by forgetting.
    address public bondForfeitTo;

    /// @notice Native this contract may not spend: every bond it still owes
    /// out, plus every bond it has burned in place.
    ///
    /// @dev `bondOf` is per-leaf and a mapping cannot be iterated, so the total
    /// is maintained incrementally — the same rule `FinalPhiSupply.totalAllocated`
    /// follows, and for the same reason: a liability that can only be totalled
    /// by an off-chain sweep is not a liability the contract can defend. It is
    /// what `_sweepReserved` fences, so a rescue of a stray asset can never
    /// reach a poster's refund.
    ///
    /// A bond leaves this total when it is refunded (`claimBond`) or when it is
    /// forfeited TO A DESTINATION (`forfeitBond` with `bondForfeitTo` set): both
    /// send the wei out, so the obligation ends with the transfer. A forfeit to
    /// a zero destination does NOT leave it. That case burns the value in place
    /// — deliberately, which is why arming a bond without a destination is
    /// refused — and letting the burn fall out of the total would quietly turn
    /// it into sweepable revenue, which is the opposite of what it was.
    uint256 public reservedBondWei;

    /// @notice The bond rate, or its destination, changed.
    event BondPolicySet(uint256 bondWei, address forfeitTo);
    /// @notice A bond was posted alongside an intent.
    event BondPosted(bytes32 indexed leaf, address indexed payer, uint256 amount);
    /// @notice A bond was returned after the intent was anchored.
    event BondRefunded(bytes32 indexed leaf, address indexed payer, uint256 amount);
    /// @notice A bond was forfeited: the intent expired without being anchored.
    event BondForfeited(bytes32 indexed leaf, address indexed payer, uint256 amount);

    /// @notice Thrown when a posting's bond does not match what the policy requires.
    /// @param required The bond the policy demands.
    /// @param supplied The bond offered.
    error BondMismatch(uint256 required, uint256 supplied);
    /// @notice Thrown when a bond operation names an intent that posted none.
    /// @param leaf The intent.
    error NoBond(bytes32 leaf);
    /// @notice Thrown when a bond that has already been refunded or forfeited is settled again.
    /// @param leaf The intent.
    error BondAlreadySettled(bytes32 leaf);
    /// @notice Thrown when a bond is refunded for an intent that did not earn it back.
    /// @dev The bond is what makes posting cost something: refunding one that was not honoured would make
    ///       posting free again and the deterrent nominal.
    /// @param leaf The intent.
    error BondNotRefundable(bytes32 leaf);
    /// @notice Thrown when a bond is forfeited before the intent's deadline has passed.
    /// @dev Forfeiting early would take a bond from a poster who still had time to honour the intent.
    /// @param leaf The intent.
    /// @param deadline The deadline that has not yet passed.
    error BondNotForfeitable(bytes32 leaf, uint64 deadline);
    /// @notice Thrown when paying out a bond fails.
    /// @param to The intended recipient.
    /// @param amount The amount that failed to transfer.
    error BondTransferFailed(address to, uint256 amount);
    /// @notice Thrown when a bond policy sets a bond without naming where a forfeit goes.
    /// @dev A forfeitable bond with nowhere to go would be burned by accident rather than by decision.
    error BondNeedsAForfeitDestination();

    /// @notice The one contract allowed to consume. Set once.
    address public consumer;

    /// @notice Discovery only. The envelope itself is in state — read it with
    ///         `envelopeOf`, which needs no `getLogs` range.
    event IntentPosted(
        bytes32 indexed leaf,
        bytes32 indexed targetChainRef,
        uint64 executeNotBefore,
        uint64 deadline,
        uint8 flags,
        uint256 headerBytes,
        uint256 ciphertextBytes
    );
    /// @notice The user read the posted intent back and approved it. Carries
    ///         the executor section's size — the bytes that let the executor
    ///         decrypt from here on.
    event IntentApproved(bytes32 indexed leaf, uint256 executorSectionBytes);
    /// @notice The approval key revoked the intent. Terminal.
    event IntentCancelled(bytes32 indexed leaf);
    /// @notice Consumed by an anchoring append.
    event IntentConsumed(bytes32 indexed leaf, address indexed by);
    /// @notice The single consumer permitted to mark intents consumed was pinned.
    /// @param consumer The consumer.
    event ConsumerSet(address indexed consumer);
    /// @notice A target chain's schedule lead was configured.

    /// @notice Thrown when an intent header is shorter than the fields it must contain.
    /// @dev Checked before any offset is read, so a short header is refused rather than parsed against whatever
    ///       follows it in calldata.
    /// @param length The length supplied.
    error HeaderTooShort(uint256 length);
    /// @notice Thrown when a header declares a version this log does not parse.
    /// @dev Asserted rather than inferred: reading one layout's bytes under another's field names produces a
    ///       well-formed intent that means something else entirely.
    /// @param version The version declared.
    error UnsupportedHeaderVersion(uint8 version);
    /// @notice Thrown when an intent is posted or acted on after its deadline.
    /// @dev Times here are MILLISECONDS, as everywhere on this chain.
    /// @param deadline The intent's deadline.
    /// @param nowMs The current time.
    error DeadlinePassed(uint64 deadline, uint64 nowMs);
    /// @notice Thrown when an intent's deadline is further out than the log permits.
    /// @dev Bounding it stops an intent standing open indefinitely as a claim nothing will ever clear.
    /// @param deadline The deadline supplied.
    /// @param limit The furthest the log allows.
    error DeadlineTooFar(uint64 deadline, uint64 limit);
    /// @notice Thrown when the zero leaf is offered as an intent identifier.
    error ZeroLeaf();
    /// @notice Thrown when an intent leaf that is already posted is posted again.
    /// @param leaf The intent.
    error AlreadyPosted(bytes32 leaf);
    /// @notice Thrown when an intent that was never posted is acted on.
    /// @param leaf The intent.
    error NotPosted(bytes32 leaf);
    /// @notice Thrown when an intent that is already consumed is consumed again.
    /// @dev Consumption is terminal and one-shot, which is what stops one approval funding two executions.
    /// @param leaf The intent.
    /// @param at When it was consumed.
    error AlreadyConsumed(bytes32 leaf, uint64 at);
    /// @notice Thrown when an expired intent is consumed.
    /// @param leaf The intent.
    /// @param deadline The deadline that passed.
    error Expired(bytes32 leaf, uint64 deadline);
    /// @notice Header flag bits beyond the ones this version defines.
    error UnsupportedFlags(uint8 flags);
    /// @notice An AUTO_APPROVE header must commit to no approval key.
    error AutoApproveTakesNoCommitment(bytes32 approvalKeyCommit);
    /// @notice An approval-gated header must commit to one.
    error ApprovalKeyCommitRequired();
    /// @notice `executeNotBefore` sits beyond the scheduling horizon.
    error ScheduleTooFar(uint64 executeNotBefore, uint64 limit);
    /// @notice The presented key does not hash to the header's commitment.
    error WrongApprovalKey(bytes32 expected, bytes32 got);
    /// @notice The signature did not verify under the committed key.
    error ApprovalSignatureInvalid(bytes32 leaf);
    /// @notice Thrown when an intent that is already approved is approved again.
    /// @param leaf The intent.
    /// @param at When it was approved.
    error AlreadyApproved(bytes32 leaf, uint64 at);
    /// @notice AUTO_APPROVE intents commit to no key: nothing to approve or
    /// cancel per-intent. Their veto is the account-wide freeze.
    error NoApprovalKey(bytes32 leaf);
    /// @notice Thrown when a cancelled intent is approved or consumed.
    /// @param leaf The intent.
    /// @param at When it was cancelled.
    error IntentIsCancelled(bytes32 leaf, uint64 at);
    /// @notice `executeNotBefore` has not arrived. Scheduling is a chain rule.
    error TooEarly(bytes32 leaf, uint64 executeNotBefore, uint64 nowMs);
    /// @notice Neither approved nor AUTO_APPROVE: the user has not read it back.
    error NotApproved(bytes32 leaf);
    /// @notice Thrown when consumption is attempted by anything but the pinned consumer.
    /// @param caller The rejected caller.
    error NotConsumer(address caller);
    /// @notice Thrown when consumption is attempted before a consumer is pinned.
    error ConsumerUnset();
    /// @notice Thrown when the consumer is pinned a second time.
    /// @dev Write-once: a rotatable consumer would let whoever could move it consume every standing intent.
    /// @param current The consumer already pinned.
    error ConsumerAlreadySet(address current);
    /// @notice Thrown when the zero address is offered as the consumer.
    error ZeroConsumer();
    /// @notice Thrown when a caller holds none of the authorities the entrypoint accepts.
    /// @param caller The rejected caller.
    error NotAuthorized(address caller);

    /**
     * @dev No precompile of its own to call — this contract verifies no
     *      signature and reads no PQ key, it hashes and compares. The probe is
     *      still here because the claim "these deploy to Final Chain and nowhere
     *      else" should be enforced rather than documented, and a posting log on
     *      an execution chain would be a place intents could be posted that no
     *      anchor will ever read.
     */
    constructor(FinalIdentityRegistry registry_, FinalStateTrees trees_) {
        FinalChainPrecompiles.assertAvailable();
        registry = registry_;
        trees = trees_;
    }

    /// @notice A fresh log took over the previous log's sequence.
    event SequenceSeeded(uint64 postSeq);
    /// @notice The sequence can be seeded only into a log that holds nothing.
    error NotFresh();

    /**
     * @notice Take over the previous log's `postSeq`, so every walker's cursor
     *         (`postSeq` is the count; the fleet walks it, never a `getLogs`
     *         range) stays ahead of nothing and behind everything new. Slots
     *         below the seed belong to the previous log. A redeploy does NOT
     *         wipe. Configuration authority; only while empty.
     */
    function seedSequence(
        uint64 postSeq_,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SEED_SEQUENCE, keccak256(abi.encode(postSeq_)), anchorBlock, approvals
        );
        if (postSeq != 0) revert NotFresh();
        postSeq = postSeq_;
        emit SequenceSeeded(postSeq_);
    }

    /**
     * @dev The configuration gate: the registry's bootstrap admin alone while
     * its window is open, the sealed `ROLE_REGISTRAR` quorum afterwards — the
     * same window and quorum the registry and the trees use. `approvals` is
     * empty during bootstrap.
     */
    function _requireConfigurationAuthority(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) private {
        if (!registry.bootstrapSealed() && msg.sender == registry.bootstrapAdmin()) return;
        registry.requireRegistrarQuorum(actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /**
     * @notice Name the contract allowed to consume postings.
     *
     * @dev One-way and one-shot, like `FinalStateTrees.treeWriter`. Deployment
     *      is circular — the bundle log needs this address in its constructor —
     *      so this is set afterwards, and it can never be moved: a consumer that
     *      could be repointed would be a consumer that could be replaced with
     *      one that does not check.
     *
     *      Authorized, and it has to be. Left open it would be a front-run away
     *      from permanent: an attacker naming their own contract first would
     *      brick the PQ lane with no recovery, because one-shot cuts both ways.
     */
    function setConsumer(
        address newConsumer,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_CONSUMER, keccak256(abi.encode(newConsumer)), anchorBlock, approvals
        );
        if (consumer != address(0)) revert ConsumerAlreadySet(consumer);
        if (newConsumer == address(0)) revert ZeroConsumer();
        consumer = newConsumer;
        emit ConsumerSet(newConsumer);
    }

    /// @notice The lead a scheduled posting for `targetChainRef` gets: the
    ///         config-branch row when one exists (explicit zero included),
    ///         else `DEFAULT_SCHEDULE_LEAD_MS`. Capped at the schedule horizon:
    ///         a longer lead would make every scheduled posting consumable at
    ///         post time. The fleet reads this so the watch and the contract
    ///         agree on when an attempt may begin.
    function scheduleLeadMsOf(bytes32 targetChainRef) public view returns (uint64) {
        (bytes32 value, bool present) =
            trees.configValue(TREE_INTENTS_ID, trees.configKey(CONFIG_SCHEDULE_LEAD_MS, targetChainRef));
        if (!present) return DEFAULT_SCHEDULE_LEAD_MS;
        uint256 lead = uint256(value);
        return lead > MAX_SCHEDULE_HORIZON ? MAX_SCHEDULE_HORIZON : uint64(lead);
    }

    /**
     * @notice Post an intent. Permissionless.
     *
     * @dev Permissionless on purpose. Gating posting on an identity would
     *      reintroduce exactly the attribution the encryption removes, so what
     *      bounds a spammer is the BOND rather than a quota: `msg.value` must
     *      equal `bondWei`, and it comes back only if the intent is anchored.
     *
     *      While `bondWei` is zero — the shipped state — this is what it has
     *      always been, and `msg.value` must then be zero too. Accepting a
     *      payment the contract has no rule for would be accepting value it
     *      cannot refund.
     *
     * @param header Canonical envelope header. Parsed, never stored.
     * @param ciphertext The sealed body. Emitted, never stored, never read.
     * @return leaf The intent leaf this posting is keyed on.
     */
    function post(bytes calldata header, bytes calldata ciphertext)
        external
        payable
        returns (bytes32 leaf)
    {
        if (header.length < HEADER_MIN_BYTES) revert HeaderTooShort(header.length);
        uint8 version = uint8(header[0]);
        if (version < HEADER_VERSION || version > HEADER_VERSION_MAX) {
            revert UnsupportedHeaderVersion(version);
        }
        uint8 flags = uint8(header[OFF_FLAGS]);
        if (flags & ~FLAG_AUTO_APPROVE != 0) revert UnsupportedFlags(flags);

        bytes32 targetChainRef = bytes32(header[OFF_TARGET_CHAIN_REF:OFF_TARGET_CHAIN_REF + 32]);
        uint64 executeNotBefore =
            uint64(bytes8(header[OFF_EXECUTE_NOT_BEFORE:OFF_EXECUTE_NOT_BEFORE + 8]));
        uint64 deadline = uint64(bytes8(header[OFF_DEADLINE:OFF_DEADLINE + 8]));
        bytes32 bodyCommitment = bytes32(header[OFF_BODY_COMMITMENT:OFF_BODY_COMMITMENT + 32]);
        leaf = bytes32(header[OFF_INTENT_LEAF:OFF_INTENT_LEAF + 32]);
        bytes32 approvalKeyCommit =
            bytes32(header[OFF_APPROVAL_KEY_COMMIT:OFF_APPROVAL_KEY_COMMIT + 32]);

        // The commitment and the flag are one decision stated twice, so the two
        // must agree: an auto intent with a commitment would look cancellable
        // and not be gated, and a gated one without a commitment could never be
        // approved by anyone — dead on arrival, wearing a live intent's shape.
        bool auto_ = flags & FLAG_AUTO_APPROVE != 0;
        if (auto_ && approvalKeyCommit != bytes32(0)) {
            revert AutoApproveTakesNoCommitment(approvalKeyCommit);
        }
        if (!auto_ && approvalKeyCommit == bytes32(0)) revert ApprovalKeyCommitRequired();

        if (leaf == bytes32(0)) revert ZeroLeaf();
        if (postedOf[leaf].postedAt != 0) revert AlreadyPosted(leaf);

        // Scheduling is a contract rule. An immediate intent gets the window
        // from now; a scheduled one gets it from its own start, and the start
        // itself is bounded by the horizon — chosen at signing, because the
        // deadline is inside the signed intent and cannot be extended after.
        uint64 nowMs = FinalChainTime.nowMs();
        if (deadline <= nowMs) revert DeadlinePassed(deadline, nowMs);
        if (executeNotBefore == 0) {
            uint64 limit = nowMs + EXECUTION_WINDOW;
            if (deadline > limit) revert DeadlineTooFar(deadline, limit);
        } else {
            uint64 horizon = nowMs + MAX_SCHEDULE_HORIZON;
            if (executeNotBefore > horizon) revert ScheduleTooFar(executeNotBefore, horizon);
            uint64 limit = executeNotBefore + EXECUTION_WINDOW;
            if (deadline > limit) revert DeadlineTooFar(deadline, limit);
        }

        // The bond scales with lifetime. A month-long posting occupies state
        // and attention a 48-hour one does not, and a flat bond makes
        // long-lived spam the cheap kind. Ceiling division, so a single extra
        // millisecond of a new window costs a whole unit.
        uint256 required = bondWei;
        if (required != 0) {
            required = required
                * ((uint256(deadline) - nowMs + EXECUTION_WINDOW - 1) / EXECUTION_WINDOW);
        }
        if (required > type(uint88).max) revert BondMismatch(type(uint88).max, required);
        if (msg.value != required) revert BondMismatch(required, msg.value);

        uint64 seq = postSeq;
        postSeq = seq + 1;
        leafAt[seq] = leaf;

        postedOf[leaf] = Posted({
            postedAt: nowMs,
            executeNotBefore: executeNotBefore,
            deadline: deadline,
            consumedAt: 0,
            bodyCommitment: bodyCommitment,
            approvalKeyCommit: approvalKeyCommit,
            targetChainRef: targetChainRef,
            approvedAt: 0,
            cancelledAt: 0,
            seq: seq,
            flags: flags
        });
        _envelopeOf[leaf] = Envelope({header: header, ciphertext: ciphertext, executorSection: ""});

        if (required != 0) {
            bondOf[leaf] = Bond({payer: msg.sender, amount: uint88(required), settled: false});
            reservedBondWei += required;
            emit BondPosted(leaf, msg.sender, required);
        }

        _writeStatus(leaf, postedOf[leaf], STATUS_POSTED);
        emit IntentPosted(
            leaf, targetChainRef, executeNotBefore, deadline, flags, header.length, ciphertext.length
        );
    }

    /**
     * @notice Set the bond rate and where a forfeit goes.
     *
     * @dev Same gate as `setConsumer`, and unlike it this is NOT one-shot: a
     * bond is a price and a price that could never move would be a parameter
     * chosen once, before the traffic it is meant to bound existed.
     *
     * Arming a non-zero bond without a forfeit destination is refused. Zero
     * means burn, which is a real choice — but it has to be made rather than
     * arrived at by leaving a field unset, because the difference is an
     * accumulating balance nobody can ever move.
     *
     * Changing the rate does not touch bonds already posted. Each stores what
     * was actually paid, so a rate rise cannot retroactively underpay a refund
     * and a cut cannot leave one over-funded.
     */
    function setBondPolicy(
        uint256 newBondWei,
        address forfeitTo,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_BOND_POLICY, keccak256(abi.encode(newBondWei, forfeitTo)), anchorBlock, approvals
        );
        if (newBondWei != 0 && forfeitTo == address(0)) {
            // Only when ARMING. Setting the rate back to zero with no
            // destination is disarming, which needs no destination.
            revert BondNeedsAForfeitDestination();
        }
        if (newBondWei > type(uint88).max) revert BondMismatch(type(uint88).max, newBondWei);
        bondWei = newBondWei;
        bondForfeitTo = forfeitTo;
        emit BondPolicySet(newBondWei, forfeitTo);
    }

    /**
     * @notice Return a bond whose intent was anchored.
     *
     * @dev Permissionless to CALL and fixed in destination: the money goes to
     * the recorded payer whoever asks. That is what lets a relayer sweep on a
     * user's behalf without being able to redirect anything.
     *
     * A pull rather than a push inside `consume`. `consume` runs in the
     * anchoring append, and a refund there would put an arbitrary payer's
     * `receive` on the path of every bundle — one contract that reverts, or
     * burns the gas, and the anchor fails for reasons that have nothing to do
     * with the bundle.
     *
     * `consumedAt != 0` is the whole test. Consumption means `FinalBundleLog`
     * folded this leaf into an anchored bundle, which is exactly "valid
     * execution" — there is no later outcome for the chain to wait on, because
     * from here the execution chains authorize against the anchor.
     */
    function claimBond(bytes32 leaf) external {
        Bond storage b = bondOf[leaf];
        if (b.payer == address(0)) revert NoBond(leaf);
        if (b.settled) revert BondAlreadySettled(leaf);
        if (postedOf[leaf].consumedAt == 0) revert BondNotRefundable(leaf);

        b.settled = true;
        uint256 amount = b.amount;
        address payer = b.payer;
        reservedBondWei -= amount;
        emit BondRefunded(leaf, payer, amount);
        _send(payer, amount);
    }

    /**
     * @notice Forfeit the bond on an intent that expired without being anchored.
     *
     * @dev Permissionless, and gated on the DEADLINE rather than on a judgement.
     * An intent past its deadline can never be consumed — `consume` refuses one
     * — so "expired and unconsumed" is a terminal, objective state, and anyone
     * may say so.
     *
     * That is the half of the bond that actually bounds spam. A refund on
     * success only makes an honest posting free; the cost of a vertex that goes
     * nowhere is what a spammer pays.
     */
    function forfeitBond(bytes32 leaf) external {
        Bond storage b = bondOf[leaf];
        if (b.payer == address(0)) revert NoBond(leaf);
        if (b.settled) revert BondAlreadySettled(leaf);
        Posted storage p = postedOf[leaf];
        if (p.consumedAt != 0) revert BondNotForfeitable(leaf, p.deadline);
        if (FinalChainTime.nowMs() <= p.deadline) revert BondNotForfeitable(leaf, p.deadline);

        b.settled = true;
        uint256 amount = b.amount;
        address to = bondForfeitTo;
        emit BondForfeited(leaf, b.payer, amount);
        // A zero destination BURNS: the value stays here and nothing can move
        // it. Deliberate, and the reason `setBondPolicy` refuses to arm a bond
        // without a destination unless somebody chose this one. The reserve
        // follows that: it drops only when the wei actually leaves, so a burn
        // stays fenced and the sweep cannot undo it.
        if (to != address(0)) {
            reservedBondWei -= amount;
            _send(to, amount);
        }
    }

    /// @dev Checks-effects-interactions is done by the caller — `settled` is set
    /// before this runs — so a reentrant call finds the bond already spent.
    ///
    /// Full gas rather than a stipend, because a payer may legitimately be a
    /// contract. The consequence to accept: a payer whose `receive` reverts
    /// cannot be refunded and the bond is stuck. That is their own contract's
    /// behaviour, and the alternative — swallowing the failure — would mark the
    /// bond settled while the money stayed here.
    function _send(address to, uint256 amount) private {
        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert BondTransferFailed(to, amount);
    }

    /**
     * @notice Approve a posted intent: the read-back that authorizes execution.
     *
     * @dev Anyone may SUBMIT this — the authority is the signature and the
     *      sender only pays gas, the same standing the guardian lanes give
     *      their relay. The key arrives in calldata and proves something only
     *      because it is checked against the commitment the header made at
     *      posting: storage-anchored evidence, exactly the rule `FinalPqQuorum`
     *      states for quorum keys.
     *
     *      The digest binds this chain, this log, the leaf AND the executor
     *      section, so an approval cannot be replayed here or elsewhere, and
     *      the section it delivered cannot be swapped for another under the
     *      same signature.
     *
     * @param publicKey The per-intent ML-DSA-87 public key (2592 B).
     * @param signature Over the 32-byte approval digest, verbatim (4627 B).
     * @param executorSection The content key and the account's KEM public
     *        keys, sealed to the executor set. Stored beside the envelope;
     *        this contract never parses it.
     */
    function approve(
        bytes32 leaf,
        bytes calldata publicKey,
        bytes calldata signature,
        bytes calldata executorSection
    ) external {
        Posted storage p = _requireActionable(leaf);
        if (p.approvedAt != 0) revert AlreadyApproved(leaf, p.approvedAt);
        uint64 nowMs = FinalChainTime.nowMs();
        if (nowMs > p.deadline) revert Expired(leaf, p.deadline);

        bytes32 digest = keccak256(
            abi.encode(
                DOMAIN_INTENT_APPROVE, block.chainid, address(this), leaf, keccak256(executorSection)
            )
        );
        _requireApprovalKey(p, publicKey, signature, digest, leaf);

        p.approvedAt = nowMs;
        _envelopeOf[leaf].executorSection = executorSection;
        _writeStatus(leaf, p, STATUS_APPROVED);
        emit IntentApproved(leaf, executorSection.length);
    }

    /**
     * @notice Revoke a posted intent. Terminal, and allowed AFTER approval:
     *         a user may change their mind at any point before execution.
     *
     * @dev No deadline check — cancelling an expired intent is harmless and
     *      refusing it would fail a retry for nothing. Only consumption closes
     *      the door, because consumption is execution.
     */
    function cancel(bytes32 leaf, bytes calldata publicKey, bytes calldata signature) external {
        Posted storage p = _requireActionable(leaf);

        bytes32 digest =
            keccak256(abi.encode(DOMAIN_INTENT_CANCEL, block.chainid, address(this), leaf));
        _requireApprovalKey(p, publicKey, signature, digest, leaf);

        p.cancelledAt = FinalChainTime.nowMs();
        _writeStatus(leaf, p, STATUS_CANCELLED);
        emit IntentCancelled(leaf);
    }

    /// @dev Posted, not consumed, not cancelled — the states in which the
    /// approval key still has anything to say.
    function _requireActionable(bytes32 leaf) private view returns (Posted storage p) {
        p = postedOf[leaf];
        if (p.postedAt == 0) revert NotPosted(leaf);
        if (p.consumedAt != 0) revert AlreadyConsumed(leaf, p.consumedAt);
        if (p.cancelledAt != 0) revert IntentIsCancelled(leaf, p.cancelledAt);
    }

    /// @dev The committed key, and a valid ML-DSA-87 signature under it over
    /// the 32-byte digest verbatim — the same convention every quorum approval
    /// follows. An AUTO_APPROVE posting committed to nothing and has nothing
    /// to approve or cancel; its veto is the account-wide freeze.
    function _requireApprovalKey(
        Posted storage p,
        bytes calldata publicKey,
        bytes calldata signature,
        bytes32 digest,
        bytes32 leaf
    ) private view {
        bytes32 commit = p.approvalKeyCommit;
        if (commit == bytes32(0)) revert NoApprovalKey(leaf);
        bytes32 got = keccak256(publicKey);
        if (got != commit) revert WrongApprovalKey(commit, got);
        if (!FinalChainPrecompiles.verifyMlDsa87(publicKey, abi.encodePacked(digest), signature)) {
            revert ApprovalSignatureInvalid(leaf);
        }
    }

    // ------------------------------------------------------ tree-7 mirror

    /// @notice The tree-7 key for posting sequence `seq` — a RING, so slots
    ///         recycle at `INTENT_SLOT_RING` and the tree can never fill.
    /// @dev Mirrored by the backend byte for byte; pinned cross-repo.
    function intentSlotKey(uint64 seq) public pure returns (bytes32) {
        return keccak256(
            abi.encodePacked(DOMAIN_INTENT_KEY, bytes32(uint256(seq % INTENT_SLOT_RING)))
        );
    }

    /// @notice The tree-7 status leaf. Everything in it is public log state —
    ///         nothing account-linked, so the tree adds searchability without
    ///         touching unlinkability. Expiry is derived from `deadline` by the
    ///         reader, never written.
    function intentStatusLeaf(
        bytes32 intentLeaf,
        uint8 status,
        uint64 postedAt,
        uint64 executeNotBefore,
        uint64 deadline,
        bytes32 targetChainRef,
        bytes32 bodyCommitment,
        uint64 seq
    ) public pure returns (bytes32) {
        return keccak256(
            bytes.concat(
                DOMAIN_INTENT_STATUS_LEAF,
                abi.encode(
                    intentLeaf,
                    status,
                    postedAt,
                    executeNotBefore,
                    deadline,
                    targetChainRef,
                    bodyCommitment,
                    seq
                )
            )
        );
    }

    /// @dev One transition, one write. The log is `treeWriter[7]`, and the
    /// tree-1 argument applies verbatim: everything this mirrors was already
    /// verified here, so no quorum belongs on top.
    function _writeStatus(bytes32 leaf, Posted storage p, uint8 status) private {
        bytes32[] memory keys = new bytes32[](1);
        bytes32[] memory statusLeaves = new bytes32[](1);
        keys[0] = intentSlotKey(p.seq);
        statusLeaves[0] = intentStatusLeaf(
            leaf,
            status,
            p.postedAt,
            p.executeNotBefore,
            p.deadline,
            p.targetChainRef,
            p.bodyCommitment,
            p.seq
        );
        trees.setLeavesAsWriter(TREE_INTENTS_ID, BRANCH_MAIN_ID, keys, statusLeaves);
    }

    /**
     * @notice Consume a posted intent as part of an anchoring append.
     *
     * @dev Called by `FinalBundleLog` in the same transaction as the append, so
     *      there is no window in which a bundle is anchored and its intents are
     *      not yet spent.
     *
     *      Consumption is permanent and is the replay gate. The execution chain
     *      keeps its own consumed-seqId set, which covers a bundle being
     *      re-executed; this covers an intent being re-anchored into a second
     *      bundle, which that set does not see.
     */
    function consume(bytes32 leaf) external {
        // Checked before the comparison, not folded into it. An unset consumer
        // is `address(0)`, and `msg.sender != consumer` would then be FALSE for
        // a caller of `address(0)` — so an unwired log would consume for the one
        // caller nobody can be, which is the kind of "unreachable" that stops
        // being unreachable the moment something else changes.
        address c = consumer;
        if (c == address(0)) revert ConsumerUnset();
        if (msg.sender != c) revert NotConsumer(msg.sender);
        Posted storage p = postedOf[leaf];
        if (p.postedAt == 0) revert NotPosted(leaf);
        if (p.consumedAt != 0) revert AlreadyConsumed(leaf, p.consumedAt);
        if (p.cancelledAt != 0) revert IntentIsCancelled(leaf, p.cancelledAt);
        uint64 nowMs = FinalChainTime.nowMs();
        if (nowMs > p.deadline) revert Expired(leaf, p.deadline);
        // Scheduling is enforced HERE, which is what makes the funding and fee
        // re-checks execution-time checks for free: admission cannot happen
        // before the moment the intent named — less the lead, so the wrapper
        // that follows admission can land AT that moment rather than minutes
        // after it (`SCHEDULE_LEAD_MS`).
        if (nowMs + _scheduleLeadMs(p) < p.executeNotBefore) {
            revert TooEarly(leaf, p.executeNotBefore, nowMs);
        }
        // The read-back gate. Neither approved nor auto means the user has not
        // seen on chain what is about to execute.
        if (p.approvedAt == 0 && p.flags & FLAG_AUTO_APPROVE == 0) revert NotApproved(leaf);
        p.consumedAt = nowMs;
        _writeStatus(leaf, p, STATUS_CONSUMED);
        emit IntentConsumed(leaf, msg.sender);
    }

    /// @notice The stored envelope. One `eth_call`, no range, any age.
    ///
    /// @dev A getter rather than a public mapping so the three parts come back
    ///      in one call — a caller pairing a header from one read with a
    ///      ciphertext from another would be pairing across a posting that
    ///      landed between them.
    function envelopeOf(bytes32 leaf)
        external
        view
        returns (bytes memory header, bytes memory ciphertext, bytes memory executorSection)
    {
        Envelope storage e = _envelopeOf[leaf];
        return (e.header, e.ciphertext, e.executorSection);
    }

    /// @notice Is `leaf` posted, unspent, uncancelled and unexpired right now?
    function isOpen(bytes32 leaf) external view returns (bool) {
        Posted storage p = postedOf[leaf];
        return p.postedAt != 0 && p.consumedAt == 0 && p.cancelledAt == 0
            && FinalChainTime.nowMs() <= p.deadline;
    }

    /// @notice Would `consume` accept `leaf` right now? Open, due, and either
    ///         approved or AUTO_APPROVE — the work-queue predicate, so a solver
    ///         asks the same question the contract answers.
    function isConsumable(bytes32 leaf) external view returns (bool) {
        Posted storage p = postedOf[leaf];
        uint64 nowMs = FinalChainTime.nowMs();
        return p.postedAt != 0 && p.consumedAt == 0 && p.cancelledAt == 0 && nowMs <= p.deadline
            && nowMs + _scheduleLeadMs(p) >= p.executeNotBefore
            && (p.approvedAt != 0 || p.flags & FLAG_AUTO_APPROVE != 0);
    }

    /// @dev The lead `consume` grants a posting: its target chain's
    ///      (`scheduleLeadMsOf`) for a scheduled one, nothing for an immediate
    ///      one (there is no moment to lead).
    function _scheduleLeadMs(Posted storage p) private view returns (uint64) {
        return p.executeNotBefore == 0 ? 0 : scheduleLeadMsOf(p.targetChainRef);
    }

    // ------------------------------------------------------------------ sweep

    /// @dev This contract's configuration gate reads the membership registry it
    /// was constructed against, so the sweep authority reads the same one.
    function _sweepRegistry() internal view override returns (FinalIdentityRegistry) {
        return registry;
    }

    /// @dev The recorded forfeit destination, and the proven authority that
    /// called. `bondForfeitTo` is the one address this contract already names
    /// as somewhere its native value legitimately goes, so a rescue pays it
    /// rather than inventing a second one. Zero while no bond is armed — the
    /// shipped state — and `FinalSweep` refuses a zero destination, so the pair
    /// collapses to the caller until a forfeit destination exists.
    function _sweepDestinations() internal view override returns (address, address) {
        return (bondForfeitTo, msg.sender);
    }

    /**
     * @dev The outstanding bonds are the liability, and the whole of it.
     *
     * A posted bond is native this contract is holding FOR the poster: refunded
     * by `claimBond` once the intent is anchored, forfeited by `forfeitBond`
     * once its deadline passes without anchoring. Either way it is somebody
     * else's wei until it settles, and a sweep that could take it would let a
     * registrar collect the spam deposit of every intent still in flight.
     * `reservedBondWei` also keeps holding the bonds forfeited to a zero
     * destination, which are burned in place and must stay burned.
     *
     * Nothing else here is owed: intents are records, not custody, and this
     * contract has no other payable entrypoint — so a foreign token, an NFT or
     * native beyond the bond total is stray and sweepable in full.
     */
    function _sweepReserved(SweepKind kind, address, uint256) internal view override returns (uint256) {
        return kind == SweepKind.Native ? reservedBondWei : 0;
    }
}

contracts/finalchain/FinalPlaneSweep.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may inherit this mixin from a contract deployed as
//    part of a Final DeFi Protocol state plane, and may operate the asset-rescue
//    surface it completes.
// 2. Integrators, indexers and operators may call the resulting rescue surface
//    where the state plane's own configuration authority permits it, and may
//    read the authority and destination answers it gives.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this mixin or a competing state-plane rescue
//    authority without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

import {FinalSweep} from "../utils/FinalSweep.sol";
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";

/**
 * @title Final Plane Sweep
 * @notice The authority and destination halves of the shared asset-rescue surface, answered once for every
 *         contract of the protocol's own state plane.
 * @dev `FinalSweep` gives every contract that can end up holding a stray asset one rescue surface and leaves two
 *      questions for the inheritor: who may call it, and where the value may go. Every contract on this state
 *      plane answers both the same way — the registry's bootstrap admin alone while that window is open, and the
 *      sealed registrar authority afterwards — and stating that once per contract would be one chance per
 *      contract to state it differently. An inheritor of this mixin answers a single question instead: which
 *      registry is mine.
 *
 *      **The authority is the plane's own configuration gate, narrowed to what a fixed signature can carry.**
 *      The sealed half of that gate is a K-of-N over the registrar role, and its approvals arrive in CALLDATA.
 *      The rescue entrypoint's signature is shared across every contract on the plane and cannot grow a
 *      per-contract quorum argument, so what survives into a no-argument `internal view` is MEMBERSHIP: the
 *      bootstrap admin while the window is open, and afterwards any account the registry currently attests as a
 *      live registrar.
 *
 *      That is a narrowing — one registrar rather than K of them — and it is deliberate rather than overlooked.
 *      Two other gates make it safe, and a registrar can widen neither:
 *
 *        - a rescue moves SURPLUS only. Every contract that owes something declares the debt as a reservation,
 *          and no key reaches behind that line: an intent log's bonds, a billing plane's prepaid credit and a gas
 *          well's entire float are all unreachable by this surface however it is called.
 *        - the destination is not the caller's to invent.
 *
 *      A registrar already configures tree writers, thresholds and consumers. An account that can decide who may
 *      write the account tree is not meaningfully restrained from moving a stray token, so demanding a quorum
 *      ceremony for the rescue lane would buy nothing and would instead guarantee the lane is never used when it
 *      is needed. No new role and no new authority pointer is introduced here: the registrar role is the
 *      registry's own, and membership in it moves in the registry rather than in any contract that reads it.
 *
 *      **The destination is the authority that ordered the rescue.** This state plane has no treasury pointer,
 *      and adding one would be exactly the new authority this mixin is not allowed to invent — a per-contract
 *      treasury setter would need its own quorum action on every contract of the plane, to configure something
 *      the plane has never needed. So the two legitimate destinations are the two addresses already proven: the
 *      bootstrap admin, and the caller.
 *
 *      The caller is not a free parameter. The rescue entrypoint proves the authority BEFORE it resolves
 *      destinations, so by the time this mixin is asked, the sender is already either the bootstrap admin or a
 *      live registrar. Every service on this chain is a Final Wallet with a registered identity and no EOA
 *      signing key, so the value lands on an account the chain itself attests to. What the gate rules out is the
 *      thing worth ruling out: a rescue paying an address the plane knows nothing about.
 *
 *      Once the bootstrap window is sealed the admin address is zero, and the base contract refuses a zero
 *      destination, so the pair collapses to the caller alone — one legitimate destination, which is the case the
 *      base contract already handles.
 */
abstract contract FinalPlaneSweep is FinalSweep {
    /// @notice The membership registry an inheriting contract's configuration gate reads.
    /// @dev The one question this mixin leaves open, and the only line an inheritor has to supply. It exists
    ///      because some contracts of the plane hold the registry directly while others reach it through another
    ///      contract they already hold, and both must resolve to the SAME registry their configuration answers
    ///      to — a rescue authority read from a different source would be a second authority in disguise.
    /// @return The registry whose bootstrap admin and registrar membership decide this contract's rescue
    ///         authority and destinations.
    function _sweepRegistry() internal view virtual returns (FinalIdentityRegistry);

    /// @notice The plane's configuration gate, in the caller-only form the shared rescue surface can express.
    /// @dev Two accepting branches, checked in order: the bootstrap admin while the window is open, and any live
    ///      registrar once it is sealed. The bootstrap branch is guarded on the seal as well as on the address,
    ///      so it closes the moment the window does rather than depending on the admin field being cleared.
    ///      Membership is read live from the registry on every call, so revoking a registrar there revokes this
    ///      authority everywhere on the plane at once. Anything else reverts.
    function _requireSweepAuthority() internal view virtual override {
        FinalIdentityRegistry reg = _sweepRegistry();
        if (!reg.bootstrapSealed() && msg.sender == reg.bootstrapAdmin()) return;
        if (reg.hasRole(msg.sender, reg.ROLE_REGISTRAR())) return;
        revert SweepUnauthorized(msg.sender);
    }

    /// @notice The two addresses a rescue on this plane may pay.
    /// @dev The bootstrap admin, and the authority that called — which the base contract has already proven by
    ///      the time this is read, so the second is never an address of the caller's choosing. After the seal the
    ///      admin half is the zero address, which the base contract refuses as a destination, leaving the proven
    ///      caller as the single legitimate target.
    /// @return The bootstrap admin, and the proven caller.
    function _sweepDestinations() internal view virtual override returns (address, address) {
        return (_sweepRegistry().bootstrapAdmin(), msg.sender);
    }
}

contracts/finalchain/FinalPqQuorum.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy and operate this quorum as part of a
//    Final DeFi Protocol chain, and may inherit it to gate an action behind a
//    post-quantum K-of-N.
// 2. Integrators, auditors, and node operators may read its membership and
//    thresholds and independently re-verify any approval it recorded, as part
//    of their integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this quorum or a competing identity or
//    authorization plane derived from it without permission prior to the
//    Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";

/**
 * @title FinalPqQuorum
 * @notice K-of-N approval where the signatures are post-quantum and the chain
 *         is what checks them.
 *
 * @dev This library is the reason Final Chain exists in this design.
 *
 * `FinalBackend/src/pq/credential.js` carries a rule it had to enforce in code
 * because nothing else could: **a surface whose signature is verified on chain
 * cannot be PQ.** A co-signer approval reaching `FinalRootAuthority` is checked
 * by ECDSA/ERC-1271 in Solidity, so a PQ co-signer would produce approvals the
 * contract cannot read, and the quorum would stop reaching threshold with
 * nothing in any log naming the cause. `PQ_SURFACE` and `assertBackendVerified`
 * exist to keep anyone from crossing that line by accident.
 *
 * Here the line is gone. The precompiles verify ML-DSA-87 and
 * SLH-DSA-SHAKE-256s natively, so a quorum can be PQ *and* on chain, and
 * "the backend says these four signatures verified" becomes "these four
 * signatures verify, and any node re-derives that independently".
 *
 * ## Three rules, each closing a specific hole
 *
 * 1. **Keys come from the registry, never from calldata.** A key passed as an
 *    argument proves nothing — anyone with a keypair can sign under it. This is
 *    the difference between a 4-of-5 quorum and a 1-of-1 held by whoever built
 *    the transaction.
 *
 * 2. **Signers strictly ascending.** One comparison per entry rejects duplicates
 *    outright, so a single member cannot supply four approvals and satisfy a
 *    threshold of four. The alternative — an O(n²) seen-check — is the same
 *    guarantee with more ways to get it wrong.
 *
 * 3. **The digest binds chain id and verifying contract.** Without both, an
 *    approval collected for one contract is replayable against another with the
 *    same payload shape, and an approval from the test chain is replayable on
 *    the production one. These co-signers hold one key across environments.
 *
 * ## Which algorithm
 *
 * The stack splits its keys by hardness assumption, not by convenience:
 * ML-DSA-87 (lattice) signs transactions, SLH-DSA-SHAKE-256s (hash-based) signs
 * identity. Two families, so one cryptanalytic result cannot take both.
 *
 * So an action inherits the class of what it authorizes. Advancing a state root
 * is operational and high-cadence: transaction class. Registering or revoking
 * an identity is the thing the access class exists for. `ALG_ANY` is available
 * and should be used sparingly — accepting either means a break in one family
 * takes the quorum.
 *
 * An action that authorizes EXECUTION takes both: the ML-DSA-87 approval and a
 * `seal`, an SLH-DSA-SHAKE-256s signature over the same digest by the member's
 * `activeSeal` key. Neither family alone can then move funds, and the seal key
 * is its own slot — never the access key — so the process that seals cannot
 * also rotate the identity it seals for.
 *
 * Every digest binds an `anchorBlock`: the block at which the members read
 * tree 1 to decide who is in the round. Binding it means every approval in a
 * round was made against ONE roster view, and the window in `require_` means a
 * view older than `ANCHOR_WINDOW` blocks is refused rather than honoured.
 *
 * The practical cost is worth stating: an SLH-DSA signature is 29,792 bytes, so
 * a 4-of-5 access-class quorum is ~119 KB of calldata. That is affordable here
 * only because this is our own chain. Do not carry this pattern to a chain
 * where it is not.
 */
library FinalPqQuorum {
    /// @notice ML-DSA-87 — FIPS 204. Algorithm ids are the FIPS numbers: the
    /// same ids `FinalCertificate` and the backend registry use, and the numbers
    /// the precompile addresses end in (`0x0204`).
    uint8 internal constant ALG_ML_DSA_87 = 4;
    /// @notice SLH-DSA-SHAKE-256s — FIPS 205 (`0x0205`).
    uint8 internal constant ALG_SLH_DSA_SHAKE_256S = 5;
    /// @notice Either scheme is acceptable for this action.
    uint8 internal constant ALG_ANY = 0;

    /// @notice How far behind the chain head an approval's anchor may sit.
    /// @dev Members evaluate roster membership against tree 1 AT the anchor
    /// block. 600 blocks is ten minutes at the chain's one-second cadence —
    /// generous against a round that takes seconds, and short enough that a
    /// roster rotated away is refused rather than counted.
    uint64 internal constant ANCHOR_WINDOW = 600;

    /// @dev Domain separator for every quorum digest. Distinct from any
    /// EIP-712 domain in the stack: these are not typed-data signatures and
    /// must not be confusable with one.
    bytes32 internal constant DOMAIN_PQ_QUORUM = keccak256("FINAL_CHAIN_PQ_QUORUM_v01");

    /// @notice One member's approval.
    struct Approval {
        /// The member's account, which is also the key it is looked up by.
        address signer;
        /// `ALG_ML_DSA_87` or `ALG_SLH_DSA_SHAKE_256S`.
        uint8 algorithm;
        /// Over the 32-byte digest from `digest()`, verbatim. Both schemes
        /// hash internally, so the digest is not re-hashed before signing.
        bytes signature;
        /// SLH-DSA-SHAKE-256s over the same digest, by the member's `activeSeal`
        /// key. Required where the action authorizes execution; empty otherwise.
        bytes seal;
    }

    /// @notice Thrown when fewer valid approvals were supplied than the action requires.
    /// @param valid Approvals that verified.
    /// @param required Approvals the action demands.
    error ThresholdNotMet(uint256 valid, uint256 required);
    /// @notice Thrown when approvals are not in strictly ascending signer order.
    /// @dev Ascending order is what makes duplicate detection a single comparison instead of a quadratic scan,
    ///      so it is the rule that stops one signer being counted twice toward a threshold.
    /// @param previous The preceding signer.
    /// @param next The signer that failed to exceed it.
    error SignersNotAscending(address previous, address next);
    /// @notice Thrown when an approving signer does not hold the role this action is gated on.
    /// @param signer The approving signer.
    /// @param roleMask The role the action requires.
    error SignerLacksRole(address signer, uint256 roleMask);
    /// @notice Thrown when an approval is signed under an algorithm this action does not accept.
    /// @param signer The approving signer.
    /// @param got The algorithm the approval declared.
    /// @param required The algorithm the action demands.
    error WrongAlgorithm(address signer, uint8 got, uint8 required);
    /// @notice Thrown when an approval's signature fails verification in the precompile.
    /// @param signer The approving signer.
    /// @param algorithm The algorithm it was verified under.
    error BadSignature(address signer, uint8 algorithm);
    /// @notice Thrown when an approval's access seal fails verification.
    /// @param signer The approving signer.
    error BadSeal(address signer);
    /// @notice Thrown when an approval anchors to a block this chain has not reached.
    /// @param anchorBlock The block the approval anchored to.
    /// @param blockNumber The current block.
    error AnchorAhead(uint64 anchorBlock, uint256 blockNumber);
    /// @notice Thrown when an approval's anchor is older than the accepted window.
    /// @dev Bounding the window is what stops an approval collected once being replayed indefinitely later.
    /// @param anchorBlock The block the approval anchored to.
    /// @param blockNumber The current block.
    error AnchorStale(uint64 anchorBlock, uint256 blockNumber);
    /// @notice Thrown when an action is gated on a threshold of zero.
    /// @dev Refused rather than treated as "no approvals needed": a zero threshold is always a
    ///      misconfiguration, and reading it as permissive would silently remove the quorum.
    error ThresholdIsZero();

    /**
     * @notice The message every member of this quorum signs.
     * @param verifyingContract The contract consuming the approvals. Binding it
     *        stops an approval collected for one contract being replayed
     *        against another with the same payload shape.
     * @param actionDomain What is being authorized — a per-action constant, so
     *        an approval for "advance the accounts tree" cannot be replayed as
     *        one for "revoke an identity".
     * @param anchorBlock The Final Chain block the members read tree 1 at to
     *        decide the roster. Bound here so every approval in a round names
     *        the same view; checked against `ANCHOR_WINDOW` by `require_`.
     * @param payloadDigest The action's own committed content. Callers MUST
     *        include a nonce or a monotonic counter in it; nothing here can
     *        tell a replay of round 7 from a fresh round 7.
     */
    function digest(
        address verifyingContract,
        bytes32 actionDomain,
        uint64 anchorBlock,
        bytes32 payloadDigest
    ) internal view returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_PQ_QUORUM,
                block.chainid,
                verifyingContract,
                actionDomain,
                anchorBlock,
                payloadDigest
            )
        );
    }

    /**
     * @notice Reverts unless at least `threshold` distinct members holding
     *         `roleMask` have signed `quorumDigest`.
     * @param registry Where public keys and roles come from. Not a parameter
     *        for flexibility — a parameter so the caller's own immutable
     *        registry address is what is used, rather than one from calldata.
     * @param requiredAlgorithm `ALG_ANY` to accept either scheme.
     * @param anchorBlock The anchor the digest was built over. Refused if it is
     *        ahead of this block or more than `ANCHOR_WINDOW` behind it.
     * @param requireSeal Whether every approval must also carry a valid `seal`
     *        by the member's `activeSeal` key — the execution class.
     * @return valid The number of approvals that verified, which is at least
     *         `threshold` if this returns at all.
     *
     * @dev Every failure reverts with the offending signer named. A quorum that
     * silently skipped bad approvals and counted the rest would let a
     * misconfigured co-signer sit broken indefinitely: the threshold would keep
     * being met by the others and nothing would say one member had stopped
     * contributing. That is exactly the failure this program has already had,
     * in `fanOut`, where a per-chain advance failure was recorded and execution
     * continued.
     */
    function require_(
        FinalIdentityRegistry registry,
        Approval[] calldata approvals,
        bytes32 quorumDigest,
        uint256 roleMask,
        uint256 threshold,
        uint8 requiredAlgorithm,
        uint64 anchorBlock,
        bool requireSeal
    ) internal view returns (uint256 valid) {
        if (threshold == 0) revert ThresholdIsZero();
        if (anchorBlock > block.number) revert AnchorAhead(anchorBlock, block.number);
        if (block.number - anchorBlock > ANCHOR_WINDOW) revert AnchorStale(anchorBlock, block.number);

        bytes memory message = abi.encodePacked(quorumDigest);
        address previous = address(0);

        uint256 n = approvals.length;
        for (uint256 i = 0; i < n; i++) {
            Approval calldata a = approvals[i];

            // Strictly ascending. `address(0)` as the initial value works
            // because it can never be a registered signer.
            if (a.signer <= previous) revert SignersNotAscending(previous, a.signer);
            previous = a.signer;

            if (!registry.hasRole(a.signer, roleMask)) revert SignerLacksRole(a.signer, roleMask);

            if (requiredAlgorithm != ALG_ANY && a.algorithm != requiredAlgorithm) {
                revert WrongAlgorithm(a.signer, a.algorithm, requiredAlgorithm);
            }

            if (!_verify(registry, a, message)) revert BadSignature(a.signer, a.algorithm);
            if (requireSeal && !_verifySeal(registry, a, message)) revert BadSeal(a.signer);

            valid++;
        }

        if (valid < threshold) revert ThresholdNotMet(valid, threshold);
    }

    /// @notice Non-reverting form, for views and for callers that want to
    /// report rather than refuse.
    function count(
        FinalIdentityRegistry registry,
        Approval[] calldata approvals,
        bytes32 quorumDigest,
        uint256 roleMask,
        uint8 requiredAlgorithm,
        uint64 anchorBlock,
        bool requireSeal
    ) internal view returns (uint256 valid) {
        if (anchorBlock > block.number || block.number - anchorBlock > ANCHOR_WINDOW) return 0;
        bytes memory message = abi.encodePacked(quorumDigest);
        address previous = address(0);
        uint256 n = approvals.length;
        for (uint256 i = 0; i < n; i++) {
            Approval calldata a = approvals[i];
            if (a.signer <= previous) return valid;
            previous = a.signer;
            if (!registry.hasRole(a.signer, roleMask)) continue;
            if (requiredAlgorithm != ALG_ANY && a.algorithm != requiredAlgorithm) continue;
            if (!_verify(registry, a, message)) continue;
            if (requireSeal && !_verifySeal(registry, a, message)) continue;
            valid++;
        }
    }

    /// @dev The seal: SLH-DSA-SHAKE-256s by the member's `activeSeal` key over
    /// the same digest. A member with no seal key on record cannot seal, and an
    /// approval with no seal bytes is not one.
    function _verifySeal(
        FinalIdentityRegistry registry,
        Approval calldata a,
        bytes memory message
    ) private view returns (bool) {
        bytes memory key = registry.activeSealKeyOf(a.signer);
        if (key.length == 0 || a.seal.length == 0) return false;
        return FinalChainPrecompiles.verifySlhDsa(key, message, a.seal);
    }

    /// @dev Verifies one approval against the key the REGISTRY holds for that signer, never against a key
    ///      supplied in the approval. A key passed as an argument proves nothing, because anyone holding a
    ///      keypair can sign under it; reading from storage is what makes the verdict re-derivable from public
    ///      state rather than a claim by whoever assembled the call.
    /// @param registry The identity registry that holds each signer's live keys.
    /// @param a The approval being verified.
    /// @param message The exact bytes the approval must cover.
    /// @return valid True when the signature verifies under the signer's live key for the declared algorithm.
    function _verify(
        FinalIdentityRegistry registry,
        Approval calldata a,
        bytes memory message
    ) private view returns (bool) {
        // The LIVE pair, always. The recovery pair authorizes rotating this
        // account's own credentials and NOTHING else — a quorum that accepted
        // it would hand the recovery keys everyday authority, which is exactly
        // the separation the two stages exist to draw.
        if (a.algorithm == ALG_ML_DSA_87) {
            return FinalChainPrecompiles.verifyMlDsa87(
                registry.activeTransactionKeyOf(a.signer), message, a.signature
            );
        }
        if (a.algorithm == ALG_SLH_DSA_SHAKE_256S) {
            return FinalChainPrecompiles.verifySlhDsa(
                registry.activeAccessKeyOf(a.signer), message, a.signature
            );
        }
        // Any other id is a refusal, never a default — including the KEM ids
        // (3, 7) and the reserved FN-DSA id (6), none of which is a signature
        // scheme this quorum verifies.
        return false;
    }
}

contracts/finalchain/FinalStateTrees.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy this state-tree contract as the state
//    plane of a Final DeFi Protocol chain, and may operate that chain.
// 2. Integrators, indexers, operators and end users may read every tree, take
//    inclusion proofs, branch roots, tree roots and round roots from it, and
//    write into a tree they hold the quorum, the writer seat or the
//    configuration authority for, as part of their integration with the Final
//    DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this state-tree contract or a competing state
//    plane derived from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
import {FinalChainTime} from "./FinalChainTime.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalPlaneSweep} from "./FinalPlaneSweep.sol";

/// @title Chain Source
/// @notice The one question `syncIdentities` asks the asset registry.
/// @dev An interface rather than an import of `FinalAssetRegistry`, which
///      imports this file: the registry is tree 6's writer and holds the trees
///      as an immutable, so the dependency runs that way and this is the one
///      read that runs the other.
interface IChainSource {
    /// @notice Every chain reference the asset registry currently has enabled.
    /// @dev Read once per `syncIdentities` batch, so a service account's
    ///      `deployedChains` table is DERIVED from registry state instead of
    ///      being supplied by the caller. A caller-chosen table would let
    ///      anyone place a service identity on a chain of their choosing,
    ///      which is why the projection reads and never accepts.
    /// @return The enabled chain references, in the registry's own order.
    function enabledChainRefs() external view returns (bytes32[] memory);
}

/// @title Slot Key Source
/// @notice The one question {FinalStateTrees.syncSlotKeyLeaves} asks the
///         slot-key registry: the leaf value for one member's slot — the
///         registry's own verdict, zero when the slot holds nothing usable.
interface ISlotKeySource {
    /// @notice The leaf value one member's slot-key ring position carries.
    /// @dev The registry decides; this contract only copies. Zero is the
    ///      answer for a slot that never held a key and for one whose window
    ///      has passed, so re-projecting a lapsed slot retires its leaf.
    /// @param member The co-signer whose slot key is being read.
    /// @param slotIndex The slot the key belongs to, before the ring modulus.
    /// @return The registry's leaf value, or zero when the slot holds nothing usable.
    function slotKeyLeafOf(address member, uint64 slotIndex) external view returns (bytes32);
}

/// @title Endpoint Source
/// @notice The one question {FinalStateTrees.syncEndpointLeaves} asks the
///         endpoint registry: the leaf value for one tunnel endpoint — the
///         registry's own verdict (certificate hash, status, expiry, region),
///         zero when nothing is registered under the id.
interface IEndpointSource {
    /// @notice The leaf value one tunnel endpoint carries.
    /// @dev The registry admitted the certificate under its own quorum with
    ///      the holder's proof of possession, so this read carries a verdict
    ///      rather than a claim. Zero means nothing stands under the id.
    /// @param endpointId The endpoint's certificate subject key id.
    /// @return The registry's leaf value, or zero when nothing is registered under the id.
    function endpointLeafOf(bytes32 endpointId) external view returns (bytes32);
}

/**
 * @title Final State Trees
 * @notice Final Chain's state plane: eight fixed-depth Merkle trees, and the rounds that publish all
 *         eight of their roots as one contemporaneous snapshot.
 *
 * @dev This contract runs on the project's own reth-based chains and nowhere else. Every signer is
 * resolved through an identity registry that verifies post-quantum signatures in precompiles those chains
 * alone provide, so a deployment anywhere else cannot authorize a single write. Nothing under
 * `contracts/` outside the Final Chain directory imports it, and it takes part in no CREATE2 derivation —
 * its address is whatever its deploy transaction produced, never a mined constant that other code pins.
 * Gas is deliberately NOT a design constraint here and must not be optimised for: full sibling paths are
 * stored, every branch enumerates on chain, and a configuration row keeps its value beside its hash,
 * precisely so that no reader ever has to rebuild anything off chain to be sure of it.
 *
 * **Immutable, and behind no proxy.** There is no upgrade path and no authority that can replace this
 * code. Any change to the surface below is a REDEPLOY at a new address, and everything holding the old
 * address — the account ledger, the registries, the records contract, every service configured against
 * it, every consumer pinning a root — is orphaned the moment that happens and has to be repointed. The
 * registry projections into trees 1 and 8 do not travel with a redeploy either: they are derived from the
 * registry, so a fresh deployment re-derives them rather than migrating anything.
 *
 * ## What each tree carries
 *
 * One tree per domain, because they change at unrelated cadences and a combined tree invalidates every
 * outstanding proof on every tick:
 *
 * | # | tree | holds | cadence |
 * |---|---|---|---|
 * | 1 | accounts | every Final Wallet's public state | per rotation / creation |
 * | 2 | phi | the PHI record: per (wallet, chain) balances, the lock, exposures | per publisher round |
 * | 3 | vasset | issued vAsset supply and backing, per (asset, chain) | per settlement |
 * | 4 | oracle | published prices and their inputs | ~10 s; 1 s for morph and fee assets |
 * | 5 | settlement | chain and asset registry roots | rarely |
 * | 6 | allowlist | assets, chains, policy, price sources, DEX deployments | rarely |
 * | 7 | intents | intent status, ring-keyed over the posting sequence | per posting |
 * | 8 | identity | the wallet-creation admission set, projected from the registry | per identity mutation |
 *
 * ## Tree 1 is READ, never rebuilt
 *
 * Tree 1 is a Final Wallet's public state and the SOURCE OF TRUTH every execution chain projects from.
 * The sanctioned way to ask it a question is {proofFor} for the sibling path and {liveRoot} for the root
 * each chain republishes — {branchProofFor} with {branchRoot} to prove against a branch instead,
 * {roundProofFor} with {roundRootAt} to prove against a published round. Those entrypoints are the whole
 * interface, and their answers are the only ones that verify.
 *
 * Do NOT fold the same leaves off chain. This tree is FIXED DEPTH — `DEPTH` levels, with a branch subtree
 * at `BRANCH_DEPTH` — zero-padded to that depth, and INSERTION-ORDERED: a key keeps the slot it was first
 * handed, permanently, and empty slots hash as the empty subtree rather than being skipped. A rebuild
 * that sorts its leaves, or sizes itself `log2(n)` to the number of leaves present, is a DIFFERENT tree.
 * Its root is not this root, no proof against it verifies anywhere, and nothing in the failure names the
 * cause: the execution chain simply refuses a proof that looks perfectly well formed.
 *
 * ## Who may write which tree
 *
 * Four kinds of door, and every tree sits on exactly one of the first three:
 *
 * - **A service quorum.** {setLeaves} for trees 5 and 6, {setAccountStates} for tree 1: at least
 *   `threshold[treeId]` approvals from members holding `writerRole[treeId]`, each an ML-DSA-87 vote over
 *   a digest binding the tree, its nonce and the whole batch. Tree 1's round additionally carries each
 *   member's SLH-DSA seal, because a leaf there states who an account IS on every chain.
 * - **A typed writer.** Trees 2, 3 and 4 are reachable only through {writeTyped}, from the records
 *   contract, which holds the preimage behind each leaf and computes the hash from it. {setLeaves}
 *   refuses those three outright, so a stored value can never drift from the commitment beside it.
 * - **A writer contract.** `treeWriter[treeId]` writes its tree with no quorum at all: the account ledger
 *   for tree 1, the intent log for tree 7, the ledger again for tree 8's user admissions. Trees 7 and 8
 *   have no quorum path whatsoever — {setLeaves} refuses both.
 * - **The configuration authority.** Branch 0 of every tree through {setConfig}, plus the pointers,
 *   rosters and thresholds themselves. Never a tree's own writer or quorum: what a service states is not
 *   authority over how that service is configured.
 *
 * `treeWriter[1]` being the account ledger, with no service quorum layered on top, is the design and not
 * a gap. A writer contract is not a key: its rules are its bytecode, it has no owner and no proxy, and it
 * authorizes every transition by verifying the ACCOUNT HOLDER'S own SLH-DSA credential against the
 * commitment this chain holds. That is stronger evidence than a K-of-N of our own services attesting to
 * what they read. A quorum on top would be strictly worse than nothing — it would let operators withhold
 * approval from a user rotating a stolen key, which is a censorship power over the exact operation the
 * account plane exists to make possible.
 *
 * ## Seeding the chain and asset trees
 *
 * Trees 5 and 6 are the two a fresh plane cannot infer. Tree 5 carries the settlement chain and asset
 * registry roots; tree 6 carries the allowlist those roots stand over — supported chains, supported
 * assets, policy, price sources, DEX deployments. Both are quorum-written, and both are expected to be
 * SEEDED before the plane is usable: an execution chain copies its chain set and its asset set from these
 * roots, so an unseeded pair means every settlement toward a chain is refused at the source and no vAsset
 * ever registers. A test plane seeds the test chains; a production plane seeds the production chains and
 * their assets. `chainSource` belongs in the same window, because `syncIdentities` derives a service
 * account's `deployedChains` table from the enabled chain set, and an unset source quietly produces
 * service leaves that exist on Final Chain alone.
 *
 * The bootstrap ordering is load bearing in one more place: {configureTree} refuses a threshold no live
 * roster can meet, so members are registered first and trees configured after. A plane whose trees were
 * never configured accepts no quorum write at all while looking perfectly healthy from outside.
 *
 * ## The hash shape is not a choice
 *
 * Leaves hash as `keccak256(0x00 ‖ leaf)` and internal nodes as
 * `keccak256(0x01 ‖ lo ‖ hi)` with the pair sorted. That is
 * `FinalMerkle.verifyTaggedSortedProof`, verbatim, which is what
 * `FinalWalletFactory.syncAccountState` and `FinalSettlement` already run on
 * every supported chain. A proof produced here is consumed there with no
 * translation and no contract change, and tree 1's leaf preimage is exactly
 * `FinalWalletFactory.accountStateLeafHash` — same fields, same order, the
 * `deployedChains` table `abi.encode`d like every other field.
 *
 * Getting this wrong is not a compile error anywhere. It is a root every chain
 * silently rejects, with nothing pointing at the cause.
 *
 * ## Positional slots under a sorted-pair tree
 *
 * Sorted pairs make a proof position-agnostic, which is why it carries no
 * direction bits. That does not stop the TREE from being positional, and here
 * it is: every key gets a permanent slot, so a single leaf update is `DEPTH`
 * hashes instead of a rebuild over every leaf. The verifier neither knows nor
 * needs to know that a slot exists.
 *
 * ## Branches
 *
 * The slot space of every tree is cut into `BRANCH_COUNT` branches by the top
 * `BRANCH_BITS` of the slot: a branch is a subtree with a permanent place, its
 * root is one internal node, and a leaf's path to the tree root passes through
 * it. Branches hold what belongs to the same domain but not to the same rows
 * — branch 0 is the owning service's CONFIGURATION on every tree, tree 8 adds
 * the owner → wallets index and the co-signers' slot keys beside the admission
 * set — and they are chosen over more trees because a branch shares its
 * tree's authority doors and writer, while a tree would need its own. A leaf
 * proves against its branch root with `BRANCH_DEPTH` siblings, against the
 * tree root with `DEPTH`, against the round root with `ROUND_DEPTH`: one path,
 * cut at three heights, one verifier.
 *
 * ## Rounds, and why the live roots are not the product
 *
 * `setLeaves` moves a tree. It does not publish one. A consumer that fetched
 * eight roots one at a time would get a price proof from one moment and a
 * roster proof from another, and something delisted in between would still
 * verify.
 *
 * `publishRound` snapshots all eight together, and folds them into ONE round
 * root — the tree roots as the level-`DEPTH` nodes of a depth-`ROUND_DEPTH`
 * tree, tree `t` at position `t` — so a single word commits to the whole
 * plane and any leaf in it proves against that word with four more siblings.
 * A round is the unit a consumer pins, and it is the only thing this contract
 * promises is contemporaneous. The execution chains keep anchoring per-tree
 * roots (identity, account state, registry roots): those must move at their
 * own cadence, not at the oracle's.
 */
contract FinalStateTrees is FinalPlaneSweep {
    // ---------------------------------------------------------------- trees

    /// @notice Every Final Wallet's public state. The source of truth other
    /// chains copy through `syncAccountState`.
    uint8 public constant TREE_ACCOUNTS = 1;
    /// @notice The PHI record, per `(wallet, chain)`: balances, the lock, its
    /// terms, the exposures carved from it and the accrual between reconciliations.
    uint8 public constant TREE_PHI = 2;
    /// @notice vAsset supply and backing.
    uint8 public constant TREE_VASSET = 3;
    /// @notice Oracle prices and their inputs.
    uint8 public constant TREE_ORACLE = 4;
    /// @notice Settlement chain and asset registry roots.
    uint8 public constant TREE_SETTLEMENT = 5;
    /// @notice Which assets and chains are supported.
    uint8 public constant TREE_ALLOWLIST = 6;
    /// @notice Intent status, keyed by a RING over the posting sequence.
    /// @dev The search structure beside `FinalBundleLog`'s permanent record.
    /// Written only by `FinalIntentLog` through `treeWriter[7]` — the tree-1
    /// argument verbatim: the log verified the bond, the commitment, the
    /// approval and the consume itself, and a service quorum on top would be a
    /// censorship point over posting. Slots are permanent and intents are
    /// unbounded flow, so the log recycles keys modulo `CAPACITY`: the tree is
    /// an index with a ~1M-posting retention window, never the record.
    uint8 public constant TREE_INTENTS = 7;
    /// @notice The wallet-creation admission set — the identity leaves
    /// (`keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)`) every execution
    /// chain's gateway verifies certificates against.
    /// @dev The root the gateways anchor as `currentIdentityRoot`, CONTINUOUS
    /// over this tree: an admission or a revocation is live the moment it
    /// lands here, with no off-chain folding step standing between the two.
    /// Two feeders, one per identity plane, and NO quorum door for either:
    ///
    /// - SERVICE identities: {syncIdentityLeaves}, the permissionless
    ///   projection of `FinalIdentityRegistry`'s own verdict — the registry
    ///   calls it same-tx on every identity mutation, and anyone may call it
    ///   to retire a leaf whose standing lapsed by TIME (expiry moves no
    ///   registry storage, so only a projection pass can zero it).
    /// - USER identities: `treeWriter[8]` — `FinalAccountLedger`, which
    ///   computes the leaf from the genesis certificate fields it verified
    ///   under its opener quorum and writes it once at `openAccount`. A user
    ///   admission leaf is permanent by construction: the certificate IS the
    ///   address, rotation never changes it, and a post-rotation creation on
    ///   a new chain reads PUBLISHED account state out of tree 1, never the
    ///   certificate's genesis keys.
    ///
    /// A quorum of service signatures must not be able to state an identity
    /// neither ruler decided, so `setLeaves` refuses this tree outright.
    uint8 public constant TREE_IDENTITY = 8;
    /// @notice Count, for iteration. Trees are 1-indexed; 0 is not a tree.
    /// @notice Tree 9 — compliance: the approved set (branch 1), revocations (2), per-jurisdiction
    ///         counters (3) and minutes-lived action attestations (4); branch 0 pins the jurisdiction
    ///         policy in force and the attestation life. Typed-only: `FinalStateRecords` writes it under
    ///         the REGISTRAR quorum (an attestation is an admission) through `writeTypedInBranch`, and
    ///         the presale ledger mirrors its counters through the same companion; no `setLeaves` door
    ///         — no set of service signatures may attest what the provider and the screening did not
    ///         decide. Leaves are `FinalComplianceLeaves`; nothing in them names a person.
    uint8 public constant TREE_COMPLIANCE = 9;
    /// @notice Number of trees. The round root has room for 2**FOREST_BITS; a new tree is a redeploy.
    uint8 public constant TREE_COUNT = 9;

    /// @notice Tree height: 2^`DEPTH` slots per tree, laid out as 16 BRANCHES
    /// of 2^20. The top `BRANCH_BITS` of a slot name the branch, the rest its
    /// position inside it.
    /// @dev FIXED, and baked into every root this contract produces. A tree is
    /// padded to this height with the empty-subtree hash whether it holds one
    /// leaf or a million, which is why an off-chain rebuild must use this
    /// depth verbatim: a `log2(n)` tree over the same leaves is a different
    /// tree and proves nothing here. Raising it is a migration and not a
    /// parameter change — every outstanding proof and every root anchored on
    /// another chain would have to be replaced in the same instant.
    uint256 public constant DEPTH = 24;
    /// @notice How many of a slot's top bits name the branch it lives in.
    /// @dev `BRANCH_COUNT` is `1 << BRANCH_BITS` and `BRANCH_DEPTH` is
    /// `DEPTH - BRANCH_BITS`; the three move together, or the branch a slot
    /// belongs to stops matching the subtree its proof passes through.
    uint256 public constant BRANCH_BITS = 4;
    /// @notice Branches per tree. Ids run `0 .. BRANCH_COUNT - 1`.
    /// @dev Sixteen is deliberately generous: an unused branch costs only the
    /// empty-subtree hash it contributes, so a domain can grow a new family of
    /// rows without a new tree, a new writer or a new authority.
    uint8 public constant BRANCH_COUNT = 16;
    /// @notice Height of a branch: a leaf proves against its branch root with
    /// this many siblings.
    uint256 public constant BRANCH_DEPTH = DEPTH - BRANCH_BITS;
    /// @notice Slots per branch.
    /// @dev The hard ceiling `_set` enforces: a branch that runs out of slots
    /// reverts `BranchFull` rather than spilling into its neighbour, because a
    /// key in the wrong branch would prove against the wrong branch root.
    uint256 public constant BRANCH_CAPACITY = 1 << BRANCH_DEPTH;
    /// @notice Slots per tree, all branches together.
    uint256 public constant CAPACITY = 1 << DEPTH;
    /// @notice How many of the round root's levels sit above the tree roots.
    /// @dev The round root is a tree over the tree roots — position `t` holds
    /// tree `t`'s root, positions 0 and 9..15 the empty tree — folded with the
    /// same node hash. It is literally the root of a depth-`ROUND_DEPTH` tree
    /// whose level-`DEPTH` nodes are the eight tree roots, which is what lets
    /// one path prove a leaf against it.
    uint256 public constant FOREST_BITS = 4;
    /// @notice Height of the round tree: a leaf proves against a round root
    /// with this many siblings, the last `FOREST_BITS` of them from
    /// {roundProofFor}.
    uint256 public constant ROUND_DEPTH = DEPTH + FOREST_BITS;

    /// @notice Branch 0 of EVERY tree: the configuration of the service that
    /// owns the tree — key → one word, the VALUE stored so a contract on this
    /// chain reads it directly (`configValue`), the hash in the tree so it is
    /// provable wherever a round root is. Written only by {setConfig} under
    /// the configuration authority; every other door refuses the branch.
    uint8 public constant BRANCH_CONFIG = 0;
    /// @notice Branch 1 of every tree: the domain's own rows — accounts, PHI
    /// records, vAssets, prices, registry roots, the allowlist, the intent
    /// ring, the identity admission set.
    uint8 public constant BRANCH_MAIN = 1;
    /// @notice Tree 8, branch 2: the owner → wallets index. Key = the owner
    /// (`ownerIndexKeyFor`), leaf = {ownerIndexLeafHash} over the ledger's
    /// `walletsByOwner(owner)`. Written by tree 8's writer, the ledger, beside
    /// every open and every owner transfer — the tree is the search structure,
    /// the ledger holds the readable array it proves.
    uint8 public constant BRANCH_OWNER_INDEX = 2;
    /// @notice Tree 8, branch 3: the co-signers' per-slot KEM publics — a RING
    /// of `SLOT_KEY_RING` positions per member, projected from
    /// `slotKeySource` by {syncSlotKeyLeaves} exactly as identities are.
    uint8 public constant BRANCH_SLOT_KEYS = 3;
    /// @notice Tree 8, branch 4: the tunnel endpoints — the Final Node
    /// identities a wallet's FNP session terminates at. Key = the endpoint id
    /// (`endpointKeyFor`, the certificate's subject key id), leaf = the
    /// endpoint registry's verdict, projected from `endpointSource` by
    /// {syncEndpointLeaves} exactly as slot keys are. An execution chain never
    /// parses an endpoint certificate; it anchors this tree's root and a client
    /// proves the leaf against it.
    uint8 public constant BRANCH_ENDPOINTS = 4;
    /// @notice Slot-key positions per member. A slot index wraps modulo this,
    /// so the branch is an index over the recent slots and never fills; 1024
    /// members × 1024 positions is the branch exactly.
    uint64 public constant SLOT_KEY_RING = 1024;

    /// @notice The domain every tree-1 leaf is hashed under.
    /// @dev Must equal `FinalWalletFactory.DOMAIN_ACCOUNT_STATE_LEAF` byte for
    /// byte, and the leaf's fields must be encoded in the same order on both
    /// sides. A field reordered on one side only is not a compile error
    /// anywhere: it is a root every execution chain rejects, with nothing
    /// pointing at the cause.
    ///
    /// The version suffix is part of the domain, so a leaf built under a
    /// different account-state shape hashes into a different domain and cannot
    /// verify against this one by accident.
    bytes32 public constant DOMAIN_ACCOUNT_STATE_LEAF =
        keccak256("FINAL_ACCOUNT_STATE_LEAF_v02");

    /// @dev The quorum action every leaf write is approved under — {setLeaves},
    /// {setAccountStates} and {writeTyped} share it, so a member recomputes one
    /// digest whichever door a batch came through and there is no second
    /// approval shape to get wrong.
    bytes32 private constant ACTION_SET_LEAVES = keccak256("FinalStateTrees.setLeaves.v01");
    /// @notice Configuration action: set a tree's writer role and threshold.
    /// @dev Registrar-quorum actions, verified by the registry with this
    /// contract as the verifying contract. See `FinalIdentityRegistry.requireRegistrarQuorum`.
    bytes32 public constant ACTION_CONFIGURE_TREE = keccak256("FINAL_STATE_TREES_CONFIGURE_TREE_v01");
    /// @notice Configuration action: point a tree at its writer contract.
    bytes32 public constant ACTION_SET_TREE_WRITER = keccak256("FINAL_STATE_TREES_SET_TREE_WRITER_v01");
    /// @notice Configuration action: point `syncIdentities` at the chain set.
    bytes32 public constant ACTION_SET_CHAIN_SOURCE = keccak256("FINAL_STATE_TREES_SET_CHAIN_SOURCE_v01");
    /// @notice Configuration action: point tree 8's branch 3 at the slot-key registry.
    bytes32 public constant ACTION_SET_SLOT_KEY_SOURCE = keccak256("FINAL_STATE_TREES_SET_SLOT_KEY_SOURCE_v01");
    /// @notice Configuration action: point tree 8's branch 4 at the endpoint registry.
    bytes32 public constant ACTION_SET_ENDPOINT_SOURCE = keccak256("FINAL_STATE_TREES_SET_ENDPOINT_SOURCE_v01");
    /// @notice Configuration action: adopt a preceding plane's version and round counters.
    bytes32 public constant ACTION_SEED_COUNTERS = keccak256("FINAL_STATE_TREES_SEED_COUNTERS_v01");
    /// @notice Configuration action: install the records contract that writes the typed trees.
    bytes32 public constant ACTION_SET_TYPED_WRITER = keccak256("FINAL_STATE_TREES_SET_TYPED_WRITER_v01");
    /// @notice Configuration action: write rows into a tree's branch 0.
    bytes32 public constant ACTION_SET_CONFIG = keccak256("FINAL_STATE_TREES_SET_CONFIG_v01");

    /// @dev Tree-1 key domain. A full-width hash rather than the packed address
    /// it came from, which matters: an address key occupies only the low 160
    /// bits, so a hashed key colliding with one needs ~2^96 work rather than a
    /// full collision. That is expensive but not comfortable, and the
    /// consequence would be a service identity landing in a wallet's slot.
    bytes32 private constant DOMAIN_ACCOUNT_KEY = keccak256("FinalStateTrees.key.account.v01");
    /// @dev Tree-8 admission key domain, separated from the tree-1 domain for
    /// the same reason: one account's two keys must never be the same word.
    bytes32 private constant DOMAIN_IDENTITY_TREE_KEY = keccak256("FinalStateTrees.key.identity.v01");
    /// @dev Tree 8, branches 2 and 3, and branch 0 of every tree. Each is its
    ///      own domain so a key can never land in another branch's slot by
    ///      construction — `_set` refuses a key whose slot sits in a different
    ///      branch, and the domain is what makes that refusal unreachable.
    bytes32 private constant DOMAIN_OWNER_INDEX_KEY = keccak256("FinalStateTrees.key.ownerIndex.v01");
    /// @dev Tree 8, branch 3: one key per `(member, ring position)` pair.
    bytes32 private constant DOMAIN_SLOT_KEY = keccak256("FinalStateTrees.key.slotKey.v01");
    /// @dev Tree 8, branch 4: one key per tunnel endpoint id.
    bytes32 private constant DOMAIN_ENDPOINT_KEY = keccak256("FinalStateTrees.key.endpoint.v01");
    /// @dev Branch 0 of every tree: one key per `(name, sub)` configuration row.
    bytes32 private constant DOMAIN_CONFIG_KEY = keccak256("FinalStateTrees.key.config.v01");

    /// @notice Leaf domain for the owner index in tree 8, branch 2.
    /// @dev Separate from the key domain above so the leaf and the slot it
    /// occupies can never be confused for one another by a reader that has
    /// only one of the two.
    bytes32 public constant DOMAIN_OWNER_INDEX_LEAF = keccak256("FINAL_OWNER_INDEX_LEAF_v01");
    /// @notice Leaf domain for configuration rows in branch 0 of every tree.
    /// @dev The leaf binds the tree id as well as the key and value, so the
    /// same row written into two trees produces two different leaves and a
    /// proof cannot be carried from one tree's branch 0 to another's.
    bytes32 public constant DOMAIN_CONFIG_LEAF = keccak256("FINAL_CONFIG_LEAF_v01");

    // -------------------------------------------------------------- storage

    /// @notice The registry every signer is resolved through. Immutable so the
    /// quorum can never be pointed at a registry supplied in calldata.
    FinalIdentityRegistry public immutable registry;

    /// @notice Approvals required per tree.
    ///
    /// @dev Per-tree and not a scalar, because each tree is gated by a
    ///      DIFFERENT role — account co-signers, PHI, vAsset and oracle
    ///      publishers, registry publishers — so K is a property of that
    ///      tree's roster, not of the contract. All six read 2 today; that is
    ///      a deploy-time default, not an invariant, and collapsing them would
    ///      put the oracle roster's quorum on the account co-signers'.
    ///
    ///      The VALUE is a full word: it is a quantity compared against a live
    ///      member count, and every other threshold in the system is `uint256`.
    ///      The KEY is `uint8` because that is what a tree id is here — six
    ///      `uint8` constants, every parameter, every event, every error,
    ///      `_assertTree`, and the ten sibling mappings below. Widening it
    ///      would buy nothing (a narrow key is padded to 32 bytes before
    ///      hashing, so the slot is identical) and cost the getter's selector
    ///      on a contract that is live on both Final Chains.
    mapping(uint8 treeId => uint256) public threshold;
    /// @notice Role a signer must hold to write to a tree.
    mapping(uint8 treeId => uint256) public writerRole;

    /// @notice Raw (untagged) leaf value by tree and slot.
    /// @dev The tag is applied when the leaf is hashed, never when it is
    ///      stored, so what a caller wrote is what {leafOf} hands back.
    mapping(uint8 => mapping(uint256 => bytes32)) private _leaf;
    /// @notice Internal nodes, levels 1..`DEPTH`, by tree, level and index.
    /// @dev Level 0 is DERIVED from `_leaf` rather than duplicated here, so a
    ///      leaf lives in exactly one place and the two can never disagree. An
    ///      unwritten position reads zero and falls through to `_zero[level]`.
    mapping(uint8 => mapping(uint256 => mapping(uint256 => bytes32))) private _node;
    /// @notice Empty-subtree hash per level, computed once at construction.
    /// @dev Sized to the ROUND root's height, not the tree's, because the
    ///      round tree's unused positions are themselves empty trees. Built in
    ///      the constructor rather than declared as constants: it depends on
    ///      the tagging, and a constant table that drifted from the tagging
    ///      would produce roots nothing can verify, silently, since both sides
    ///      would still be internally consistent.
    bytes32[ROUND_DEPTH + 1] private _zero;

    /// @notice Permanent slot for a key, stored 1-based so 0 means unassigned.
    /// @dev The slot's top `BRANCH_BITS` are the branch the key lives in, and
    ///      the assignment is permanent: a key handed a slot keeps it for the
    ///      life of the contract. This is what makes an update `DEPTH` hashes
    ///      rather than a rebuild, and what makes the tree insertion-ordered.
    mapping(uint8 => mapping(bytes32 => uint256)) private _slotPlusOne;
    /// @notice The key a slot was handed to — the reverse of `_slotPlusOne`.
    /// @dev Lets any branch enumerate on chain ({keyAt} over
    ///      `0 .. branchSlotsUsed`) with no log window and no indexer. Costs
    ///      one extra word per NEW key, never one per update.
    mapping(uint8 => mapping(uint256 => bytes32)) private _keyAt;
    /// @notice Slots handed out per tree, all branches together.
    mapping(uint8 => uint256) public slotsUsed;
    /// @notice Slots handed out per branch — the next free position in it.
    /// @dev Per branch and not per tree, because a branch is a fixed region of
    ///      the slot space: positions are allocated from the branch's own base
    ///      so a key can never be handed a slot outside the branch it belongs
    ///      to, and `BranchFull` is raised rather than spilling into the next.
    mapping(uint8 => mapping(uint8 => uint256)) private _branchSlotsUsed;
    /// @notice The VALUE behind a configuration row (branch 0), by tree and key.
    /// @dev Kept beside the leaf hash so a contract on this chain reads the row
    ///      directly through {configValue} while the same row stays provable
    ///      off chain against a round root — one source for the fleet, the
    ///      contracts and any explorer, rather than one per reader.
    mapping(uint8 => mapping(bytes32 => bytes32)) private _configValue;

    /// @notice Live root per tree. Moves on every `setLeaves`.
    mapping(uint8 treeId => bytes32) public liveRoot;
    /// @notice Writes applied per tree, for change detection between rounds.
    mapping(uint8 treeId => uint64) public treeVersion;

    /// @notice A contemporaneous snapshot of all eight roots, and the one
    /// round root that folds them.
    struct Round {
        /// @dev Live root per tree at the instant of the snapshot, indexed by
        ///      the `TREE_*` constants. Index 0 is unused, so a tree id needs
        ///      no translation.
        bytes32[TREE_COUNT + 1] roots;
        /// @dev The single word committing to all eight — the roots folded as
        ///      the level-`DEPTH` nodes of a depth-`ROUND_DEPTH` tree.
        bytes32 roundRoot;
        /// @dev Block the snapshot was taken in, for a consumer reconciling a
        ///      round against chain history.
        uint64 blockNumber;
        /// @dev Snapshot instant in MILLISECONDS, like every instant on this
        ///      chain, so a reader never has to guess the unit.
        uint64 timestamp;
    }

    /// @notice Published rounds, 1-indexed. Round 0 is "nothing published".
    /// @dev Kept forever: a consumer pinning an old round can still fetch the
    ///      roots it verified against. Only rounds this deployment published
    ///      are here — {seedCounters} moves the counter, never the history.
    mapping(uint64 => Round) private _rounds;
    /// @notice Highest published round.
    uint64 public round;
    /// @notice Tree versions as of the last published round.
    /// @dev The change detector {publishRound} reads: a round that would carry
    ///      nothing new is refused, so the round number cannot be advanced by
    ///      anyone with gas to spend.
    mapping(uint8 => uint64) private _publishedVersion;

    /// @notice Per-tree nonce, bound into every quorum digest.
    mapping(uint8 treeId => uint64) public nonce;

    /**
     * @notice A CONTRACT allowed to write one tree without a quorum.
     *
     * @dev Exactly one per tree, and today exactly one exists: tree 1's is
     * `FinalAccountLedger`.
     *
     * This looks like a hole and is the opposite. The quorum on `setLeaves`
     * exists because a tree's writer is otherwise one key deciding what the
     * chain states. A writer contract is not a key — its rules are its
     * bytecode, it has no owner and no proxy, and tree 1's writer authorizes
     * every change by verifying the ACCOUNT HOLDER'S own post-quantum signature
     * in this chain's precompiles. That is strictly stronger evidence than a
     * K-of-N of our own services attesting to what they read.
     *
     * Keeping the quorum on top of it would be actively worse: our fleet could
     * then withhold approval from a user rotating a stolen key, which is a
     * censorship power over the exact operation the account plane exists to
     * make possible.
     *
     * The writer is set on the same bootstrap window as `configureTree` and can
     * be moved by a registrar afterwards — an immutable pointer would mean a
     * ledger upgrade abandons the tree it writes.
     */
    mapping(uint8 treeId => address) public treeWriter;

    /**
     * @notice Where `syncIdentities` reads the chain set from — the asset
     *         registry, which is also tree 6's writer.
     *
     * @dev A service identity is a Final Wallet whose address is the same on
     * every EVM chain, so its tree-1 `deployedChains` table is derivable: one
     * `(chainRef, itself)` row per chain the registry has enabled. The table
     * is DERIVED from state rather than supplied by the caller precisely so
     * that `syncIdentities` can stay permissionless — a caller-chosen table
     * would let anyone place a service identity on a chain of their choosing.
     *
     * Unset (zero) means services carry an empty table and exist on Final
     * Chain alone, which is what a plane looks like before its registry is
     * seeded. Same configuration gate as `setTreeWriter`, because pointing this
     * at a different contract changes what every service leaf says.
     */
    address public chainSource;
    /// @notice Where {syncSlotKeyLeaves} reads the co-signers' slot keys from
    ///         — the slot-key registry, whose verdict tree 8's branch 3
    ///         projects. Same configuration gate as `chainSource`; unset means
    ///         the branch cannot be written.
    address public slotKeySource;
    /// @notice The endpoint registry whose verdict tree 8's branch 4 projects.
    address public endpointSource;
    /// @notice The one contract admitted to {writeTyped}: `FinalStateRecords`,
    ///         which holds the preimages behind trees 2, 3 and 4 and computes
    ///         their keys and hashes. Same configuration gate as `treeWriter`.
    address public typedWriter;

    // --------------------------------------------------------------- events

    /// @notice A batch of leaves landed in a tree and moved its live root.
    /// @dev Emitted once per write door call, not once per leaf, and always
    ///      after the root has settled — so `newRoot` is the value {liveRoot}
    ///      answers from that block onward.
    /// @param treeId The tree that moved.
    /// @param count Leaves in the batch. Zero is possible for an empty call.
    /// @param newRoot The tree's live root after the batch.
    /// @param treeVersion The tree's write counter after the batch.
    event LeavesSet(uint8 indexed treeId, uint256 count, bytes32 newRoot, uint64 treeVersion);
    /// @notice Every tree's root was snapshotted into a new round.
    /// @param round The round number, one above its predecessor.
    /// @param blockNumber Block the snapshot was taken in.
    /// @param timestamp Snapshot instant, in milliseconds.
    event RoundPublished(uint64 indexed round, uint64 blockNumber, uint64 timestamp);
    /// @notice A tree's writer role and approval threshold were installed.
    /// @param treeId The tree configured.
    /// @param writerRole Role a signer must hold to approve a write to it.
    /// @param threshold Approvals a write needs; zero leaves the tree closed.
    event TreeConfigured(uint8 indexed treeId, uint256 writerRole, uint256 threshold);
    /// @notice A tree's quorum-free writer contract was installed or moved.
    /// @param treeId The tree whose writer changed.
    /// @param writer The contract now allowed to write it; zero removes the path.
    event TreeWriterSet(uint8 indexed treeId, address writer);
    /// @notice The contract `syncIdentities` reads the enabled chain set from was set.
    /// @param source The asset registry now consulted; zero means no chain set.
    event ChainSourceSet(address source);
    /// @notice The registry tree 8's branch 3 projects slot keys from was set.
    /// @param source The slot-key registry now consulted; zero closes the branch.
    event SlotKeySourceSet(address source);
    /// @notice The registry tree 8's branch 4 projects endpoints from was set.
    /// @param source The endpoint registry now consulted; zero closes the branch.
    event EndpointSourceSet(address source);
    /// @notice A fresh plane adopted a preceding plane's counters.
    /// @dev Carries the counters only. The roots behind those rounds stay with
    ///      the plane that published them, so {roundRootAt} below the seed
    ///      answers zero on this one.
    /// @param round The round number this plane continues from.
    /// @param versions Per-tree write counters, indexed by tree id; index 0 unused.
    event CountersSeeded(uint64 round, uint64[] versions);
    /// @notice The records contract admitted to the typed trees was installed.
    /// @param writer The contract now allowed through {writeTyped}.
    event TypedWriterSet(address writer);
    /// @notice One configuration row was written into a tree's branch 0.
    /// @param treeId The tree whose owning service the row configures.
    /// @param key The row's branch-0 key, as {configKey} computes it.
    /// @param value The row's single word of value.
    event ConfigSet(uint8 indexed treeId, bytes32 indexed key, bytes32 value);

    // --------------------------------------------------------------- errors

    /// @notice A tree id outside `1 .. TREE_COUNT` was supplied. Zero is not a tree.
    /// @param treeId The rejected id.
    error UnknownTree(uint8 treeId);
    /// @notice Two parallel arrays did not have the same length, or a batch was empty
    ///         where at least one row is required.
    /// @param keys Length of the key array.
    /// @param leaves Length of the value array.
    error LengthMismatch(uint256 keys, uint256 leaves);
    /// @notice A branch has handed out every slot it owns and cannot take a new key.
    /// @dev Raised rather than spilling into the neighbouring branch: a key in
    ///      the wrong branch would prove against the wrong branch root.
    /// @param treeId The tree the branch belongs to.
    /// @param branch The exhausted branch.
    error BranchFull(uint8 treeId, uint8 branch);
    /// @notice A branch id at or above `BRANCH_COUNT` was supplied.
    /// @param branch The rejected id.
    error UnknownBranch(uint8 branch);
    /// @notice A key already holds a slot in another branch of this tree.
    /// @dev Slots are permanent, so a key cannot be moved between branches.
    ///      Reaching this means two callers disagree about where a row lives.
    /// @param treeId The tree involved.
    /// @param key The key whose slot is already assigned.
    /// @param have The branch the key's slot actually sits in.
    /// @param want The branch the caller tried to write it into.
    error BranchMismatch(uint8 treeId, bytes32 key, uint8 have, uint8 want);
    /// @notice Branch 0 is written by `setConfig` alone.
    /// @dev Every other door refuses it, so a tree's writer or quorum can never
    ///      restate the configuration of the service that feeds it.
    /// @param treeId The tree whose branch 0 was targeted.
    error ConfigBranchReserved(uint8 treeId);
    /// @notice Tree 8's branch 3 was written while no slot-key registry is installed.
    error SlotKeySourceUnset();
    /// @notice Tree 8's branch 4 was written while no endpoint registry is installed.
    error EndpointSourceUnset();
    /// @notice Counters can be seeded only into a plane that has published nothing.
    /// @dev Seeding a plane that already moved would rewind counters consumers
    ///      have compared against, so it is refused rather than reconciled.
    error NotFresh();
    /// @notice The seeded version array was not one entry per tree plus the unused index 0.
    /// @param given The length supplied.
    error VersionCountMismatch(uint256 given);
    /// @notice The tree has no threshold installed, so no quorum write can be authorized.
    /// @param treeId The unconfigured tree.
    error TreeNotConfigured(uint8 treeId);
    /// @notice A round was requested while no tree has moved since the last one.
    /// @dev The round number is therefore not advanceable by anyone with gas
    ///      to spend, and a round always means something changed.
    error NothingToPublish();
    /// @notice The key holds no slot in this tree, so there is nothing to prove or read.
    /// @param treeId The tree searched.
    /// @param key The key with no slot.
    error UnknownKey(uint8 treeId, bytes32 key);
    /// @notice The caller is not the writer seat or typed writer this door requires.
    /// @param caller The rejected address.
    error NotAuthorized(address caller);
    /// @notice A round was asked for on a plane that has published none, or one above the latest.
    error NoRounds();
    /// @notice A threshold was configured above the number of members who could meet it.
    /// @dev Refused at configuration time so a tree is never installed already
    ///      unwritable. Register the roster first; that ordering is the point.
    ///      Revocation can still walk a live tree into this state later, which
    ///      is what {quorumHealth} exists for — revocation must never be
    ///      blocked on quorum arithmetic.
    /// @param treeId The tree being configured.
    /// @param live Members currently holding the role.
    /// @param required Approvals the rejected configuration would demand.
    error ThresholdUnreachable(uint8 treeId, uint256 live, uint256 required);
    /// @notice Trees 7 and 8 take no quorum writes — only their writer
    /// contract (and, for tree 8, the registry projection).
    /// @dev An intent's status is what the intent log verified and an identity
    ///      is what the registry or the ledger verified. No set of service
    ///      signatures can make a different answer true, so there is no quorum
    ///      door to refuse at — the door does not exist.
    /// @param treeId The writer-only tree a quorum write was aimed at.
    error WriterOnlyTree(uint8 treeId);
    /// @notice `setLeaves` was called on a tree that has a typed writer.
    /// @dev Trees 2, 3 and 4 keep the leaf's preimage beside its hash so a
    ///      consumer can read the VALUE. An untyped write sets the hash and
    ///      cannot set the preimage — the pair would disagree, and the stored
    ///      value would look authoritative while committing to nothing. The
    ///      typed entrypoint is not a convenience over this one; it is the
    ///      only door.
    /// @param treeId The typed tree an untyped write was aimed at.
    error TypedTreeOnly(uint8 treeId);
    /// @notice A `deployedChains` row names the zero chain or the zero account,
    ///         or repeats a chain. A table with either proves nothing about
    ///         where the account exists.
    /// @dev Checked wherever the leaf is hashed, so no door — quorum, writer
    ///      contract, identity projection — can publish a table a resolver on
    ///      another chain would read two ways.
    /// @param chainRef The offending row's chain reference.
    /// @param account The offending row's account on that chain.
    error InvalidChainAccount(bytes32 chainRef, bytes32 account);

    // ---------------------------------------------------------- constructor

    /**
     * @notice Pin the identity registry and bring all eight trees up empty.
     * @param registry_ The identity registry. Every signer, key and role is
     *        resolved through it.
     * @dev The registry is `immutable`, so no later call can point the quorum
     * at a registry supplied in calldata — a roster chosen by the caller is a
     * roster that approves whatever the caller wants.
     *
     * The empty-subtree table is built here rather than as constants because it
     * depends on the tagging, and a constant table that drifted from the
     * tagging would produce roots nothing can verify — silently, since both
     * sides would still be self-consistent.
     *
     * Every tree starts at the empty root rather than zero, so a consumer can
     * tell "this tree holds nothing" from "this contract has never run".
     */
    constructor(FinalIdentityRegistry registry_) {
        registry = registry_;

        // Level 0: the tagged hash of an empty (zero) leaf.
        _zero[0] = keccak256(abi.encodePacked(bytes1(0x00), bytes32(0)));
        for (uint256 l = 0; l < ROUND_DEPTH; l++) {
            // Both children equal, so the sort is a no-op and the order is
            // irrelevant — which is the only reason this table is one value per
            // level rather than one per position.
            _zero[l + 1] = keccak256(abi.encodePacked(bytes1(0x01), _zero[l], _zero[l]));
        }

        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            liveRoot[t] = _zero[DEPTH];
        }
    }

    // ------------------------------------------------------- configuration

    /**
     * @notice The gate every configuration entrypoint on this contract passes through.
     * @dev The registry's bootstrap admin alone while its window is open, the
     * sealed `ROLE_REGISTRAR` quorum afterwards. The same window the registry
     * uses, for the same reason — every roster has to be installed by someone
     * before it can install itself — and the same quorum, because a threshold
     * is membership by another name: whoever can set K to one owns the tree.
     *
     * Not `view`: the registrar path burns the registry's own nonce, so an
     * approved configuration payload cannot be replayed at a later block.
     * @param actionDomain The `ACTION_*` constant naming what is being configured.
     * @param payloadDigest Hash of the arguments this call would apply.
     * @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
     * @param approvals The sealed registrar quorum. Empty during bootstrap.
     */
    function _requireConfigurationAuthority(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) private {
        if (!registry.bootstrapSealed() && msg.sender == registry.bootstrapAdmin()) return;
        registry.requireRegistrarQuorum(actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /**
     * @notice Set which role may write a tree and how many approvals it needs.
     * @dev The configuration authority, never the tree's own quorum: a roster
     * that could raise or lower its own threshold is a roster with no
     * threshold. A tree left at `k == 0` refuses every quorum write with
     * `TreeNotConfigured`, which is the state a fresh plane starts in.
     * @param treeId The tree being configured.
     * @param role Role a signer must hold for an approval to count.
     * @param k Approvals a write needs; `0` leaves the tree unconfigured.
     * @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
     * @param approvals The sealed registrar quorum. Empty during bootstrap.
     */
    function configureTree(
        uint8 treeId,
        uint256 role,
        uint256 k,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _assertTree(treeId);
        _requireConfigurationAuthority(
            ACTION_CONFIGURE_TREE, keccak256(abi.encode(treeId, role, k)), anchorBlock, approvals
        );
        // Refuse a threshold nobody can meet. Register the members first; that
        // ordering is the point, not an inconvenience. A 4-of-5 configured
        // against three registered co-signers is a tree that reverts on every
        // write, and the revert names the threshold rather than the roster.
        if (k != 0) {
            uint256 live = registry.liveMemberCount(role);
            if (live < k) revert ThresholdUnreachable(treeId, live, k);
        }
        writerRole[treeId] = role;
        threshold[treeId] = k;
        emit TreeConfigured(treeId, role, k);
    }

    /**
     * @notice Point a tree at the contract allowed to write it directly.
     * @dev Same gate as `configureTree`, for the same reason. Setting it to the
     * zero address removes the path entirely and leaves the tree quorum-only.
     *
     * Point this at a CONTRACT, never at an externally owned account. The whole
     * argument for a quorum-free writer is that its rules are its bytecode; an
     * account holding a key is exactly the single-key authority the quorum on
     * {setLeaves} exists to prevent.
     *
     * Movable rather than immutable on purpose: an immutable pointer would mean
     * a ledger redeploy abandons the tree it writes, with no way back.
     * @param treeId The tree whose writer seat is being set.
     * @param writer The contract admitted to it; zero removes the seat.
     * @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
     * @param approvals The sealed registrar quorum. Empty during bootstrap.
     */
    function setTreeWriter(
        uint8 treeId,
        address writer,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _assertTree(treeId);
        _requireConfigurationAuthority(
            ACTION_SET_TREE_WRITER, keccak256(abi.encode(treeId, writer)), anchorBlock, approvals
        );
        treeWriter[treeId] = writer;
        emit TreeWriterSet(treeId, writer);
    }

    /**
     * @notice Point `syncIdentities` at the contract that knows the chain set.
     * @dev Same gate as `setTreeWriter`. Zero removes the source, after which
     * service leaves carry an empty `deployedChains` table — which is what a
     * plane looks like before its asset registry is seeded, and is why this
     * pointer belongs in the same bootstrap window as the seed itself.
     * @param source The asset registry to read the enabled chain set from.
     * @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
     * @param approvals The sealed registrar quorum. Empty during bootstrap.
     */
    function setChainSource(
        address source,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_CHAIN_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
        );
        chainSource = source;
        emit ChainSourceSet(source);
    }

    /// @notice Point tree 8's branch 3 at the slot-key registry it projects.
    /// @dev Same gate as `setChainSource`. Zero closes the branch entirely:
    ///      {syncSlotKeyLeaves} reverts `SlotKeySourceUnset` rather than
    ///      writing leaves whose value nothing vouched for.
    /// @param source The slot-key registry whose verdict the branch projects.
    /// @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
    /// @param approvals The sealed registrar quorum. Empty during bootstrap.
    function setSlotKeySource(
        address source,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_SLOT_KEY_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
        );
        slotKeySource = source;
        emit SlotKeySourceSet(source);
    }

    /// @notice Point tree 8's branch 4 at the endpoint registry it projects.
    /// @dev Same gate as `setSlotKeySource`, and the same fail-closed shape:
    ///      zero makes {syncEndpointLeaves} revert `EndpointSourceUnset`.
    /// @param source The endpoint registry whose verdict the branch projects.
    /// @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
    /// @param approvals The sealed registrar quorum. Empty during bootstrap.
    function setEndpointSource(
        address source,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_ENDPOINT_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
        );
        endpointSource = source;
        emit EndpointSourceSet(source);
    }

    /**
     * @notice Adopt a preceding plane's counters — one `treeVersion` per tree
     *         (index = treeId, 0 unused) and the published `round` — so a
     *         redeploy stays monotonic for every consumer that compares them:
     *         rings, explorers, the round feed.
     * @dev This contract is immutable, so replacing it means a new address, and
     * a fresh address would otherwise restart every counter at zero. A consumer
     * that treats a counter as monotonic would then read the new plane as
     * older than the state it already holds, and quietly ignore live data.
     *
     * It carries the counters and nothing else. The roots behind those rounds
     * stay with the plane that published them, so {roundRootAt} below the seed
     * answers zero here — pin a round on the plane that produced it.
     *
     * Configuration authority (bootstrap admin before the seal, registrar
     * quorum after), and only while this plane has published nothing:
     * `NotFresh` otherwise, because rewinding a counter a consumer has already
     * compared against is worse than never seeding at all.
     * @param versions Per-tree write counters to adopt, indexed by tree id;
     *        index 0 is unused and must still be present.
     * @param round_ The round number this plane continues from.
     * @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
     * @param approvals The sealed registrar quorum. Empty during bootstrap.
     */
    function seedCounters(
        uint64[] calldata versions,
        uint64 round_,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SEED_COUNTERS, keccak256(abi.encode(versions, round_)), anchorBlock, approvals
        );
        if (versions.length != TREE_COUNT + 1) revert VersionCountMismatch(versions.length);
        if (round != 0) revert NotFresh();
        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            if (treeVersion[t] != 0) revert NotFresh();
        }
        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            treeVersion[t] = versions[t];
        }
        round = round_;
        emit CountersSeeded(round_, versions);
    }

    /// @notice Install the records contract that writes the typed trees.
    /// @dev Trees 2, 3 and 4 have no other door at all — {setLeaves} refuses
    ///      them outright — so leaving this unset closes those three
    ///      completely. Same gate as `setTreeWriter`, and the same rule: a
    ///      contract, never an account holding a key.
    /// @param writer The records contract admitted to {writeTyped}.
    /// @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
    /// @param approvals The sealed registrar quorum. Empty during bootstrap.
    function setTypedWriter(
        address writer,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_TYPED_WRITER, keccak256(abi.encode(writer)), anchorBlock, approvals
        );
        typedWriter = writer;
        emit TypedWriterSet(writer);
    }

    /**
     * @notice Write configuration rows into a tree's branch 0.
     * @param treeId The tree whose owning service the rows configure.
     * @param keys `configKey(name, sub)` per row.
     * @param values One word per row — a duration, a count, an address, a
     *        flag; the reader knows the shape from the name.
     * @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
     * @param approvals The sealed registrar quorum. Empty during bootstrap.
     *
     * @dev The configuration authority, not the tree's writer or quorum: a
     * tree's writer states what its domain verified, its quorum attests to
     * what it read, and neither is the authority over how the service that
     * feeds it is configured.
     *
     * The value is stored beside the hash so a contract on this chain reads it
     * in one call ({configValue}) while the same row is provable off chain
     * against a round root. That is one source of truth for the fleet, the
     * contracts and any explorer at once — a service reading its own
     * environment instead would be a second source, free to disagree with this
     * one and with nothing on chain able to notice.
     */
    function setConfig(
        uint8 treeId,
        bytes32[] calldata keys,
        bytes32[] calldata values,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _assertTree(treeId);
        if (keys.length != values.length || keys.length == 0) revert LengthMismatch(keys.length, values.length);
        _requireConfigurationAuthority(
            ACTION_SET_CONFIG, keccak256(abi.encode(treeId, keys, values)), anchorBlock, approvals
        );
        for (uint256 i = 0; i < keys.length; i++) {
            _configValue[treeId][keys[i]] = values[i];
            _set(treeId, BRANCH_CONFIG, keys[i], configLeafHash(treeId, keys[i], values[i]));
            emit ConfigSet(treeId, keys[i], values[i]);
        }
        _bump(treeId, keys.length);
    }

    // ------------------------------------------------------------- writing

    /**
     * @notice Write leaves into one branch of one tree under a PQ quorum.
     * @param treeId Which tree.
     * @param branch Which branch — never 0, which `setConfig` alone writes.
     * @param keys Domain keys — a wallet address for accounts, an asset id for
     *        the allowlist, whatever identifies a row in that domain. Each gets
     *        a permanent slot in the branch on first write.
     * @param leaves The raw (untagged) leaf values.
     * @param anchorBlock The block the approving roster is read as of.
     * @param approvals At least `threshold[treeId]` of them, ascending by signer.
     *
     * @dev The digest binds the tree, its nonce, and the full batch. Binding the
     * nonce is what stops the same approved batch being replayed: without it,
     * an approval to set a price is an approval to set that price again at any
     * later block, which for an oracle is the whole attack.
     *
     * ML-DSA-87 is required rather than accepted. These are operational,
     * high-cadence writes — the transaction class — and leaving the choice open
     * would mean a break in either scheme takes the tree.
     *
     * Three tree classes are refused here outright, each with its own error:
     * the typed trees (2, 3 and 4) because their preimage has to be built by
     * the records contract, and the writer-only trees (7 and 8) because no set
     * of service signatures can make a different answer true about an intent's
     * status or an identity's standing.
     */
    function setLeaves(
        uint8 treeId,
        uint8 branch,
        bytes32[] calldata keys,
        bytes32[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _assertTree(treeId);
        _assertDataBranch(treeId, branch);
        if (treeId == TREE_PHI || treeId == TREE_VASSET || treeId == TREE_ORACLE || treeId == TREE_COMPLIANCE) {
            revert TypedTreeOnly(treeId);
        }
        // Trees 7 and 8 have their own rulers and NO quorum path at all: an
        // intent's status is what `FinalIntentLog` verified, an identity is
        // what the registry or the ledger verified, and no set of service
        // signatures can make a different answer true.
        if (treeId == TREE_INTENTS || treeId == TREE_IDENTITY) revert WriterOnlyTree(treeId);
        if (keys.length != leaves.length) revert LengthMismatch(keys.length, leaves.length);
        uint256 k = threshold[treeId];
        if (k == 0) revert TreeNotConfigured(treeId);

        uint64 n = nonce[treeId];
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this),
                ACTION_SET_LEAVES,
                anchorBlock,
                keccak256(abi.encode(treeId, branch, n, keys, leaves))
            ),
            writerRole[treeId],
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce[treeId] = n + 1;

        for (uint256 i = 0; i < keys.length; i++) {
            _set(treeId, branch, keys[i], leaves[i]);
        }

        _bump(treeId, keys.length);
    }

    /// @notice One chain an account exists on, and as what.
    /// @dev `chainRef` is the registry's CAIP-derived chain reference — the one
    ///      identifier that names an EVM chain and a non-EVM one alike — and
    ///      `account` is the wallet's account there, in that chain's own account
    ///      space (an EVM address right-aligned, a 32-byte key filling the
    ///      width). Field-for-field with `IWalletTypes.ChainAccount`.
    struct ChainAccount {
        /// @dev The registry's CAIP-derived reference for the chain.
        bytes32 chainRef;
        /// @dev The account on that chain, in that chain's own account space.
        bytes32 account;
    }

    /// @notice `FinalWalletFactory.AccountStateLeaf`, field for field.
    /// @dev The preimage of every tree-1 leaf. The field set, the field ORDER
    ///      and the domain must match the factory's exactly on every supported
    ///      chain; a field added, removed or reordered on one side alone is a
    ///      root every execution chain rejects with nothing naming the cause.
    struct AccountStateLeaf {
        /// @dev The Final Wallet this leaf describes. Also what `accountKeyFor`
        ///      hashes into the tree-1 key, so one wallet holds one slot.
        address wallet;
        /// @dev Active-stage access-key commitment — the credential the account
        ///      ledger checks a state transition against.
        bytes32 liveAccess;
        /// @dev Active-stage transaction-key commitment.
        bytes32 liveTransaction;
        /// @dev Pre-committed successor to `liveAccess`, so a rotation reveals a
        ///      key that was already committed rather than one chosen after.
        bytes32 recoveryAccess;
        /// @dev Pre-committed successor to `liveTransaction`.
        bytes32 recoveryTransaction;
        /// @dev Active-stage encapsulation commitment and its pre-committed
        /// successor. Field-for-field with `FinalWalletFactory.AccountStateLeaf`;
        /// a field added on one side and not the other is a root every execution
        /// chain rejects, with nothing pointing at the cause.
        bytes32 liveKem;
        /// @dev Pre-committed successor to `liveKem`.
        bytes32 recoveryKem;
        /// @dev Who may authorize for this account. This is the PROVEN owner an
        ///      execution chain resolves authority from; a copy stored there is
        ///      wrong for as long as nobody has pushed to that chain, and
        ///      nothing there can tell.
        address owner;
        /// @dev Whether the account authorizes post-quantum. One-way once set.
        bool pqEnabled;
        /// @dev Whether the account is frozen. Returned to a resolver rather
        ///      than enforced by it, so a reader can still learn who owns a
        ///      frozen account; the wallet refuses on this PROVEN value rather
        ///      than on a synced copy, so a chain behind on the fan-out cannot
        ///      let a frozen account transact.
        bool frozen;
        /// @dev The chains this account exists on, and its account on each —
        /// including chains whose accounts are not EVM addresses. Decided HERE
        /// (set by the holder through the ledger) and enforced there: an
        /// execution chain refuses to create the account unless the table has a
        /// row for it, and a settlement toward a chain with no row is refused at
        /// the source. This is also what a zero beneficiary resolves through: a
        /// table naming the account on each chain answers "as what", which a
        /// bare membership flag never could. `_assertChainAccounts` rejects a
        /// zero chain, a zero account and a repeated chain, so no door can
        /// publish a table a resolver would read two ways.
        ChainAccount[] deployedChains;
        /// @dev Per-chain dormancy verdict, one bit per asset-registry chain
        /// slot, so the bit positions are the registry's slot numbering rather
        /// than this table's row order.
        uint32 dormantChains;
        /// @dev Monotonic per-account revision. Lets a reader holding two
        ///      proofs tell which one is newer without consulting a round.
        uint64 version;
    }

    /**
     * @notice Write account state into tree 1 from the typed leaf.
     * @dev The typed form exists so the leaf preimage is built HERE rather than
     * by whoever assembles the calldata. Tree 1 is the source of truth for every
     * other chain, and `syncAccountState` will accept any 32 bytes that carry a
     * valid proof — so if the publisher chose the preimage, the publisher could
     * write an account state that no wallet record on this chain agrees with,
     * and the proof would still verify everywhere.
     *
     * **Sealed.** Tree 1 is membership: a leaf here is who an account is, on
     * every chain. So the round takes the hybrid class — each approval carries
     * the ML-DSA-87 vote AND the member's SLH-DSA seal — where the other trees
     * take the transaction class alone. A lattice break rewrites a price; it
     * does not rewrite an account.
     * @param leaves The account states to write, one per wallet.
     * @param anchorBlock The block the approving roster is read as of.
     * @param approvals At least `threshold[TREE_ACCOUNTS]` of them, ascending by signer.
     */
    function setAccountStates(
        AccountStateLeaf[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        uint256 k = threshold[TREE_ACCOUNTS];
        if (k == 0) revert TreeNotConfigured(TREE_ACCOUNTS);

        bytes32[] memory keys = new bytes32[](leaves.length);
        bytes32[] memory hashes = new bytes32[](leaves.length);
        for (uint256 i = 0; i < leaves.length; i++) {
            keys[i] = accountKeyFor(leaves[i].wallet);
            hashes[i] = accountStateLeafHash(leaves[i]);
        }

        uint64 n = nonce[TREE_ACCOUNTS];
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this),
                ACTION_SET_LEAVES,
                anchorBlock,
                keccak256(abi.encode(TREE_ACCOUNTS, n, keys, hashes))
            ),
            writerRole[TREE_ACCOUNTS],
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            true
        );
        nonce[TREE_ACCOUNTS] = n + 1;

        for (uint256 i = 0; i < leaves.length; i++) {
            _set(TREE_ACCOUNTS, BRANCH_MAIN, keys[i], hashes[i]);
        }

        _bump(TREE_ACCOUNTS, leaves.length);
    }

    /**
     * @notice Write account state into tree 1 from the contract that owns it.
     * @dev No quorum, and no nonce burned: `treeWriter[1]` is the ledger, and
     * the ledger already verified the holder's own signature before it called
     * here. See {treeWriter} for why adding a service quorum on top would be a
     * censorship power rather than a safeguard.
     *
     * Typed, exactly as `setAccountStates` is: the preimage is built HERE, so
     * even the writer contract cannot publish a leaf whose meaning no record on
     * this chain agrees with.
     * @param leaves The account states to write, one per wallet.
     */
    function setAccountStatesAsWriter(AccountStateLeaf[] calldata leaves) external {
        if (msg.sender != treeWriter[TREE_ACCOUNTS]) revert NotAuthorized(msg.sender);
        for (uint256 i = 0; i < leaves.length; i++) {
            _set(TREE_ACCOUNTS, BRANCH_MAIN, accountKeyFor(leaves[i].wallet), accountStateLeafHash(leaves[i]));
        }
        _bump(TREE_ACCOUNTS, leaves.length);
    }

    /**
     * @notice Write raw leaves into any tree from the contract that owns it.
     * @dev The generic sibling of {setAccountStatesAsWriter}, for a tree whose
     * writer is a contract rather than a service quorum. Same authorization —
     * `treeWriter[treeId]` and nothing else — and the same reasoning: the
     * writer has already verified whatever its domain requires, and layering a
     * quorum on top of a contract's own rules is a censorship power rather
     * than a safeguard.
     *
     * UNTYPED, unlike the account path, and that is the trade. Tree 1's
     * preimage is built here so even the ledger cannot publish a leaf whose
     * meaning no record agrees with; a generic writer supplies its own hash,
     * so the leaf means whatever that contract says it means. Acceptable only
     * because the writer is a specific contract this chain's operators
     * installed — its rules are its bytecode, it has no owner and no proxy —
     * and NOT acceptable for a role-gated key. Point `treeWriter` at a
     * contract, never at an externally owned account.
     * @param treeId The tree to write.
     * @param branch The branch within it. Never 0, which `setConfig` alone writes.
     * @param keys Domain keys, one per leaf. Each takes a permanent slot in the
     *        branch on first write.
     * @param leaves The raw (untagged) leaf values.
     */
    function setLeavesAsWriter(uint8 treeId, uint8 branch, bytes32[] calldata keys, bytes32[] calldata leaves)
        external
    {
        if (msg.sender != treeWriter[treeId]) revert NotAuthorized(msg.sender);
        _assertDataBranch(treeId, branch);
        if (keys.length != leaves.length) revert LengthMismatch(keys.length, leaves.length);
        for (uint256 i = 0; i < keys.length; i++) {
            _set(treeId, branch, keys[i], leaves[i]);
        }
        _bump(treeId, keys.length);
    }

    /// @notice The leaf hash `FinalWalletFactory.accountStateLeafHash` computes.
    /// @dev Identical `abi.encode`, identical field order, identical domain, and
    /// that identity is the whole contract between this chain and every
    /// execution chain. `deployedChains` rides through `abi.encode` like every
    /// other field — head offset, then length and rows — so the table is
    /// committed whole and in order. The table is validated here rather than at
    /// each door, so every path into tree 1 gets the same refusal.
    /// @param leaf The account state to commit to.
    /// @return The tagged leaf hash, ready to be placed in tree 1.
    function accountStateLeafHash(AccountStateLeaf memory leaf) public pure returns (bytes32) {
        _assertChainAccounts(leaf.deployedChains);
        return keccak256(
            abi.encode(
                DOMAIN_ACCOUNT_STATE_LEAF,
                leaf.wallet,
                leaf.liveAccess,
                leaf.liveTransaction,
                leaf.recoveryAccess,
                leaf.recoveryTransaction,
                leaf.liveKem,
                leaf.recoveryKem,
                leaf.owner,
                leaf.pqEnabled,
                leaf.frozen,
                leaf.deployedChains,
                leaf.dormantChains,
                leaf.version
            )
        );
    }

    /// @notice Reject a `deployedChains` table a resolver could not read.
    /// @dev A well-formed table: no zero chain, no zero account, no chain twice.
    ///      Checked where the leaf is hashed so no door — quorum, writer
    ///      contract, identity projection — can publish a table a resolver
    ///      would read two ways. The duplicate scan is quadratic in the row
    ///      count, which is deliberate: gas is not a constraint on this chain,
    ///      and a sort or a seen-set would cost correctness or storage to save
    ///      something nobody is paying for.
    /// @param rows The table to validate.
    function _assertChainAccounts(ChainAccount[] memory rows) private pure {
        for (uint256 i = 0; i < rows.length; i++) {
            if (rows[i].chainRef == bytes32(0) || rows[i].account == bytes32(0)) {
                revert InvalidChainAccount(rows[i].chainRef, rows[i].account);
            }
            for (uint256 j = 0; j < i; j++) {
                if (rows[j].chainRef == rows[i].chainRef) {
                    revert InvalidChainAccount(rows[i].chainRef, rows[i].account);
                }
            }
        }
    }

    /// @notice The account `wallet`'s published table names on `chainRef`, or
    ///         zero if it has no row there.
    /// @dev A convenience over `accountStateLeafHash`'s input for readers on
    /// this chain; execution chains answer the same question from their synced
    /// record (`FinalWalletFactory.addressOn`). Pure, so it reads the leaf it is
    /// handed and never this contract's storage — the caller is responsible for
    /// having proved that leaf first.
    /// @param leaf The account state to search.
    /// @param chainRef The chain being asked about.
    /// @return The account on that chain, or zero when the table has no row for it.
    function accountOn(AccountStateLeaf memory leaf, bytes32 chainRef) public pure returns (bytes32) {
        for (uint256 i = 0; i < leaf.deployedChains.length; i++) {
            if (leaf.deployedChains[i].chainRef == chainRef) return leaf.deployedChains[i].account;
        }
        return bytes32(0);
    }

    /**
     * @notice The typed trees' write door — `FinalStateRecords` alone.
     * @dev The quorum, the nonce and the write, shared by every typed record.
     * The records contract computed the keys and hashes from the structs it
     * stores; this contract admits nobody else to trees 2, 3 and 4
     * (`setLeaves` refuses them), so the value there can never drift from
     * the commitment here.
     *
     * The digest is byte-identical to `setLeaves`' over the same keys and
     * hashes, deliberately: the typed entrypoints choose the PREIMAGE, not the
     * authorization. A member recomputes one digest whichever door the batch
     * came through, and there is no second approval shape to get wrong.
     *
     * Always branch 1: a typed record is a domain row, and branch 0 belongs to
     * the configuration authority on every tree without exception.
     * @param treeId The typed tree being written.
     * @param keys Domain keys the records contract computed, one per leaf.
     * @param hashes Leaf hashes the records contract computed from its structs.
     * @param anchorBlock The block the approving roster is read as of.
     * @param approvals At least `threshold[treeId]` of them, ascending by signer.
     */
    function writeTyped(
        uint8 treeId,
        bytes32[] memory keys,
        bytes32[] memory hashes,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (msg.sender != typedWriter) revert NotAuthorized(msg.sender);
        uint256 k = threshold[treeId];
        if (k == 0) revert TreeNotConfigured(treeId);

        uint64 n = nonce[treeId];
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this),
                ACTION_SET_LEAVES,
                anchorBlock,
                keccak256(abi.encode(treeId, n, keys, hashes))
            ),
            writerRole[treeId],
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce[treeId] = n + 1;

        for (uint256 i = 0; i < keys.length; i++) {
            _set(treeId, BRANCH_MAIN, keys[i], hashes[i]);
        }

        _bump(treeId, keys.length);
    }

    /**
     * @notice The typed door for a tree whose leaves live in SEVERAL data branches — tree 9, whose
     *         approvals, revocations, counters and attestations are four key families, each with a
     *         permanent branch. Same writer, same role, same threshold and the same per-tree nonce as
     *         `writeTyped`; the branch is folded into the signed payload so a quorum that approved a
     *         revocation cannot be replayed as an approval.
     * @dev `writeTyped` stays byte-for-byte what it is (trees 2–4 write `BRANCH_MAIN` and their lanes
     *      sign `(treeId, n, keys, hashes)`); this door signs `(treeId, branch, n, keys, hashes)`.
     *      Branch 0 is `setConfig`'s alone.
     * @param treeId The tree.
     * @param branch The data branch every key of this write lives in (`1 .. BRANCH_COUNT - 1`).
     * @param keys Domain keys, as the companion derived them.
     * @param hashes The leaf hashes, one per key.
     * @param anchorBlock The roster anchor the approvals were made against.
     * @param approvals `threshold[treeId]` ML-DSA-87 votes from `writerRole[treeId]` members.
     */
    function writeTypedInBranch(
        uint8 treeId,
        uint8 branch,
        bytes32[] memory keys,
        bytes32[] memory hashes,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (msg.sender != typedWriter) revert NotAuthorized(msg.sender);
        _assertDataBranch(treeId, branch);
        if (keys.length != hashes.length) revert LengthMismatch(keys.length, hashes.length);
        uint256 k = threshold[treeId];
        if (k == 0) revert TreeNotConfigured(treeId);
        uint64 n = nonce[treeId];
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this),
                ACTION_SET_LEAVES,
                anchorBlock,
                keccak256(abi.encode(treeId, branch, n, keys, hashes))
            ),
            writerRole[treeId],
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce[treeId] = n + 1;
        for (uint256 i = 0; i < keys.length; i++) {
            _set(treeId, branch, keys[i], hashes[i]);
        }
        _bump(treeId, keys.length);
    }

    /**
     * @notice Snapshot every tree's root into a new round.
     * @dev Permissionless, deliberately. Every root being snapshotted was
     * already authorized by its tree's quorum, so this adds no authority — it
     * only fixes a moment. Requiring a signature would put a liveness
     * dependency in front of publication for no security gain.
     *
     * A round that would change nothing is refused, so the round number cannot
     * be advanced by anyone with gas to spend.
     * @return published The round number just written.
     */
    function publishRound() external returns (uint64 published) {
        bool changed;
        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            if (treeVersion[t] != _publishedVersion[t]) {
                changed = true;
                break;
            }
        }
        if (!changed) revert NothingToPublish();

        published = round + 1;
        Round storage r = _rounds[published];
        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            r.roots[t] = liveRoot[t];
            _publishedVersion[t] = treeVersion[t];
        }
        r.roundRoot = _foldForest(_forestLeaves(r.roots));
        r.blockNumber = uint64(block.number);
        // MILLISECONDS, like every instant on this chain.
        r.timestamp = FinalChainTime.nowMs();
        round = published;
        emit RoundPublished(published, r.blockNumber, r.timestamp);
    }

    // ---------------------------------------------------------------- views

    /// @notice Every root from one round. Index by the `TREE_*` constants;
    /// index 0 is unused.
    /// @dev An unpublished round answers all zeros rather than reverting, so a
    ///      caller scanning forward can tell where the history ends.
    /// @param which The round number.
    /// @return The eight tree roots at that round, indexed by tree id.
    function rootsAt(uint64 which) external view returns (bytes32[TREE_COUNT + 1] memory) {
        return _rounds[which].roots;
    }

    /// @notice One tree's root at one round.
    /// @param which The round number.
    /// @param treeId The tree to read.
    /// @return That tree's root at that round; zero if the round is unpublished.
    function rootAt(uint64 which, uint8 treeId) external view returns (bytes32) {
        _assertTree(treeId);
        return _rounds[which].roots[treeId];
    }

    /// @notice The one word that commits to every tree at one round.
    /// @dev The value a consumer pins. Everything in the plane at that instant
    ///      proves against it, which is the only contemporaneity this contract
    ///      offers — the live roots move independently and do not.
    /// @param which The round number.
    /// @return The round root; zero if the round is unpublished on this plane.
    function roundRootAt(uint64 which) external view returns (bytes32) {
        return _rounds[which].roundRoot;
    }

    /**
     * @notice The `FOREST_BITS` siblings that take a tree's root at one round
     *         up to that round's root — appended to `proofFor`, they make a
     *         leaf provable against `roundRootAt(which)` by the same verifier.
     * @dev Folds the round's stored roots in memory rather than keeping the
     * upper levels in storage: the fold is cheap, and one stored copy of a
     * value is one fewer place for two copies to disagree.
     * @param which The round number. Must be published on this plane.
     * @param treeId The tree whose root is being lifted to the round root.
     * @return path The `FOREST_BITS` siblings, lowest level first.
     */
    function roundProofFor(uint64 which, uint8 treeId) external view returns (bytes32[] memory path) {
        _assertTree(treeId);
        if (which == 0 || which > round) revert NoRounds();
        bytes32[] memory level = _forestLeaves(_rounds[which].roots);
        path = new bytes32[](FOREST_BITS);
        uint256 idx = treeId;
        uint256 n = level.length;
        for (uint256 l = 0; l < FOREST_BITS; l++) {
            path[l] = level[idx ^ 1];
            n >>= 1;
            for (uint256 i = 0; i < n; i++) {
                level[i] = _pair(level[2 * i], level[2 * i + 1]);
            }
            idx >>= 1;
        }
    }

    /// @notice The latest round's roots, with the block it was taken at.
    /// @dev Reverts `NoRounds` on a plane that has published nothing, rather
    ///      than answering an empty round that a caller could mistake for a
    ///      real snapshot of an empty plane.
    /// @return which The round number.
    /// @return roots The eight tree roots, indexed by tree id; index 0 unused.
    /// @return blockNumber Block the snapshot was taken in.
    /// @return timestamp Snapshot instant, in milliseconds.
    function latestRound()
        external
        view
        returns (uint64 which, bytes32[TREE_COUNT + 1] memory roots, uint64 blockNumber, uint64 timestamp)
    {
        which = round;
        if (which == 0) revert NoRounds();
        Round storage r = _rounds[which];
        return (which, r.roots, r.blockNumber, r.timestamp);
    }

    /// @notice The raw leaf stored for a key, and whether it has a slot.
    /// @dev The UNTAGGED value, as it was written. The tag is applied when the
    ///      leaf is hashed into the tree, so a caller reproducing a leaf hash
    ///      applies it themselves. A key with no slot answers `(0, false)`
    ///      rather than reverting, so presence is a question this view can be
    ///      asked directly.
    /// @param treeId The tree to read.
    /// @param key The domain key.
    /// @return leaf The stored value, or zero when the key has no slot.
    /// @return present Whether the key holds a slot in this tree.
    function leafOf(uint8 treeId, bytes32 key) external view returns (bytes32 leaf, bool present) {
        uint256 s = _slotPlusOne[treeId][key];
        if (s == 0) return (bytes32(0), false);
        return (_leaf[treeId][s - 1], true);
    }

    /// @notice The permanent slot for a key. Reverts if it has none. The
    /// slot's top `BRANCH_BITS` are its branch.
    /// @dev Stored one-based internally so an unassigned key is distinguishable
    ///      from slot 0, and returned zero-based here — slot 0 of branch 0 is a
    ///      real position.
    /// @param treeId The tree to read.
    /// @param key The domain key.
    /// @return The key's zero-based slot index within the tree.
    function slotOf(uint8 treeId, bytes32 key) public view returns (uint256) {
        uint256 s = _slotPlusOne[treeId][key];
        if (s == 0) revert UnknownKey(treeId, key);
        return s - 1;
    }

    /// @notice The key a slot was handed to, or zero if it is still free —
    /// the enumeration every branch offers: slots `branch << BRANCH_DEPTH`
    /// through `+ branchSlotsUsed(treeId, branch) - 1`.
    /// @dev Because slots are handed out in order and never reused, that range
    ///      is exactly the branch's contents: a reader enumerates a branch on
    ///      chain without an event window and without an indexer.
    /// @param treeId The tree to read.
    /// @param slot The slot index.
    /// @return The key holding that slot, or zero when it was never handed out.
    function keyAt(uint8 treeId, uint256 slot) external view returns (bytes32) {
        return _keyAt[treeId][slot];
    }

    /// @notice Slots handed out in one branch.
    /// @param treeId The tree to read.
    /// @param branch The branch to read.
    /// @return How many slots of that branch are in use — its enumeration bound.
    function branchSlotsUsed(uint8 treeId, uint8 branch) external view returns (uint256) {
        return _branchSlotsUsed[treeId][branch];
    }

    /// @notice One branch's root: the level-`BRANCH_DEPTH` node at its position.
    /// @dev A branch that has never been written answers the empty-subtree hash
    ///      at that level, not zero, because that is genuinely its root.
    /// @param treeId The tree the branch belongs to.
    /// @param branch The branch to read.
    /// @return The branch's root node.
    function branchRoot(uint8 treeId, uint8 branch) external view returns (bytes32) {
        _assertTree(treeId);
        _assertBranch(branch);
        return _nodeAt(treeId, BRANCH_DEPTH, branch);
    }

    /// @notice The first `BRANCH_DEPTH` siblings of `proofFor` — a proof
    /// against the leaf's branch root rather than the tree root.
    /// @dev The same path cut lower. A consumer that only ever needs one
    ///      branch can pin `branchRoot` and verify with fewer siblings; the
    ///      verifier is unchanged, since sorted pairs carry no direction bits.
    /// @param treeId The tree to read.
    /// @param key The domain key. Must already hold a slot.
    /// @return The sibling path from the leaf up to its branch root.
    function branchProofFor(uint8 treeId, bytes32 key) external view returns (bytes32[] memory) {
        _assertTree(treeId);
        return _path(treeId, slotOf(treeId, key), BRANCH_DEPTH);
    }

    /// @notice A configuration row's value, and whether the row exists.
    /// @dev Presence is read from the slot table, not from the value: a row
    ///      deliberately set to zero exists and answers `present`.
    /// @param treeId The tree whose branch 0 holds the row.
    /// @param key The row key, as {configKey} computes it.
    /// @return value The row's single word of value.
    /// @return present Whether the row has ever been written.
    function configValue(uint8 treeId, bytes32 key) external view returns (bytes32 value, bool present) {
        present = _slotPlusOne[treeId][key] != 0;
        value = _configValue[treeId][key];
    }

    /// @notice The branch-0 key of a configuration row: a name the owning
    /// service defines, and a sub-key (a chain reference, an asset, zero).
    /// @dev Its own key domain, so a configuration row can never be handed a
    ///      slot that a domain row of the same tree would want.
    /// @param name The row's name, defined by the service that owns the tree.
    /// @param sub The row's sub-key, or zero when the name stands alone.
    /// @return The branch-0 key.
    function configKey(bytes32 name, bytes32 sub) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_CONFIG_KEY, name, sub));
    }

    /// @notice The leaf a configuration row hashes to.
    /// @dev Binds the tree id as well as the key and the value, so the same row
    ///      in two trees is two different leaves and a proof cannot be carried
    ///      from one tree's branch 0 to another's.
    /// @param treeId The tree the row belongs to.
    /// @param key The row key.
    /// @param value The row value.
    /// @return The untagged leaf value for that row.
    function configLeafHash(uint8 treeId, bytes32 key, bytes32 value) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_CONFIG_LEAF, treeId, key, value));
    }

    /// @notice The tree-8 branch-2 key an owner occupies.
    /// @param owner The owner whose wallet list the row indexes.
    /// @return The branch-2 key.
    function ownerIndexKeyFor(address owner) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_OWNER_INDEX_KEY, owner));
    }

    /// @notice The owner-index leaf: a commitment to the ledger's ordered
    /// `walletsByOwner(owner)`.
    /// @dev A commitment, not the list. The tree is the search structure; the
    ///      ledger holds the readable array this leaf proves, so ORDER matters
    ///      — the same wallets in a different order are a different leaf.
    /// @param owner The owner the index row belongs to.
    /// @param wallets The owner's wallets, in the ledger's own order.
    /// @return The untagged leaf value for that row.
    function ownerIndexLeafHash(address owner, address[] memory wallets) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_OWNER_INDEX_LEAF, owner, wallets));
    }

    /// @notice The tree-8 branch-3 key of one member's slot — a ring position.
    /// @dev The index is reduced modulo `SLOT_KEY_RING` here, so the branch is
    ///      an index over the recent slots and never fills. A caller passes the
    ///      real slot number and does not do the reduction itself.
    /// @param member The co-signer the slot key belongs to.
    /// @param slotIndex The slot number, before the ring modulus.
    /// @return The branch-3 key.
    function slotKeyFor(address member, uint64 slotIndex) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_SLOT_KEY, member, slotIndex % SLOT_KEY_RING));
    }

    /**
     * @notice Project slot keys into tree 8's branch 3 — the co-signers'
     *         per-slot KEM publics the private option seals to.
     * @dev Permissionless, for {syncIdentityLeaves}' reason: the leaf VALUE
     * is `slotKeySource`'s own verdict (the registry verified the member's
     * signature when the key was published, and answers zero once the slot's
     * window has passed), so this adds no authority and only projects. The
     * registry calls it same-tx on publication; anyone may call it to retire a
     * slot that lapsed by time.
     * @param member The co-signer whose ring positions are being projected.
     * @param slotIndexes The slots to project. Reduced modulo `SLOT_KEY_RING`.
     */
    function syncSlotKeyLeaves(address member, uint64[] calldata slotIndexes) external {
        address source = slotKeySource;
        if (source == address(0)) revert SlotKeySourceUnset();
        for (uint256 i = 0; i < slotIndexes.length; i++) {
            _set(
                TREE_IDENTITY,
                BRANCH_SLOT_KEYS,
                slotKeyFor(member, slotIndexes[i]),
                ISlotKeySource(source).slotKeyLeafOf(member, slotIndexes[i])
            );
        }
        _bump(TREE_IDENTITY, slotIndexes.length);
    }

    /// @notice The tree-8 branch-4 key of one tunnel endpoint.
    /// @param endpointId The endpoint's certificate subject key id.
    /// @return The branch-4 key.
    function endpointKeyFor(bytes32 endpointId) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ENDPOINT_KEY, endpointId));
    }

    /**
     * @notice Project tunnel endpoints into tree 8's branch 4.
     * @dev Permissionless, for {syncSlotKeyLeaves}' reason: the leaf VALUE is
     * `endpointSource`'s own verdict — the registry admitted the certificate
     * under the registrar quorum with the holder's proof of possession, and
     * answers the revoked status once it is revoked — so this adds no authority
     * and only projects. The registry calls it same-tx on registration and
     * revocation; anyone may call it to re-project.
     * @param endpointIds The endpoint ids to project.
     */
    function syncEndpointLeaves(bytes32[] calldata endpointIds) external {
        address source = endpointSource;
        if (source == address(0)) revert EndpointSourceUnset();
        for (uint256 i = 0; i < endpointIds.length; i++) {
            _set(
                TREE_IDENTITY,
                BRANCH_ENDPOINTS,
                endpointKeyFor(endpointIds[i]),
                IEndpointSource(source).endpointLeafOf(endpointIds[i])
            );
        }
        _bump(TREE_IDENTITY, endpointIds.length);
    }

    /**
     * @notice The sibling path for a key, ready for
     *         `FinalMerkle.verifyTaggedSortedProof` on any chain.
     * @dev The sanctioned way to ask any tree a question, tree 1 above all: a
     * view, so a caller fetches a proof with one `eth_call` and never rebuilds
     * the tree off chain. Rebuilding is where a divergence between what the
     * chain holds and what a service believes it holds would come from, and
     * this removes the second implementation entirely.
     *
     * A rebuild is not merely redundant, it is wrong. This tree is fixed depth,
     * zero-padded and insertion-ordered; a fold that sorts its leaves or sizes
     * itself to the leaf count produces a different root, and a proof against
     * that root verifies nowhere while looking perfectly well formed.
     *
     * Pair the path with {liveRoot} for the current root, or append
     * {roundProofFor} and verify against {roundRootAt} to pin a whole round.
     * @param treeId The tree to read.
     * @param key The domain key. Must already hold a slot.
     * @return The `DEPTH` siblings from the leaf up to the tree root, lowest first.
     */
    function proofFor(uint8 treeId, bytes32 key) external view returns (bytes32[] memory) {
        _assertTree(treeId);
        return _path(treeId, slotOf(treeId, key), DEPTH);
    }

    /// @notice The empty-subtree hash at a level. Level `DEPTH` is the root of
    /// a tree with nothing in it.
    /// @dev What an off-chain verifier needs to reproduce the padding this tree
    ///      uses. Levels run `0 .. ROUND_DEPTH`; anything above reverts on the
    ///      array bound.
    /// @param level The level to read.
    /// @return The hash of an empty subtree of that height.
    function emptyRoot(uint256 level) external view returns (bytes32) {
        return _zero[level];
    }

    /// @notice The tree-1 key a wallet occupies.
    /// @dev A full-width hash rather than the packed address, so a hashed key
    ///      cannot be steered onto a slot an address key would take.
    /// @param wallet The Final Wallet.
    /// @return The tree-1 key.
    function accountKeyFor(address wallet) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ACCOUNT_KEY, wallet));
    }

    /**
     * @notice Copy a registered identity into tree 1 as an account-state leaf.
     * @dev Services are Final Wallets, so a service's leaf is the SAME leaf a
     * user's wallet gets — `FinalWalletFactory.AccountStateLeaf`, four key
     * commitments and all. There is no second shape and no second domain,
     * which is what lets every chain that already consumes account state
     * consume a co-signer's identity with no contract change.
     *
     * `owner` is the account itself: a service wallet is its own owner, having
     * no separate holder to speak for it.
     *
     * Permissionless, and for the same reason `publishRound` is: every fact it
     * writes was already authorized when it entered the registry, so this adds
     * no authority and only projects. Gating it would put a liveness dependency
     * in front of publishing a revocation, which is the one thing that must
     * never wait.
     * @param accounts The registered service identities to project. Each must
     *        already be registered; an unknown account reverts `UnknownKey`.
     */
    function syncIdentities(address[] calldata accounts) external {
        // One table for the batch: a service is its own canonical address on
        // every enabled chain, so the rows differ only in `account`.
        bytes32[] memory chainRefs = _enabledChainRefs();
        for (uint256 i = 0; i < accounts.length; i++) {
            address who = accounts[i];
            FinalIdentityRegistry.Identity memory id = registry.identityOf(who);
            if (!id.registered) revert UnknownKey(TREE_ACCOUNTS, accountKeyFor(who));
            (bytes32 la, bytes32 lt, bytes32 ra, bytes32 rt) = registry.keyCommitments(who);
            (bytes32 lk, bytes32 rk) = registry.kemCommitments(who);
            ChainAccount[] memory table = new ChainAccount[](chainRefs.length);
            for (uint256 c = 0; c < chainRefs.length; c++) {
                table[c] = ChainAccount({chainRef: chainRefs[c], account: bytes32(uint256(uint160(who)))});
            }
            AccountStateLeaf memory leaf = AccountStateLeaf({
                wallet: who,
                liveAccess: la,
                liveTransaction: lt,
                recoveryAccess: ra,
                recoveryTransaction: rt,
                liveKem: lk,
                recoveryKem: rk,
                // A service reaches every chain the registry has enabled, at
                // its own address, and is never dormant: dormancy measures an
                // ABSENT holder, and these identities have no holder to be
                // absent.
                deployedChains: table,
                dormantChains: 0,
                owner: who,
                // Every identity here is PQ by construction — there is no other
                // kind of key in this registry.
                pqEnabled: true,
                // Revocation is a leaf that CHANGES, not one that disappears.
                // A consumer holding an old proof gets a stale `false`, which is
                // why the round is the thing to pin.
                frozen: id.revoked,
                version: id.version
            });
            _set(TREE_ACCOUNTS, BRANCH_MAIN, accountKeyFor(who), accountStateLeafHash(leaf));
        }
        _bump(TREE_ACCOUNTS, accounts.length);
    }

    /// @notice The tree-8 slot key an identity occupies.
    /// @dev Its own domain, separate from the tree-1 account key, so one
    ///      account's admission row and its state row can never collide.
    /// @param account The identity.
    /// @return The tree-8 branch-1 key.
    function identityKeyFor(address account) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_IDENTITY_TREE_KEY, account));
    }

    /**
     * @notice Project identities into tree 8 — the wallet-creation admission
     *         set whose live root every execution chain anchors as its
     *         `currentIdentityRoot`.
     *
     * @dev The leaf VALUE is the registry's own verdict —
     * `FinalIdentityRegistry.identityTreeLeafOf`: the execution chains'
     * identity leaf while the identity stands, zero once it does not. Derived
     * there rather than here because every input (serial, the six key
     * commitments, standing, the CA depth pair) is registry storage, and this
     * contract sits against EIP-170 while the registry does not.
     *
     * Permissionless, for exactly {syncIdentities}' reason: every fact
     * written here was authorized when it entered the registry, so this adds
     * no authority and only projects. The registry itself calls it same-tx on
     * every identity mutation (register, rotate, roles, revoke, LMS-key ops),
     * which is what makes the root CONTINUOUS; the open door additionally lets
     * anyone retire a leaf whose standing lapsed by TIME — expiry moves no
     * registry storage, so no mutation hook can ever fire for it.
     *
     * There is no quorum door and no writer seat (both raw doors refuse this
     * tree), so the strongest thing any caller can do here is copy the
     * registry's own verdict.
     * @param accounts The identities to project. An unregistered account
     *        projects the registry's zero verdict, which retires its leaf.
     */
    function syncIdentityLeaves(address[] calldata accounts) external {
        for (uint256 i = 0; i < accounts.length; i++) {
            _set(TREE_IDENTITY, BRANCH_MAIN, identityKeyFor(accounts[i]), registry.identityTreeLeafOf(accounts[i]));
        }
        _bump(TREE_IDENTITY, accounts.length);
    }

    /**
     * @notice Per-tree quorum health: can each configured tree still be written?
     * @dev A threshold above the live member count is not a strict quorum, it is
     * a tree that reverts forever with nothing naming the roster as the cause.
     * `configureTree` refuses to create that state, but revocation can arrive at
     * it later — revocation must never be blocked on quorum arithmetic, so the
     * check has to be something monitoring reads rather than something the
     * contract enforces after the fact.
     * @return live Members currently holding each tree's writer role; zero for
     *         an unconfigured tree, which is not the same as a starved one.
     * @return required Each tree's threshold, indexed by tree id.
     * @return ok Whether each tree can still be written. An unconfigured tree
     *         reports `true`: it is closed, not starved.
     */
    function quorumHealth()
        external
        view
        returns (uint256[] memory live, uint256[] memory required, bool[] memory ok)
    {
        live = new uint256[](TREE_COUNT + 1);
        required = new uint256[](TREE_COUNT + 1);
        ok = new bool[](TREE_COUNT + 1);
        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            required[t] = threshold[t];
            live[t] = required[t] == 0 ? 0 : registry.liveMemberCount(writerRole[t]);
            ok[t] = required[t] == 0 || live[t] >= required[t];
        }
    }

    // -------------------------------------------------------------- internal

    /// @notice The chain set a service account's `deployedChains` table is built from.
    /// @dev The enabled chain references `chainSource` knows, or none if it is
    ///      unset. Read through the narrow interface so this contract need not
    ///      import the registry that imports it. An unset source answers an
    ///      empty list rather than reverting, because a plane whose registry is
    ///      not yet seeded must still be able to project its identities.
    /// @return The enabled chain references, or an empty list when unset.
    function _enabledChainRefs() private view returns (bytes32[] memory) {
        address source = chainSource;
        if (source == address(0)) return new bytes32[](0);
        return IChainSource(source).enabledChainRefs();
    }

    /// @notice Refuse a tree id outside `1 .. TREE_COUNT`.
    /// @dev Trees are 1-indexed so a tree id doubles as its position in the
    ///      round tree; id 0 is the unused position there and not a tree here.
    /// @param treeId The id to check.
    function _assertTree(uint8 treeId) private pure {
        if (treeId == 0 || treeId > TREE_COUNT) revert UnknownTree(treeId);
    }

    /// @notice Refuse a branch id no slot can encode.
    /// @dev The bound is the branch COUNT, not the count of branches in use: an
    ///      unused branch is a legal, empty subtree.
    /// @param branch The id to check.
    function _assertBranch(uint8 branch) private pure {
        if (branch >= BRANCH_COUNT) revert UnknownBranch(branch);
    }

    /// @notice Refuse a branch a quorum or a writer contract may not write.
    /// @dev A branch a quorum or a writer may write: any but the config branch.
    ///      Branch 0 belongs to the configuration authority on every tree, so
    ///      the refusal is structural rather than per-tree.
    /// @param treeId The tree, carried so the revert names it.
    /// @param branch The branch being written.
    function _assertDataBranch(uint8 treeId, uint8 branch) private pure {
        _assertBranch(branch);
        if (branch == BRANCH_CONFIG) revert ConfigBranchReserved(treeId);
    }

    /// @notice Advance a tree's write counter and announce the new root.
    /// @dev Version + event, the tail of every write door. Called AFTER the
    ///      leaves have settled, so the event carries the root a reader will
    ///      see, and the counter is what {publishRound} compares to decide
    ///      whether a round would carry anything new.
    /// @param treeId The tree that moved.
    /// @param count Leaves in the batch, for the event.
    function _bump(uint8 treeId, uint256 count) private {
        uint64 v = treeVersion[treeId] + 1;
        treeVersion[treeId] = v;
        emit LeavesSet(treeId, count, liveRoot[treeId], v);
    }

    /// @notice The one internal-node hash every tree, branch and round shares.
    /// @dev `keccak256(0x01 ‖ lo ‖ hi)`, the pair sorted — the one node hash.
    ///      Sorting is what makes a proof position-agnostic, so it carries no
    ///      direction bits; the 0x01 tag is what keeps an internal node from
    ///      ever colliding with a leaf, which is hashed under 0x00.
    /// @param a One child.
    /// @param b The other child.
    /// @return The parent node.
    function _pair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        (bytes32 lo, bytes32 hi) = a < b ? (a, b) : (b, a);
        return keccak256(abi.encodePacked(bytes1(0x01), lo, hi));
    }

    /// @notice Collect the siblings from a slot up a given number of levels.
    /// @dev The sibling path from a slot up `height` levels. One routine serves
    ///      the branch proof and the tree proof; only the height differs, which
    ///      is why the two can never disagree about a shared prefix.
    /// @param treeId The tree to read.
    /// @param idx The starting slot. Consumed as the walk climbs.
    /// @param height How many levels to climb.
    /// @return path The siblings, lowest level first.
    function _path(uint8 treeId, uint256 idx, uint256 height) private view returns (bytes32[] memory path) {
        path = new bytes32[](height);
        for (uint256 l = 0; l < height; l++) {
            path[l] = _nodeAt(treeId, l, idx ^ 1);
            idx >>= 1;
        }
    }

    /// @notice Lay the tree roots out as the leaves of the round tree.
    /// @dev The forest's leaves: the tree roots at their positions, the
    ///      empty tree at the rest. Tree `t` sits at position `t`, so the
    ///      round proof's index is the tree id with no translation, and the
    ///      unused positions hold the empty TREE root rather than zero — they
    ///      are genuinely empty trees, and hashing them as zero would make the
    ///      round root unreproducible off chain.
    /// @param roots The round's tree roots, indexed by tree id.
    /// @return level The `1 << FOREST_BITS` leaves of the round tree.
    function _forestLeaves(bytes32[TREE_COUNT + 1] memory roots) private view returns (bytes32[] memory level) {
        level = new bytes32[](1 << FOREST_BITS);
        for (uint256 p = 0; p < level.length; p++) {
            level[p] = (p >= 1 && p <= TREE_COUNT) ? roots[p] : _zero[DEPTH];
        }
    }

    /// @notice Fold the round tree's leaves down to the round root.
    /// @dev Fold a power-of-two level to its root, in place. The input array is
    ///      overwritten, so the caller must not reuse it afterwards.
    /// @param level The level to fold. Length must be a power of two.
    /// @return The root of that level.
    function _foldForest(bytes32[] memory level) private pure returns (bytes32) {
        for (uint256 n = level.length; n > 1; n >>= 1) {
            for (uint256 i = 0; i < n / 2; i++) {
                level[i] = _pair(level[2 * i], level[2 * i + 1]);
            }
        }
        return level[0];
    }

    /// @notice Place one leaf, assigning the key a permanent slot on first sight.
    /// @dev The single point every write door funnels through, which is what
    ///      makes the slot discipline unconditional: a key is handed the next
    ///      free position in its branch, remembered in both directions, and
    ///      keeps it for the life of the contract. A key that already holds a
    ///      slot in a DIFFERENT branch is refused rather than moved — moving it
    ///      would silently invalidate every proof anyone holds for it.
    ///
    ///      The update then rehashes exactly `DEPTH` nodes up the leaf's own
    ///      path, so the cost of a write is the height of the tree and not the
    ///      number of leaves in it. This is also where the tree's shape comes
    ///      from: fixed height, zero-padded siblings, insertion-ordered slots.
    /// @param treeId The tree to write.
    /// @param branch The branch the key belongs to.
    /// @param key The domain key.
    /// @param leaf The raw (untagged) value to store.
    function _set(uint8 treeId, uint8 branch, bytes32 key, bytes32 leaf) private {
        uint256 s = _slotPlusOne[treeId][key];
        uint256 idx;
        if (s == 0) {
            uint256 used = _branchSlotsUsed[treeId][branch];
            if (used >= BRANCH_CAPACITY) revert BranchFull(treeId, branch);
            idx = (uint256(branch) << BRANCH_DEPTH) | used;
            _branchSlotsUsed[treeId][branch] = used + 1;
            slotsUsed[treeId] += 1;
            _slotPlusOne[treeId][key] = idx + 1;
            _keyAt[treeId][idx] = key;
        } else {
            idx = s - 1;
            uint8 have = uint8(idx >> BRANCH_DEPTH);
            if (have != branch) revert BranchMismatch(treeId, key, have, branch);
        }

        _leaf[treeId][idx] = leaf;

        bytes32 cursor = keccak256(abi.encodePacked(bytes1(0x00), leaf));
        for (uint256 l = 0; l < DEPTH; l++) {
            cursor = _pair(cursor, _nodeAt(treeId, l, idx ^ 1));
            idx >>= 1;
            _node[treeId][l + 1][idx] = cursor;
        }
        liveRoot[treeId] = cursor;
    }

    /// @notice One node of a tree, at any level, with empty positions filled in.
    /// @dev Level 0 is derived from the leaf store rather than duplicated into
    /// `_node`, so there is one place a leaf lives and no way for the two to
    /// disagree. Unset positions fall through to the empty-subtree hash — the
    /// zero padding that gives the tree its fixed height, and the reason an
    /// off-chain rebuild must pad to the same height to reach the same root.
    /// @param treeId The tree to read.
    /// @param level The level, 0 being the leaves.
    /// @param index The position at that level.
    /// @return The node, or the empty-subtree hash when nothing was written there.
    function _nodeAt(uint8 treeId, uint256 level, uint256 index) private view returns (bytes32) {
        if (level == 0) {
            return keccak256(abi.encodePacked(bytes1(0x00), _leaf[treeId][index]));
        }
        bytes32 v = _node[treeId][level][index];
        return v == bytes32(0) ? _zero[level] : v;
    }

    // ------------------------------------------------------------------ sweep

    /// @notice The registry the inherited sweep authority resolves members through.
    /// @dev This contract's configuration gate reads the membership registry it
    /// was constructed against, so the sweep authority reads the same one. One
    /// registry for both means a member removed from the roster loses the sweep
    /// at the same instant it loses everything else.
    /// @return The immutable identity registry pinned at construction.
    function _sweepRegistry() internal view override returns (FinalIdentityRegistry) {
        return registry;
    }

    /// @dev Nothing is reserved because nothing is owed: this contract has no
    /// payable entrypoint and no custody line — it records, it does not hold.
    /// Anything it carries arrived by accident and is sweepable in full.
}

contracts/utils/FinalBundleTree.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may link against and call this bundle-root fold, and
//    may build an off-chain bundle assembler that reproduces it, as part of the
//    Final DeFi Protocol.
// 2. Protocol operators, integrators, and end users may have bundles rooted and
//    anchored through any Final DeFi surface that embeds it.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this bundle-root fold or a competing bundle
//    anchoring scheme without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

/// @notice Thrown when a fold is attempted over an empty leaf layer, which commits to nothing.
/// @dev Declared at file level rather than inside the library so that every surface refusing an empty bundle
///      reverts with the SAME selector: `PqAnchorModule` and `PaymasterModule` on an execution chain, and the
///      bundle log on Final Chain. An off-chain caller decoding the failure therefore gets one answer no
///      matter which side refused, and cannot mistake an empty bundle for an unrelated fault.
error EmptyBundleTree();

/**
 * @title Final Bundle Tree
 * @notice The single definition of the fold from a bundle's intent leaves to its bundle root.
 * @dev Two chains compute this root independently and must agree exactly. An execution chain's gateway folds
 *      it from the intents it is about to dispatch; the bundle log on Final Chain folds it from the leaves
 *      being anchored, so that the payload it stores is RECOMPUTED rather than asserted by whoever assembled
 *      the transaction. A divergence between the two would not surface as a failed verification — it would be
 *      a bundle that anchors on Final Chain and can never execute anywhere, with nothing naming the cause.
 *      That is why the fold lives here once instead of being written twice. Any off-chain assembler that
 *      predicts a bundle root must reproduce it identically, down to the tag bytes.
 *
 *      ## Preimage layouts
 *
 *      - interior pair: `keccak256(POS_NODE(1) || DOMAIN_PQ_NODE(32) || left(32) || right(32))`   — 97 bytes
 *      - odd-tail lift: `keccak256(POS_LIFT(1) || DOMAIN_PQ_NODE(32) || value(32))`               — 65 bytes
 *
 *      `DOMAIN_PQ_NODE` is `keccak256("FINAL_PQ_BUNDLE_NODE_v01")`. It separates this commitment space from
 *      every other Merkle surface in the system, so a node from another tree cannot be replayed as a node here
 *      even when the raw hashes line up.
 *
 *      ## Tree shape
 *
 *      Insertion-ordered and POSITIONAL: leaves are folded left to right in the order given, pairs are never
 *      sorted, and the tree is neither fixed-depth nor zero-padded. Each layer takes elements two at a time;
 *      an element left over at the end of a layer is LIFTED to the next layer under its own tag rather than
 *      paired with itself or carried through untouched. Folding continues until one element remains, which is
 *      the root; a single-leaf layer roots to that leaf's own hash unchanged.
 *
 *      The lift tag is the whole point of the odd-tail branch. `POS_LIFT` differs from `POS_NODE`, and lifting
 *      changes the value, so `Root([a, b, c]) != Root([a, b, c, c])`. Under a self-pairing convention those
 *      two layers collapse to the same root and a bundle could be re-presented with its last intent silently
 *      duplicated. Carrying the odd element through unchanged would be worse still, letting a leaf value and
 *      an interior value occupy the same position.
 *
 *      This library folds a layer and nothing else. It does not build leaves, does not know what a leaf
 *      commits to, and takes no view on whether the layer it was handed is the right one — those belong to the
 *      surface that owns the intent leaf preimage.
 */
library FinalBundleTree {
    /// @notice One-byte position tag opening the preimage of an interior node built from two children.
    /// @dev Nonzero, and distinct from {POS_LIFT}, so that neither an intent leaf nor a lifted element can be
    ///      reinterpreted as a paired node.
    uint8 internal constant POS_NODE = 0x01;
    /// @notice One-byte position tag opening the preimage of a lifted odd trailing element.
    /// @dev Distinct from {POS_NODE} so a lift and a pair are different commitments even over the same bytes.
    ///      This is what makes an odd layer and the same layer with its tail duplicated root differently.
    uint8 internal constant POS_LIFT = 0x02;

    /// @notice Domain separator carried in every interior preimage of a bundle tree, paired and lifted alike.
    /// @dev `keccak256("FINAL_PQ_BUNDLE_NODE_v01")`. Every producer of a bundle root — this gateway, the
    ///      Final Chain bundle log, and any off-chain assembler — must hash this exact value in this exact
    ///      position, or the roots diverge and the bundle anchors to something that can never execute.
    bytes32 internal constant DOMAIN_PQ_NODE = keccak256("FINAL_PQ_BUNDLE_NODE_v01");

    /**
     * @notice Fold a layer of leaves into the single root that commits to all of them, in order.
     * @dev Takes a LAYER rather than a bundle, deliberately. A bundle may carry both post-quantum and
     *      pre-quantum rows, and only the post-quantum subset is anchored: Final Chain never saw the other
     *      rows and cannot commit to leaves it did not build. Passing the subset lets both sides fold exactly
     *      the same list.
     *
     *      Order is significant and is never normalised. The caller is responsible for presenting leaves in
     *      the same order the anchoring side used; a permuted layer folds to a different root and fails
     *      closed rather than verifying against some other arrangement.
     *
     *      Mutates nothing the caller can observe. `leaves` is read on the first pass and never written, and
     *      every subsequent layer is a freshly allocated array, so the caller's array survives the call intact
     *      and may be reused.
     * @param leaves The ordered layer to fold; must be non-empty.
     * @return root The bundle root committing to exactly these leaves in exactly this order.
     */
    function foldLeaves(bytes32[] memory leaves) internal pure returns (bytes32 root) {
        if (leaves.length == 0) revert EmptyBundleTree();
        bytes32[] memory layer = leaves;
        while (layer.length > 1) {
            uint256 srcLen = layer.length;
            uint256 pairCount = srcLen >> 1;
            uint256 oddTail = srcLen & 1;
            bytes32[] memory next = new bytes32[](pairCount + oddTail);
            for (uint256 i = 0; i < pairCount; i++) {
                next[i] = keccak256(
                    abi.encodePacked(POS_NODE, DOMAIN_PQ_NODE, layer[i << 1], layer[(i << 1) | 1])
                );
            }
            if (oddTail == 1) {
                next[pairCount] = keccak256(
                    abi.encodePacked(POS_LIFT, DOMAIN_PQ_NODE, layer[srcLen - 1])
                );
            }
            layer = next;
        }
        return layer[0];
    }
}

contracts/utils/FinalMmr.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may link against and call this inclusion-proof
//    library, and may build an off-chain log that produces proofs it accepts,
//    as part of the Final DeFi Protocol.
// 2. Protocol operators, integrators, and end users may have their inclusion
//    proofs verified through any Final DeFi surface that embeds it.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this inclusion-proof library or a competing
//    append-only-log anchor without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

/**
 * @title Final MMR
 * @notice Inclusion and consistency verification against an append-only log — a Merkle mountain range over the
 *         log's perfect subtrees, positional and domain-separated.
 * @dev The algorithm is the RFC 6962 inclusion decomposition, which verifies a leaf against the Merkle tree
 *      hash of a log of ARBITRARY size rather than only a power-of-two one. A log of `size` leaves decomposes
 *      into perfect subtrees whose roots are its peaks; a proof walks the leaf up through its own subtree and
 *      then bags the peaks to its left.
 *
 *      ## Canonical layout — an off-chain log MUST match this byte for byte
 *
 *      - leaf: `keccak256(0x00 || domain || payload)`                  — 65 bytes
 *      - node: `keccak256(0x01 || domain || left || right)`            — 97 bytes
 *
 *      `left` is ALWAYS the lower-index child. This construction is positional and is never sorted: position
 *      is exactly what an inclusion proof claims, so sorting the pair would discard the claim.
 *
 *      The one-byte `0x00` / `0x01` tags are the load-bearing safety property. Without them a 64-byte interior
 *      preimage could be re-presented as a leaf preimage and vice versa, which is the second-preimage class
 *      that untagged and sorted constructions leave open. `domain` is the caller's commitment-space separator,
 *      so two logs that share this library cannot have a proof from one accepted by the other even when both
 *      commit to the same payload bytes.
 *
 *      ## Proof shape is derived, never supplied
 *
 *      A proof is `(index, size, proof[])`, where `index` is the 0-based leaf position and `size` is the total
 *      leaf count the root commits to. The path's length AND its left/right schedule are fully determined by
 *      `(index, size)`:
 *
 *      - `inner  = bitLength(index ^ (size - 1))` positional siblings, each combined on the side that bit `i`
 *        of `index` dictates, followed by
 *      - `border = popcount(index >> inner)` perfect-subtree peaks, each folded in as a LEFT sibling, which is
 *        the right-to-left peak bag.
 *
 *      Because both numbers come from `(index, size)` and never from the prover, a padded, truncated or
 *      reshaped proof cannot silently validate: `computeRoot` reverts on a length mismatch and on an
 *      out-of-range index rather than returning some other root.
 *
 *      ## A proof is specific to the SIZE it was produced against
 *
 *      This is the property that most often surprises an integrator. An append-only log has a different root
 *      at every size, and the decomposition above changes shape as `size` grows: appending leaves can change
 *      how many peaks sit to the left of a given index, and therefore both `inner` and `border`. A proof
 *      generated against size `n` is a proof about the root at size `n` and nothing else. Presented against
 *      the root at a later size it does not verify — usually by reverting with a length mismatch, and where
 *      the two shapes happen to coincide by recomputing a root that simply is not equal to the anchor. It is
 *      never accepted, but it is also never repairable by retrying, so an off-chain producer must generate the
 *      proof against the exact size the consuming contract has anchored, not against the head of the log.
 *      That is why every entrypoint here takes `size` as an explicit argument: the verifier has no way to
 *      discover it and must be told which historical root the proof is about.
 *
 *      ## A head must extend the head before it
 *
 *      Monotonicity of `size` says nothing about the leaves: a root at `size + 1` can commit to any history at
 *      all. {verifyConsistency} closes that with the RFC 6962 consistency proof — the `~log2(size)` subtree
 *      roots that bridge two heads — so a contract holding `(root, size)` can require that the head it is
 *      handed next commits to every leaf the current one does, in the same positions, plus appended ones.
 *      Whoever advances the head can then append and can never rewrite.
 *
 *      Pure library: no storage, no external calls, no upgrade surface. Hashing goes through `abi.encodePacked`
 *      rather than hand-written assembly, because the cost is one inclusion proof per bundle — roughly log2 of
 *      the log size in hashes — and legibility of the exact preimage is worth more here than the saved gas.
 */
library FinalMmr {
    /// @notice One-byte tag that opens every leaf preimage, ahead of `domain` and the payload.
    /// @dev Distinct from {NODE_TAG} so a 64-byte interior preimage can never be reinterpreted as a leaf.
    uint8 internal constant LEAF_TAG = 0x00;
    /// @notice One-byte tag that opens every interior-node preimage, ahead of `domain` and the two children.
    /// @dev Distinct from {LEAF_TAG} for the same reason, in the other direction.
    uint8 internal constant NODE_TAG = 0x01;

    /// @notice Thrown when the leaf index is not strictly less than the `size` the root commits to.
    /// @param index The 0-based leaf position that was supplied.
    /// @param size The committed leaf count the proof was checked against.
    error MmrIndexOutOfRange(uint256 index, uint256 size);
    /// @notice Thrown when the supplied path length does not equal the one `(index, size)` implies.
    /// @dev The overwhelmingly common cause is a proof produced against a different log size than the one the
    ///      verifying contract has anchored.
    /// @param expected Path length derived from `(index, size)`.
    /// @param actual Path length that was supplied.
    error MmrProofLengthMismatch(uint256 expected, uint256 actual);
    /// @notice Thrown when `size` is zero: an empty log commits to no leaves, so nothing can be included in it.
    error MmrEmptyLog();
    /// @notice Thrown when a consistency proof cannot bridge the `(oldSize, newSize)` pair it was supplied for —
    ///         too few or too many elements for those two shapes.
    /// @param oldSize Leaf count of the anchored head.
    /// @param newSize Leaf count of the proposed head.
    /// @param supplied Elements in the supplied path.
    error MmrConsistencyProofMalformed(uint256 oldSize, uint256 newSize, uint256 supplied);
    /// @notice Thrown when the proposed head does not hold more leaves than the anchored one: there is no growth
    ///         for a consistency proof to speak about.
    /// @param oldSize Leaf count of the anchored head.
    /// @param newSize Leaf count of the proposed head.
    error MmrSizeNotGrowing(uint256 oldSize, uint256 newSize);

    /// @notice Tag and hash a payload into an MMR leaf: `keccak256(0x00 || domain || payload)`, 65 bytes.
    /// @dev The single place a leaf preimage is built, so the producer of a log and the verifier of a proof
    ///      cannot drift. An off-chain log that builds leaves any other way produces proofs nothing accepts.
    /// @param domain Commitment-space separator; must be the same value the proof will later be checked under.
    /// @param payload The value being committed as a leaf, such as a bundle root.
    /// @return leaf The tagged, domain-separated leaf hash.
    function hashLeaf(bytes32 domain, bytes32 payload) internal pure returns (bytes32 leaf) {
        return keccak256(abi.encodePacked(LEAF_TAG, domain, payload));
    }

    /// @notice Hash one interior node: `keccak256(0x01 || domain || left || right)`, 97 bytes.
    /// @dev Positional, never sorted — `left` is the lower-index child and the caller is responsible for
    ///      putting it there. Private because the ordering decision belongs to {computeRoot}, which derives it
    ///      from `index`; exposing it would let a caller choose an order the proof shape does not imply.
    /// @param domain Commitment-space separator, identical to the one used for the leaves below this node.
    /// @param left The lower-index child.
    /// @param right The higher-index child.
    /// @return node The tagged, domain-separated interior-node hash.
    function _hashNode(bytes32 domain, bytes32 left, bytes32 right) private pure returns (bytes32 node) {
        return keccak256(abi.encodePacked(NODE_TAG, domain, left, right));
    }

    /// @notice Recompute the append-only-log root that a leaf's inclusion proof implies.
    /// @dev Fails closed. An out-of-range index, an empty log, or a path whose length does not match the
    ///      `(index, size)` decomposition all revert rather than returning some other root, so a caller can
    ///      never mistake a structurally invalid proof for a proof of a different leaf. What this function does
    ///      NOT do is decide trust: it returns a root, and the caller compares it against the anchor it holds.
    ///
    ///      The result is only meaningful for the `size` supplied. See the size-specificity note on this
    ///      library: a path built against a different log size is rejected here, not silently reinterpreted.
    /// @param leaf The leaf hash, already tagged by {hashLeaf}; passing an untagged payload verifies nothing.
    /// @param index 0-based position of the leaf in the log.
    /// @param size Total leaf count the target root commits to.
    /// @param domain Commitment-space separator; must equal the one the leaf was tagged with.
    /// @param proof Audit path: `inner` positional siblings, then `border` peak hashes.
    /// @return root The recomputed log root, for the caller to compare against its anchor.
    function computeRoot(
        bytes32 leaf,
        uint256 index,
        uint256 size,
        bytes32 domain,
        bytes32[] memory proof
    ) internal pure returns (bytes32 root) {
        if (size == 0) revert MmrEmptyLog();
        if (index >= size) revert MmrIndexOutOfRange(index, size);

        // The RFC 6962 decomposition. `inner` counts the levels at which this leaf still has a sibling inside
        // its own (possibly imperfect) subtree; `border` counts the completed peaks to its left that must be
        // bagged in afterwards. Both come from `(index, size)`, so the prover has no say in the path's shape.
        uint256 inner = bitLength(index ^ (size - 1));
        uint256 border = popcount(index >> inner);
        uint256 expectedLen = inner + border;
        if (proof.length != expectedLen) revert MmrProofLengthMismatch(expectedLen, proof.length);

        bytes32 res = leaf;
        // Phase 1: `inner` positional combines. Bit `i` of `index` decides the side — 0 puts the running hash
        // on the left, 1 on the right — so the path carries no direction bits a prover could flip.
        for (uint256 i = 0; i < inner; ) {
            bytes32 sibling = proof[i];
            if ((index >> i) & 1 == 0) {
                res = _hashNode(domain, res, sibling); // running hash is the left child
            } else {
                res = _hashNode(domain, sibling, res); // running hash is the right child
            }
            unchecked { ++i; }
        }
        // Phase 2: the peak bag. Every remaining element is a completed perfect-subtree peak to the LEFT of
        // this leaf's subtree, so each is folded in as the left operand, right to left.
        for (uint256 i = inner; i < expectedLen; ) {
            res = _hashNode(domain, proof[i], res);
            unchecked { ++i; }
        }
        return res;
    }

    /// @notice Verify that `leaf` is the `index`-th of `size` leaves committed to by `root`.
    /// @dev A thin wrapper over {computeRoot}; the leaf must already be tagged by {hashLeaf}. `false` means one
    ///      thing only — the recomputed root differs from `root`. A structurally invalid proof does not reach
    ///      that comparison: it reverts inside {computeRoot}, so a caller cannot conflate "not included" with
    ///      "malformed" and must not treat `false` as evidence that the proof was well formed.
    /// @param leaf The leaf hash, already tagged by {hashLeaf}.
    /// @param index 0-based position of the leaf in the log.
    /// @param size Total leaf count `root` commits to; the proof is specific to this value.
    /// @param domain Commitment-space separator; must equal the one the leaf was tagged with.
    /// @param proof Audit path: `inner` positional siblings, then `border` peak hashes.
    /// @param root The trusted anchor to compare against.
    /// @return Whether the proof places `leaf` at `index` in the log `root` commits to.
    function verifyInclusion(
        bytes32 leaf,
        uint256 index,
        uint256 size,
        bytes32 domain,
        bytes32[] memory proof,
        bytes32 root
    ) internal pure returns (bool) {
        return computeRoot(leaf, index, size, domain, proof) == root;
    }

    /// @notice Verify inclusion of a raw, untagged `payload`, tagging it into a leaf internally.
    /// @dev The entrypoint to prefer when the caller holds the committed value rather than a leaf hash: it
    ///      makes it impossible to present an interior node hash as a leaf, because the `0x00` tag is applied
    ///      here and cannot be supplied by the caller.
    /// @param payload The raw value committed as a leaf, such as a bundle root.
    /// @param index 0-based position of the leaf in the log.
    /// @param size Total leaf count `root` commits to; the proof is specific to this value.
    /// @param domain Commitment-space separator, applied to both the leaf and every node.
    /// @param proof Audit path: `inner` positional siblings, then `border` peak hashes.
    /// @param root The trusted anchor to compare against.
    /// @return Whether the proof places `payload` at `index` in the log `root` commits to.
    function verifyInclusionOfPayload(
        bytes32 payload,
        uint256 index,
        uint256 size,
        bytes32 domain,
        bytes32[] memory proof,
        bytes32 root
    ) internal pure returns (bool) {
        return computeRoot(hashLeaf(domain, payload), index, size, domain, proof) == root;
    }

    /// @notice Verify that the log `newRoot` commits to at `newSize` leaves EXTENDS the log `oldRoot` commits to
    ///         at `oldSize`: the first `oldSize` leaves are the same, in the same positions, and nothing the old
    ///         head held has been rewritten.
    /// @dev The RFC 6962 consistency proof, checked with the RFC 9162 §2.1.4.2 procedure over this library's
    ///      tagged, domain-separated, positional nodes. The path is the set of subtree roots that bridge the two
    ///      heads — about `log2(newSize)` hashes — and both roots are recomputed from it: `oldRoot` from the old
    ///      tree's right spine, `newRoot` from that spine plus the appended subtrees. A path that reconstructs
    ///      both is a proof that the new head is the old head with leaves appended and nothing else.
    ///
    ///      Fails closed like {computeRoot}: a path whose length does not fit the `(oldSize, newSize)` pair
    ///      reverts rather than returning `false`, so a caller can never mistake a malformed path for a rewritten
    ///      history or the other way round. `false` means exactly one thing — the reconstructed roots do not meet
    ///      the anchors.
    ///
    ///      Two sizes are refused rather than proven. An empty old head (`oldSize == 0`) has no history to be
    ///      consistent with; what the first head may be is the caller's decision. A non-growing size is refused
    ///      because the procedure is undefined for it, and the caller's own monotonicity check names that failure.
    ///
    ///      The producer is the RFC 6962 §2.1.2 recursion (`SUBPROOF`), and it is also derivable from ONE
    ///      inclusion proof: the path for leaf `oldSize - 1` at `newSize`, with its lowest `t` siblings (`t` the
    ///      trailing zero bits of `oldSize`) folded into the old tree's right-spine node and that node prepended
    ///      unless `oldSize` is a power of two. A log that serves inclusion proofs therefore serves this one too.
    /// @param oldRoot The anchored head's root.
    /// @param oldSize Leaf count `oldRoot` commits to; must be non-zero.
    /// @param newRoot The proposed head's root.
    /// @param newSize Leaf count `newRoot` commits to; must exceed `oldSize`.
    /// @param domain Commitment-space separator both heads were built under.
    /// @param proof The consistency path in RFC 6962 order: the old tree's right-spine node first, when the old
    ///   tree is not perfect, then one sibling per level upward.
    /// @return Whether `newRoot` at `newSize` extends `oldRoot` at `oldSize`.
    function verifyConsistency(
        bytes32 oldRoot,
        uint256 oldSize,
        bytes32 newRoot,
        uint256 newSize,
        bytes32 domain,
        bytes32[] memory proof
    ) internal pure returns (bool) {
        if (oldSize == 0) revert MmrEmptyLog();
        if (newSize <= oldSize) revert MmrSizeNotGrowing(oldSize, newSize);

        // RFC 9162 §2.1.4.2. `fn` and `sn` walk the two trees' node indices upward from the leaf level; the
        // path is consumed in the order the RFC prover emits it.
        uint256 fn = oldSize - 1;
        uint256 sn = newSize - 1;
        // The old tree's complete right spine says nothing its root does not already say: skip those levels.
        while (fn & 1 == 1) {
            fn >>= 1;
            sn >>= 1;
        }

        uint256 i;
        bytes32 fr;
        bytes32 sr;
        if (oldSize & (oldSize - 1) == 0) {
            // A perfect old tree IS its root, so the prover omits it and the verifier supplies it.
            fr = oldRoot;
            sr = oldRoot;
        } else {
            if (proof.length == 0) revert MmrConsistencyProofMalformed(oldSize, newSize, 0);
            fr = proof[0];
            sr = proof[0];
            i = 1;
        }
        for (; i < proof.length; ) {
            // A path that keeps going after the new root has been reached is not a path in this tree.
            if (sn == 0) revert MmrConsistencyProofMalformed(oldSize, newSize, proof.length);
            bytes32 c = proof[i];
            if (fn & 1 == 1 || fn == sn) {
                // A LEFT sibling of both walks — a subtree the old head already held, folded into both roots.
                fr = _hashNode(domain, c, fr);
                sr = _hashNode(domain, c, sr);
                if (fn & 1 == 0) {
                    while (fn != 0 && fn & 1 == 0) {
                        fn >>= 1;
                        sn >>= 1;
                    }
                }
            } else {
                // A RIGHT sibling of the new walk only — an appended subtree the old head never saw.
                sr = _hashNode(domain, sr, c);
            }
            fn >>= 1;
            sn >>= 1;
            unchecked { ++i; }
        }
        // The walk must end exactly at the new root, and both reconstructions must meet their anchors.
        if (sn != 0) revert MmrConsistencyProofMalformed(oldSize, newSize, proof.length);
        return fr == oldRoot && sr == newRoot;
    }

    /// @notice Bit length of `x`: the position of its highest set bit plus one, and `0` when `x` is zero.
    /// @dev `internal` rather than `private` on purpose, so that a contract producing proofs derives their
    ///      shape with the SAME arithmetic the verifier uses to check it. Two independent copies of this
    ///      decomposition means proofs built to one shape and checked against another, and the resulting
    ///      failure names neither side.
    /// @param x Value to measure.
    /// @return n Number of significant bits in `x`.
    function bitLength(uint256 x) internal pure returns (uint256 n) {
        while (x != 0) {
            x >>= 1;
            unchecked { ++n; }
        }
    }

    /// @notice Population count of `x`: how many of its bits are set.
    /// @dev `internal` for the same reason as {bitLength} — the producer of a proof and its verifier must count
    ///      peaks with one implementation, not two.
    /// @param x Value to measure.
    /// @return c Number of set bits in `x`.
    function popcount(uint256 x) internal pure returns (uint256 c) {
        while (x != 0) {
            unchecked {
                c += x & 1;
                x >>= 1;
            }
        }
    }
}

contracts/utils/FinalSweep.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may inherit this sweep surface into contracts that
//    integrate with the Final DeFi Protocol, in order to recover assets sent to
//    them by mistake.
// 2. Protocol operators and integrators may call the sweep entrypoints it
//    declares, subject to each inheriting contract's own authority and reserved
//    balance rules, as part of their integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this sweep surface or a competing asset-recovery
//    plane derived from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

/// @notice The asset kinds a sweep can move. `Native` ignores `asset` and
/// `id`; `Erc20` ignores `id`; `Erc721` reads `id` as the token id and moves
/// exactly one; `Erc1155` reads both.
enum SweepKind { Native, Erc20, Erc721, Erc1155 }

/**
 * @title Final Sweep
 * @notice One sweep surface, on every contract of ours that can end up holding
 *         an asset it does not owe to anybody.
 *
 * @dev Assets arrive at protocol contracts that were never meant to hold them:
 * a bridge delivers to the wrong leg, a user sends an ERC-20 to a registry, an
 * airdrop lands on the gateway, an NFT is safe-transferred into the vault. Left
 * alone that value is destroyed. The sweep is how it comes back — and the
 * single rule it must never break is that a sweep moves SURPLUS and nothing
 * else.
 *
 * Three seams make that rule per-contract:
 *
 *  - `_requireSweepAuthority()` — the treasury role, expressed in whatever
 *    access plane the host contract already has (`FinalAccessController` roles,
 *    a cross-chain authority, a quorum). No new authority is introduced.
 *  - `_sweepDestinations()` — where a sweep may pay. Ours is a two-address
 *    answer because a contract normally has exactly two legitimate ones (the
 *    gateway and the treasury); a contract with one returns it twice.
 *    `FinalGateway` overrides `_requireSweepDestination` outright: the gateway
 *    is the drain of the whole system and sweeps ONWARD to anywhere.
 *  - `_sweepReserved(kind, asset, id)` — the part of the raw balance that is
 *    NOT surplus: fee deposits, the pending-settlement bucket, searcher
 *    collateral, settlement custody, vaulted entries, locked PHI. The default
 *    is zero, which is correct for a contract that custodies nothing; every
 *    contract that custodies something overrides it and is the one place the
 *    liability is stated.
 *
 * The surplus is measured LIVE against the raw balance at call time, so a
 * re-entrant destination re-measures against a balance that already fell —
 * there is no cached figure to double-spend. Nothing here writes storage, so
 * there is no state for a callback to observe half-updated either.
 *
 * The three ERC-721/ERC-1155 receiver hooks are part of the same surface and
 * for the same reason: `safeTransferFrom` reverts into a contract that does not
 * answer them, so without these an NFT sent to one of ours does not land at
 * all — which is not safety, it is a different way to lose it.
 */
abstract contract FinalSweep {
    /// @notice `msg.sender` does not hold this contract's sweep authority.
    error SweepUnauthorized(address caller);
    /// @notice `to` is neither of this contract's sweep destinations.
    error SweepDestinationNotAllowed(address to);
    /// @notice The requested amount is above the surplus: the difference is
    /// owed to somebody (a deposit, a custody total, a vaulted entry).
    error SweepAboveSurplus(address asset, uint256 requested, uint256 surplus);
    /// @notice A sweep of nothing.
    error SweepZeroAmount();
    /// @notice The transfer leg failed, or the token returned `false`.
    error SweepTransferFailed(address asset);

    /// @notice `amount` of `asset` (`id` for the non-fungible kinds) left this
    /// contract for `to` under the sweep authority.
    event AssetSwept(SweepKind indexed kind, address indexed asset, address indexed to, uint256 id, uint256 amount);

    // ─────────────────────────────── seams ───────────────────────────────

    /// @dev Reverts unless `msg.sender` may sweep. The host contract's own
    /// treasury role — never a new one.
    function _requireSweepAuthority() internal view virtual;

    /// @dev The (at most two) addresses a sweep may pay. A contract with one
    /// legitimate destination returns it twice.
    function _sweepDestinations() internal view virtual returns (address a, address b);

    /// @dev The part of the raw balance that is owed and therefore never
    /// sweepable. Zero for a contract that custodies nothing.
    function _sweepReserved(SweepKind, address, uint256) internal view virtual returns (uint256) {
        return 0;
    }

    /// @dev Destination policy. Overridden by `FinalGateway`, which may sweep
    /// onward to anywhere.
    function _requireSweepDestination(address to) internal view virtual {
        (address a, address b) = _sweepDestinations();
        if (to == address(0) || (to != a && to != b)) revert SweepDestinationNotAllowed(to);
    }

    // ────────────────────────────── surface ──────────────────────────────

    /// @notice The surplus of `asset` (`id` for the non-fungible kinds) — the
    /// raw balance above everything this contract owes. What a sweep may move,
    /// readable before calling one.
    function sweepableSurplus(SweepKind kind, address asset, uint256 id) public view returns (uint256 surplus) {
        uint256 raw = _rawBalance(kind, asset, id);
        uint256 reserved = _sweepReserved(kind, asset, id);
        return raw > reserved ? raw - reserved : 0;
    }

    /// @notice Move `amount` of an asset this contract does not owe to `to`.
    /// @dev Role-gated, destination-gated and bounded by the live surplus. The
    /// three gates are independent: a treasury key cannot pay a destination
    /// the contract does not recognize, and neither key nor destination can
    /// reach a wei that backs a liability.
    /// @param kind Which asset kind is being moved.
    /// @param asset Token contract; ignored for `Native`.
    /// @param id Token id for `Erc721` / `Erc1155`; ignored otherwise.
    /// @param amount Amount to move. `type(uint256).max` means the whole
    ///   surplus, which is what an operator draining a stray balance wants and
    ///   what avoids a race with an inflow landing between the read and the call.
    /// @param to Destination.
    /// @return moved Amount actually moved.
    function sweepAsset(SweepKind kind, address asset, uint256 id, uint256 amount, address to)
        external
        returns (uint256 moved)
    {
        _requireSweepAuthority();
        _requireSweepDestination(to);

        uint256 surplus = sweepableSurplus(kind, asset, id);
        moved = amount == type(uint256).max ? surplus : amount;
        if (moved == 0) revert SweepZeroAmount();
        if (moved > surplus) revert SweepAboveSurplus(asset, moved, surplus);

        if (kind == SweepKind.Native) {
            (bool ok,) = payable(to).call{value: moved}("");
            if (!ok) revert SweepTransferFailed(address(0));
        } else if (kind == SweepKind.Erc20) {
            _callToken(asset, abi.encodeWithSelector(0xa9059cbb, to, moved)); // transfer(address,uint256)
        } else if (kind == SweepKind.Erc721) {
            // `transferFrom`, not `safeTransferFrom`: a rescue must not fail
            // because the treasury destination declines a hook. Which
            // destination is legitimate is already decided above.
            moved = 1;
            _callToken(asset, abi.encodeWithSelector(0x23b872dd, address(this), to, id)); // transferFrom
        } else {
            _callToken(
                asset,
                abi.encodeWithSelector(0xf242432a, address(this), to, id, moved, "") // safeTransferFrom(...)
            );
        }
        emit AssetSwept(kind, asset, to, id, moved);
    }

    // ───────────────────────────── receivers ─────────────────────────────

    /// @notice Accept safe ERC-721 transfers, so one sent here is recoverable
    /// rather than rejected at the door.
    function onERC721Received(address, address, uint256, bytes calldata) external pure virtual returns (bytes4) {
        return 0x150b7a02;
    }

    /// @notice Accept safe ERC-1155 single transfers.
    function onERC1155Received(address, address, uint256, uint256, bytes calldata)
        external
        pure
        virtual
        returns (bytes4)
    {
        return 0xf23a6e61;
    }

    /// @notice Accept safe ERC-1155 batch transfers.
    function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
        external
        pure
        virtual
        returns (bytes4)
    {
        return 0xbc197c81;
    }

    // ───────────────────────────── internals ─────────────────────────────

    /// @dev The raw held amount, before anything owed is subtracted.
    function _rawBalance(SweepKind kind, address asset, uint256 id) internal view returns (uint256) {
        if (kind == SweepKind.Native) return address(this).balance;
        if (kind == SweepKind.Erc20) {
            (bool ok, bytes memory ret) = asset.staticcall(abi.encodeWithSelector(0x70a08231, address(this)));
            return (ok && ret.length >= 32) ? abi.decode(ret, (uint256)) : 0;
        }
        if (kind == SweepKind.Erc721) {
            (bool ok, bytes memory ret) = asset.staticcall(abi.encodeWithSelector(0x6352211e, id)); // ownerOf
            return (ok && ret.length >= 32 && abi.decode(ret, (address)) == address(this)) ? 1 : 0;
        }
        (bool ok1155, bytes memory ret1155) =
            asset.staticcall(abi.encodeWithSelector(0x00fdd58e, address(this), id)); // balanceOf(address,uint256)
        return (ok1155 && ret1155.length >= 32) ? abi.decode(ret1155, (uint256)) : 0;
    }

    /// @dev One transfer leg, tolerant of the legacy no-return ERC-20 shape the
    /// way `FinalDeployer`'s rescue helpers are: success is "the call did not
    /// revert AND it did not return `false`".
    function _callToken(address token, bytes memory data) private {
        if (token.code.length == 0) revert SweepTransferFailed(token);
        (bool ok, bytes memory ret) = token.call(data);
        if (!ok || (ret.length != 0 && !abi.decode(ret, (bool)))) revert SweepTransferFailed(token);
    }
}

abi

[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "registry_",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      },
      {
        "name": "intentLog_",
        "type": "address",
        "internalType": "contract FinalIntentLog"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "ACTION_CONFIGURE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SEED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SEED_PAYLOADS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "append",
    "inputs": [
      {
        "name": "termsLeaves",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "bundles",
        "type": "bytes32[][]",
        "internalType": "bytes32[][]"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "firstIndex",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "appendPayloads",
    "inputs": [
      {
        "name": "payloads",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "firstIndex",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "bundleAt",
    "inputs": [
      {
        "name": "leafIndex",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "atSize",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "payload",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "proof",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "root_",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "configure",
    "inputs": [
      {
        "name": "role",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "k",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "head",
    "inputs": [],
    "outputs": [
      {
        "name": "root_",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "size_",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "indexOf",
    "inputs": [
      {
        "name": "payload",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "index",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "found",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "intentLog",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalIntentLog"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "nonce",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "onERC1155BatchReceived",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC1155Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC721Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "payloadAt",
    "inputs": [
      {
        "name": "index",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "payloadsBetween",
    "inputs": [
      {
        "name": "from",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "to",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "out",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "peaks",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "peaksAt",
    "inputs": [
      {
        "name": "atSize",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "bag",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "proofAt",
    "inputs": [
      {
        "name": "leafIndex",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "atSize",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "proof",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registry",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "root",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "rootAt",
    "inputs": [
      {
        "name": "atSize",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "seed",
    "inputs": [
      {
        "name": "peaks_",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "size_",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "seedPayloads",
    "inputs": [
      {
        "name": "payloads",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "size",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "sweepAsset",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "moved",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "sweepableSurplus",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "surplus",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "threshold",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "writerRole",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "event",
    "name": "AssetSwept",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "indexed": true,
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "BundleAppended",
    "inputs": [
      {
        "name": "index",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "payload",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "root",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      },
      {
        "name": "size",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "LogConfigured",
    "inputs": [
      {
        "name": "writerRole",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "threshold",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "Seeded",
    "inputs": [
      {
        "name": "size",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "peaks",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "SeededPayloads",
    "inputs": [
      {
        "name": "size",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "error",
    "name": "AnchorAhead",
    "inputs": [
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "blockNumber",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "AnchorStale",
    "inputs": [
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "blockNumber",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSeal",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSignature",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "algorithm",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "EmptyBatch",
    "inputs": []
  },
  {
    "type": "error",
    "name": "EmptyBundleLeaves",
    "inputs": [
      {
        "name": "bundleIndex",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "EmptyBundleTree",
    "inputs": []
  },
  {
    "type": "error",
    "name": "LogNotConfigured",
    "inputs": []
  },
  {
    "type": "error",
    "name": "MmrEmptyLog",
    "inputs": []
  },
  {
    "type": "error",
    "name": "MmrIndexOutOfRange",
    "inputs": [
      {
        "name": "index",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "size",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAuthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotFresh",
    "inputs": []
  },
  {
    "type": "error",
    "name": "PeaksMismatch",
    "inputs": [
      {
        "name": "expected",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "given",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "PrecompileUnavailable",
    "inputs": [
      {
        "name": "precompile",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "ProofShapeMismatch",
    "inputs": [
      {
        "name": "expected",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "walked",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SignerLacksRole",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "roleMask",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SignersNotAscending",
    "inputs": [
      {
        "name": "previous",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "next",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SizeAhead",
    "inputs": [
      {
        "name": "asked",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "held",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepAboveSurplus",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "requested",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "surplus",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepDestinationNotAllowed",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepTransferFailed",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepUnauthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepZeroAmount",
    "inputs": []
  },
  {
    "type": "error",
    "name": "TermsLeafCountMismatch",
    "inputs": [
      {
        "name": "termsLeaves",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "bundles",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "ThresholdIsZero",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ThresholdNotMet",
    "inputs": [
      {
        "name": "valid",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "ThresholdUnreachable",
    "inputs": [
      {
        "name": "live",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "WrongAlgorithm",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "got",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "required",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "ZeroBundleRoot",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ZeroIntentLog",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ZeroTermsLeaf",
    "inputs": [
      {
        "name": "bundleIndex",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  }
]

读取合约

字节码 · 11,619 字节

0x6080806040526004361015610012575f80fd5b5f3560e01c90816304fedb2f14611a1357508063150b7a02146119bd5780631a30f0791461199f578063205a094e146119855780632441c09b1461146757806342cde4e81461144a57806348d316ac146113fa57806352a9674b146113c05780635bb8a951146113a257806360a180081461136e57806362984f88146110da5780636ce60417146110a75780636f4ce56a1461107b57806372f56b2c146110415780637b10399914610ffd5780638f7dcfa314610fd9578063949d225d14610fbc57806394bc4e9614610c9b57806396f51f3a146109c8578063affed0e0146109a2578063b19f480514610968578063bc197c81146108d0578063c0131f591461088c578063ca2869a014610866578063cba573581461084a578063cba8bdf71461040f578063ebb3eedb14610363578063ebf0c71714610346578063f23a6e61146102f05763f47f54cb14610166575f80fd5b346102ec5761017436611b47565b9392919082156102dd576001549182156102ce576001600160401b039161028761028d9260025495858716936006549a8b8b6101de8c6101d060405193849260208401968d885260408501526060808501526080840191611e73565b03601f198101835282611be1565b51902060405160208101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527f5985b2aa0699a556c4b84df321b016abe612f656b53dfdb4741aaa1912f686b460808301528a871660a083015260c082015260c0815261025a60e082611be1565b519020905f54927f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf61218f565b50611e97565b16906001600160401b031916176002555f5b8181106102b157602084604051908152f35b806102c86102c26001938587611eb5565b35612436565b0161029f565b6382d4481f60e01b5f5260045ffd5b63c2e5347d60e01b5f5260045ffd5b5f80fd5b346102ec5760a03660031901126102ec57610309611a4b565b50610312611a61565b506084356001600160401b0381116102ec57610332903690600401611a8b565b505060405163f23a6e6160e01b8152602090f35b346102ec575f3660031901126102ec576020600754604051908152f35b346102ec5761037136611ab8565b6006548082116103f957508082116103e3576103956103908383611bc7565b611c2d565b91805b8281106103b957604051602080825281906103b590820187611ace565b0390f35b806001915f52600460205260405f20546103dc6103d68584611bc7565b87611c80565b5201610398565b906388c73b2960e01b5f5260045260245260445ffd5b90635b8d5fdb60e11b5f5260045260245260445ffd5b346102ec5760803660031901126102ec576004356001600160401b0381116102ec5761043f903690600401611b01565b6024359061044b611b31565b6064356001600160401b0381116102ec5761046a903690600401611b01565b6040516328305db160e21b81527f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03169290602081600481875afa908115610708575f9161081b575b5080156107b5575b610619575b5050505060065461060a575f82805b6105e657508181036105d157505f8060ff5b60018086831c161461056e575b801561051d578015610509575f19016104e8565b634e487b7160e01b5f52601160045260245ffd5b7f67f9b61bf7b39fd24dd60467083f89ea77979db358db2804069474590a36c035604086868160065581155f14610560575f5b60075582519182526020820152a1005b6105698261206e565b610550565b9061057a838588611eb5565b35156105c2576105bc906105986105908561212a565b948689611eb5565b35835f52600360205260405f2082851c5f5260205260405f20556001831b90611bd4565b906104f5565b634425ca1360e01b5f5260045ffd5b63ecc9b8ed60e01b5f5260045260245260445ffd5b6001808216146105fa575b60011c806104d6565b906106049061212a565b906105f1565b63dc63d81f60e01b5f5260045ffd5b60405160208101906040825261064b81610637606082018a8d611e73565b8a604083015203601f198101835282611be1565b519020833b156102ec5790826001600160401b039593926040519687956322f3f44760e11b875260848701927f405bbda3343b6e69c32fb7eafff8f0a1e55a5ee2ec35458b3abc776b2668195260048901526024880152166044860152608060648601525260a4830160a060048460051b8601010192825f90607e19813603015b8383106107135750505050505091815f818582965003925af18015610708576106f8575b8080806104c7565b5f61070291611be1565b836106f0565b6040513d5f823e3d90fd5b60a3198a8803018552949650929491939092918635828112156102ec5783016001600160a01b0361074382611a77565b16825260208101359160ff83168093036102ec576107a360209282600195858095015261079561078a6107796040850185611f15565b608060408601526080850191611f46565b926060810190611f15565b916060818503910152611f46565b980196019301909188969594926106cc565b5060405163f5778b0360e01b8152602081600481875afa908115610708575f916107ec575b506001600160a01b03163314156104c2565b61080e915060203d602011610814575b6108068183611be1565b810190611ef6565b886107da565b503d6107fc565b61083d915060203d602011610843575b6108358183611be1565b810190611ede565b886104ba565b503d61082b565b346102ec575f3660031901126102ec5760205f54604051908152f35b346102ec5760203660031901126102ec57602061088460043561206e565b604051908152f35b346102ec575f3660031901126102ec576040517f000000000000000000000000c0876d136341091581a489ce7f746692dddf498f6001600160a01b03168152602090f35b346102ec5760a03660031901126102ec576108e9611a4b565b506108f2611a61565b506044356001600160401b0381116102ec57610912903690600401611b01565b50506064356001600160401b0381116102ec57610933903690600401611b01565b50506084356001600160401b0381116102ec57610954903690600401611a8b565b505060405163bc197c8160e01b8152602090f35b346102ec575f3660031901126102ec5760206040517f27c91cbb7cc32319dd47788e8b096cc02ee8ccca641645266d7046769a12fbc38152f35b346102ec575f3660031901126102ec5760206001600160401b0360025416604051908152f35b346102ec5760a03660031901126102ec5760043560048110156102ec576109ed611a61565b90606435906084356001600160a01b03811691604435918381036102ec57610a136126e4565b60405163f5778b0360e01b81526020816004817f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03165afa908115610708575f91610c7c575b508415908115610c58575b50610c4557610a7b838784611ec5565b945f198103610c405750845b80958115610c3157808211610c0a57505f9183610b285750505f80808088885af1610ab061203f565b5015610b15575b610b0157604080519283526020838101869052956001600160a01b0316927f7643c83e539cea2f6bf506545392e52cfd5f917e327efbcd0ba28f29c28d042e9190a4604051908152f35b634e487b7160e01b5f52602160045260245ffd5b6365f4a9ef60e11b5f525f60045260245ffd5b5f92509060018403610b78575060405163a9059cbb60e01b60208201526001600160a01b03909116602482015260448101869052610b7390610b6d81606481016101d0565b87612882565b610ab7565b5f969250905060028303610bbf575050600193610b736040516323b872dd60e01b602082015230602482015285604482015284606482015260648152610b6d608482611be1565b610b739060409692965190637921219560e11b6020830152306024830152866044830152856064830152608482015260a060a48201525f60c482015260c48152610b6d60e482611be1565b632190968160e01b5f9081526001600160a01b038916600452602492909252604452606490fd5b637c2e506f60e11b5f5260045ffd5b610a87565b836315150d4d60e31b5f5260045260245ffd5b6001600160a01b0316851415905080610c72575b87610a6b565b5033841415610c6c565b610c95915060203d602011610814576108068183611be1565b87610a60565b346102ec5760803660031901126102ec57602435600435610cba611b31565b6064356001600160401b0381116102ec57610cd9903690600401611b01565b6040516328305db160e21b81527f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b0316939290602081600481885afa908115610708575f91610f9d575b508015610f47575b610df7575b50505082610d79575b7fbfc08a458e488f0e56f7ff4bfe317bed1ba5d3f7ef5a2bda241528695f6fcdf360408385815f558060015582519182526020820152a1005b60206024916040519283809263342f616360e01b82528660048301525afa908115610708575f91610dc5575b5082811015610d40579050633770da3360e11b5f5260045260245260445ffd5b90506020813d602011610def575b81610de060209383611be1565b810103126102ec575183610da5565b3d9150610dd3565b604051602081019086825287604082015260408152610e17606082611be1565b519020843b156102ec5790826001600160401b0394926040519586946322f3f44760e11b865260848601927f27c91cbb7cc32319dd47788e8b096cc02ee8ccca641645266d7046769a12fbc360048801526024870152166044850152608060648501525260a4820160a060048560051b8501010193825f90607e19813603015b838310610ed15750505050505080825f9350038183865af1801561070857610ec1575b8080610d37565b5f610ecb91611be1565b83610eba565b60a3198989030185529496939550919390928635828112156102ec5783016001600160a01b03610f0082611a77565b16825260208101359160ff83168093036102ec57610f3660209282600195858095015261079561078a6107796040850185611f15565b980196019301909187959492610e97565b5060405163f5778b0360e01b8152602081600481885afa908115610708575f91610f7e575b506001600160a01b0316331415610d32565b610f97915060203d602011610814576108068183611be1565b87610f6c565b610fb6915060203d602011610843576108358183611be1565b87610d2a565b346102ec575f3660031901126102ec576020600654604051908152f35b346102ec575f3660031901126102ec57604060075460065482519182526020820152f35b346102ec575f3660031901126102ec576040517f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03168152602090f35b346102ec575f3660031901126102ec5760206040517f405bbda3343b6e69c32fb7eafff8f0a1e55a5ee2ec35458b3abc776b266819528152f35b346102ec5760203660031901126102ec576040611099600435611ffa565b825191825215156020820152f35b346102ec5760203660031901126102ec576103b56110c6600435611f66565b604051918291602083526020830190611ace565b346102ec576110e836611b47565b6040516328305db160e21b81529394937f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03169290602081600481875afa908115610708575f9161134f575b5080156112f9575b6111a6575b5050505060065461060a5781156102dd575f5b82811061118f577f1c295873c1ce4ce2ac720f43d6909e66b931b42e9246b862278eba9624c0bf05602084604051908152a1005b806111a06102c26001938686611eb5565b0161115b565b6040516020810190602082526111c4816101d0604082018b8b611e73565b519020833b156102ec5790826001600160401b039593926040519687956322f3f44760e11b875260848701927f9abdf9961fd14fd177480eccbad16b2d7f231898b2d763c8e8b50364d8b3b17160048901526024880152166044860152608060648601525260a4830160a060048460051b8601010192825f90607e19813603015b8383106112815750505050505091815f818582965003925af1801561070857611271575b808080611148565b5f61127b91611be1565b82611269565b60a3198a8803018552949650929491939092918635828112156102ec5783016001600160a01b036112b182611a77565b16825260208101359160ff83168093036102ec576112e760209282600195858095015261079561078a6107796040850185611f15565b98019601930190918896959492611245565b5060405163f5778b0360e01b8152602081600481875afa908115610708575f91611330575b506001600160a01b0316331415611143565b611349915060203d602011610814576108068183611be1565b8761131e565b611368915060203d602011610843576108358183611be1565b8761113b565b346102ec5760603660031901126102ec5760043560048110156102ec57610884602091611399611a61565b60443591611ec5565b346102ec575f3660031901126102ec576103b56110c6600654611f66565b346102ec575f3660031901126102ec5760206040517fb66ca34dc0d9a9daa6230aee35894330ccfa7e4eaa29a198577eed0b26a412058152f35b346102ec5761140836611ab8565b61142461141e6114188385611c94565b93611bac565b9161206e565b6114406040519384938452606060208501526060840190611ace565b9060408301520390f35b346102ec575f3660031901126102ec576020600154604051908152f35b346102ec5760803660031901126102ec576004356001600160401b0381116102ec57611497903690600401611b01565b906024356001600160401b0381116102ec576114b7903690600401611b01565b6114c2939193611b31565b936064356001600160401b0381116102ec576114e2903690600401611b01565b9583156102dd5783850361196e5760015480156102ce5760029795979693965492600654966040516001600160401b03861660208201528860408201526080606082015261153460a082018c89611e73565b601f19828203016080830152888152602081019060208a60051b820101918c915f5b8c811061190257505050509061157c81611603979695949303601f198101835282611be1565b6020815191012060405160208101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527f2e1c2ff2f9bb13fd926fe3e8b209f98e6c873bb259534a2148ca355409247cba60808301526001600160401b03871660a083015260c082015260c0815261025a60e082611be1565b506001600160401b03611617818316611e97565b67ffffffffffffffff199092169116176002555f947f000000000000000000000000c0876d136341091581a489ce7f746692dddf498f6001600160a01b03165b838710156118f7578660051b860135601e19873603018112156102ec5786018035906001600160401b0382116102ec57602001908060051b360382136102ec5780156118e4576116a8898587611eb5565b35156118d15760018101808211610509576116c290611c2d565b916116ce8a8688611eb5565b356116d884611c5f565b525f5b82811061185a5750505080511561184b575b8051600181111561181f578060011c90600181169261170f6103908585611bd4565b935f5b84811061179f575060011461172a575b5050506116ed565b5f198201918211610509576117969161174291611c80565b516040516020810191600160f91b83527fc976f483968b324bd57de8efa226478a3634db61776dacd4da866f8fa37c0fd5602183015260418201526041815261178c606182611be1565b5190209183611c80565b52888080611722565b80600191821b6117bc836117b38388611c80565b51921786611c80565b516040519060208201928560f81b84527fc976f483968b324bd57de8efa226478a3634db61776dacd4da866f8fa37c0fd56021840152604183015260618201526061815261180b608182611be1565b5190206118188289611c80565b5201611712565b509661183d6118376001939699989598979497611c5f565b51612436565b019592949194939093611657565b634f297b6160e11b5f5260045ffd5b611865818484611eb5565b35853b156102ec576040519063af6f8c1b60e01b825260048201525f81602481838a5af18015610708576118c1575b506118a0818484611eb5565b35906001810191828211610509576118ba60019387611c80565b52016116db565b5f6118cb91611be1565b8b611894565b886322566cfd60e01b5f5260045260245ffd5b8863c9cdeff560e01b5f5260045260245ffd5b602085604051908152f35b909192939c9e9c601f9e9b9e19838203018452601e198c360301853512156102ec578b85350190602082359201916001600160401b0381116102ec578060051b360383136102ec5761195a6020928392600195611e73565b9601940191019e9c9e9d9a9d919091611556565b8385635b2d642360e11b5f5260045260245260445ffd5b346102ec576103b56110c661199936611ab8565b90611c94565b346102ec5760203660031901126102ec576020610884600435611bac565b346102ec5760803660031901126102ec576119d6611a4b565b506119df611a61565b506064356001600160401b0381116102ec576119ff903690600401611a8b565b5050604051630a85bd0160e11b8152602090f35b346102ec575f3660031901126102ec57807f9abdf9961fd14fd177480eccbad16b2d7f231898b2d763c8e8b50364d8b3b17160209252f35b600435906001600160a01b03821682036102ec57565b602435906001600160a01b03821682036102ec57565b35906001600160a01b03821682036102ec57565b9181601f840112156102ec578235916001600160401b0383116102ec57602083818601950101116102ec57565b60409060031901126102ec576004359060243590565b90602080835192838152019201905f5b818110611aeb5750505090565b8251845260209384019390920191600101611ade565b9181601f840112156102ec578235916001600160401b0383116102ec576020808501948460051b0101116102ec57565b604435906001600160401b03821682036102ec57565b9060606003198301126102ec576004356001600160401b0381116102ec5782611b7291600401611b01565b929092916024356001600160401b03811681036102ec5791604435906001600160401b0382116102ec57611ba891600401611b01565b9091565b600654808210156103e357505f52600460205260405f205490565b9190820391821161050957565b9190820180921161050957565b90601f801991011681019081106001600160401b03821117611c0257604052565b634e487b7160e01b5f52604160045260245ffd5b6001600160401b038111611c025760051b60200190565b90611c3782611c16565b611c446040519182611be1565b8281528092611c55601f1991611c16565b0190602036910137565b805115611c6c5760200190565b634e487b7160e01b5f52603260045260245ffd5b8051821015611c6c5760209160051b010190565b91906006548082116103f957508015611e645780831015611e4e575f198101908082116105095790611cde610390611ccd838718612138565b611cd887821c612151565b90611bd4565b9390915f835f945b611d1057505050508251808203611cfb575050565b63383613b560e01b5f5260045260245260445ffd5b90919293600185188281105f14611d5d578392916001949185925f52600360205260405f20905f5260205260405f2054611d4a828b611c80565b5201945b831c9392918201911c80611ce6565b828196929614611d73575b509060019291611d4e565b839591951b611d828186611bc7565b611d8e61039082612151565b905f9290611d9b81612138565b805b611e01575050505f19820191821161050957611db98282611c80565b5191805b611de1575050600193929181859250611dd6828b611c80565b520194909192611d68565b5f1901918290611dfb90611df58385611c80565b51612922565b92611dbd565b5f190160018083831c1614611e17575b80611d9d565b6001819395825f52600360205260405f2087841c5f5260205260405f2054611e3f8288611c80565b5201946001821b019250611e11565b826388c73b2960e01b5f5260045260245260445ffd5b635bf77f6760e01b5f5260045ffd5b81835290916001600160fb1b0383116102ec5760209260051b809284830137010190565b6001600160401b036001911601906001600160401b03821161050957565b9190811015611c6c5760051b0190565b90611ed092916125a1565b8015611ed95790565b505f90565b908160209103126102ec575180151581036102ec5790565b908160209103126102ec57516001600160a01b03811681036102ec5790565b9035601e19823603018112156102ec5701602081359101916001600160401b0382116102ec5781360383136102ec57565b908060209392818452848401375f828201840152601f01601f1916010190565b90600654808311611fe45750611f7e61039083612151565b915f5f91611f8b81612138565b805b611f975750505050565b5f190160018083831c1614611fad575b80611f8d565b6001819493825f52600360205260405f2085841c5f5260205260405f2054611fd5828a611c80565b5201926001821b019350611fa7565b82635b8d5fdb60e11b5f5260045260245260445ffd5b5f52600560205260405f2054801561201d575f1981019081116105095790600190565b505f905f90565b6001600160401b038111611c0257601f01601f191660200190565b3d15612069573d9061205082612024565b9161205e6040519384611be1565b82523d5f602084013e565b606090565b6006548082116103f957508015611ed95761208b61039082612151565b5f915f9061209881612138565b805b6120dd575050505f198201918211610509576120b68282611c80565b5191805b6120c357505090565b5f19019182906120d790611df58385611c80565b926120ba565b5f190160018083831c16146120f3575b8061209a565b6001819395825f52600360205260405f2087841c5f5260205260405f205461211b8288611c80565b5201946001821b0192506120ed565b5f1981146105095760010190565b90815f925b6121445750565b6001928301921c8061213d565b90815f925b61215d5750565b9160018316019160011c80612156565b356001600160a01b03811681036102ec5790565b3560ff811681036102ec5790565b92939195965f978615612427576001600160401b0316438111612411576102586121b98243611bc7565b116123fb5750604051946020860152602085526121d7604086611be1565b5f955f985b888a10156123d1578960051b840135607e19853603018112156102ec578401976122058961216d565b6001600160a01b0391821691168110156123a557506122238861216d565b976122646020876122338461216d565b604051632e4bfa5160e11b81526001600160a01b039091166004820152602481019190915291829081906044820190565b03816001600160a01b038c165afa908115610708575f91612387575b50156123605760208101600460ff61229783612181565b160361232e576122a889838a612a42565b156122fb57506122b9888289612b87565b156122d257506122ca60019161212a565b9901986121dc565b6122db9061216d565b63c082266360e01b5f9081526001600160a01b0391909116600452602490fd5b61230f61230960ff9361216d565b91612181565b9063bbf82ba360e01b5f5260018060a01b03166004521660245260445ffd5b61233c61230960ff9361216d565b9063587548c360e11b5f5260018060a01b031660045216602452600460445260645ffd5b61236a869161216d565b63ae8bb03960e01b5f5260018060a01b031660045260245260445ffd5b61239f915060203d8111610843576108358183611be1565b5f612280565b6123ae8961216d565b6311641feb60e21b5f9081526004929092526001600160a01b0316602452604490fd5b985095509550505050508083106123e55750565b826305bc216760e51b5f5260045260245260445ffd5b630ed38fd160e41b5f526004524360245260445ffd5b637b51505560e01b5f526004524360245260445ffd5b631fc460bf60e11b5f5260045ffd5b80156105c257600654805f5260046020528160405f2055815f52600560205260405f205415612584575b60405160208101905f82527fb66ca34dc0d9a9daa6230aee35894330ccfa7e4eaa29a198577eed0b26a412056021820152836041820152604181526124a6606182611be1565b5190205f8281527f3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff602052604081208290559082905b60018083161461252f575050507f1585fcb2f8b662b0e77609aa657994b280f417bea79b40989d135efaf88405486040600183018060065561251d8161206e565b908160075582519182526020820152a3565b825f52600360205260405f20905f198301908382116105095760019261255e925f5260205260405f2054612922565b91811c920190815f52600360205260405f20835f526020528060405f20559190916124dc565b6001810180821161050957825f52600560205260405f2055612460565b906004821015610b015781156126dd575f928392600181146126b45760021461262c57604051627eeac760e11b6020820190815230602483015260448201929092526125f081606481016101d0565b51915afa6125fc61203f565b9080612620575b15611ed957602081519181808201938492010103126102ec575190565b50602081511015612603565b60405160208101916331a9108f60e11b8352602482015260248152612652604482611be1565b51915afa61265e61203f565b816126a6575b81612679575b501561267557600190565b5f90565b90506020818051810103126102ec57602001516001600160a01b038116908190036102ec5730145f61266a565b905060208151101590612664565b505060405160208101906370a0823160e01b8252306024820152602481526125f0604482611be1565b5050504790565b6040516328305db160e21b81527f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b031690602081600481855afa908115610708575f91612863575b50158061280e575b61280b5760405163e14c465b60e01b8152602081600481855afa908115610708575f916127d7575b50604051632e4bfa5160e11b815233600482015260248101919091529060209082908180604481015b03915afa908115610708575f916127b8575b506127b65763321cbc0960e21b5f523360045260245ffd5b565b6127d1915060203d602011610843576108358183611be1565b5f61279e565b90506020813d602011612803575b816127f260209383611be1565b810103126102ec575161278c612763565b3d91506127e5565b50565b5060405163f5778b0360e01b8152602081600481855afa908115610708575f91612844575b506001600160a01b0316331461273b565b61285d915060203d602011610814576108068183611be1565b5f612833565b61287c915060203d602011610843576108358183611be1565b5f612733565b90813b15612901575f816020829351910182855af161289f61203f565b90159081156128d1575b506128b15750565b6365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b80518015159250826128e6575b50505f6128a9565b6128f99250602080918301019101611ede565b155f806128de565b506365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b90604051906020820192600160f81b84527fb66ca34dc0d9a9daa6230aee35894330ccfa7e4eaa29a198577eed0b26a4120560218401526041830152606182015260618152612972608182611be1565b51902090565b6020818303126102ec578051906001600160401b0382116102ec570181601f820112156102ec578051906129ab82612024565b926129b96040519485611be1565b828452602083830101116102ec57815f9260208093018386015e8301015290565b903590601e19813603018212156102ec57018035906001600160401b0382116102ec576020019181360383136102ec57565b929192612a1882612024565b91612a266040519384611be1565b8294818452818301116102ec578281602093845f960137010152565b9160208201600460ff612a5483612181565b1614612b065760ff612a67600592612181565b1614612a74575050505f90565b5f612a7e8361216d565b604051639e5adaeb60e01b81526001600160a01b0391821660048201529485916024918391165afa91821561070857612ad7935f93612ada575b50612aca816040612ad19301906129da565b3691612a0c565b91612ce7565b90565b612ad1919350612afe612aca913d805f833e612af68183611be1565b810190612978565b939150612ab8565b505f612b118361216d565b60405163b7af85d760e01b81526001600160a01b0391821660048201529485916024918391165afa91821561070857612ad7935f93612b63575b50612aca816040612b5d9301906129da565b91612c25565b612b5d919350612b7f612aca913d805f833e612af68183611be1565b939150612b4b565b90915f612b938461216d565b60405163ad84ad1360e01b81526001600160a01b0391821660048201529384916024918391165afa918215610708575f92612c09575b508151158015612bf3575b612bec57612ad1612aca846060612ad79601906129da565b5050505f90565b50612c0160608401846129da565b905015612bd4565b612c1e9192503d805f833e612af68183611be1565b905f612bc9565b610a20815114801590612cda575b612bec576020612c865f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282611be1565b51906102045afa612c9561203f565b81612cce575b81612ca4575090565b9050602081519101519060208110612cbd575b50151590565b5f199060200360031b1b165f612cb7565b80516020149150612c9b565b5061121383511415612c33565b6040815114801590612d56575b612bec576020612d475f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282611be1565b51906102055afa612c9561203f565b5061746083511415612cf456
没有 CBOR 元数据尾部 — 此字节码在关闭 cbor_metadata 的情况下构建,这是我们自己的合约为保持 CREATE2 地址不变而固定的设置。

反汇编 (前 4,000 条操作)

pcop操作数
0000PUSH10x80
0002DUP1
0003PUSH10x40
0005MSTORE
0006PUSH10x04
0008CALLDATASIZE
0009LT
000aISZERO
000bPUSH20x0012
000eJUMPI
000fPUSH0
0010DUP1
0011REVERT
0012JUMPDEST
0013PUSH0
0014CALLDATALOAD
0015PUSH10xe0
0017SHR
0018SWAP1
0019DUP2
001aPUSH40x04fedb2f
001fEQ
0020PUSH20x1a13
0023JUMPI
0024POP
0025DUP1
0026PUSH40x150b7a02
002bEQ
002cPUSH20x19bd
002fJUMPI
0030DUP1
0031PUSH40x1a30f079
0036EQ
0037PUSH20x199f
003aJUMPI
003bDUP1
003cPUSH40x205a094e
0041EQ
0042PUSH20x1985
0045JUMPI
0046DUP1
0047PUSH40x2441c09b
004cEQ
004dPUSH20x1467
0050JUMPI
0051DUP1
0052PUSH40x42cde4e8
0057EQ
0058PUSH20x144a
005bJUMPI
005cDUP1
005dPUSH40x48d316ac
0062EQ
0063PUSH20x13fa
0066JUMPI
0067DUP1
0068PUSH40x52a9674b
006dEQ
006ePUSH20x13c0
0071JUMPI
0072DUP1
0073PUSH40x5bb8a951
0078EQ
0079PUSH20x13a2
007cJUMPI
007dDUP1
007ePUSH40x60a18008
0083EQ
0084PUSH20x136e
0087JUMPI
0088DUP1
0089PUSH40x62984f88
008eEQ
008fPUSH20x10da
0092JUMPI
0093DUP1
0094PUSH40x6ce60417
0099EQ
009aPUSH20x10a7
009dJUMPI
009eDUP1
009fPUSH40x6f4ce56a
00a4EQ
00a5PUSH20x107b
00a8JUMPI
00a9DUP1
00aaPUSH40x72f56b2c
00afEQ
00b0PUSH20x1041
00b3JUMPI
00b4DUP1
00b5PUSH40x7b103999
00baEQ
00bbPUSH20x0ffd
00beJUMPI
00bfDUP1
00c0PUSH40x8f7dcfa3
00c5EQ
00c6PUSH20x0fd9
00c9JUMPI
00caDUP1
00cbPUSH40x949d225d
00d0EQ
00d1PUSH20x0fbc
00d4JUMPI
00d5DUP1
00d6PUSH40x94bc4e96
00dbEQ
00dcPUSH20x0c9b
00dfJUMPI
00e0DUP1
00e1PUSH40x96f51f3a
00e6EQ
00e7PUSH20x09c8
00eaJUMPI
00ebDUP1
00ecPUSH40xaffed0e0
00f1EQ
00f2PUSH20x09a2
00f5JUMPI
00f6DUP1
00f7PUSH40xb19f4805
00fcEQ
00fdPUSH20x0968
0100JUMPI
0101DUP1
0102PUSH40xbc197c81
0107EQ
0108PUSH20x08d0
010bJUMPI
010cDUP1
010dPUSH40xc0131f59
0112EQ
0113PUSH20x088c
0116JUMPI
0117DUP1
0118PUSH40xca2869a0
011dEQ
011ePUSH20x0866
0121JUMPI
0122DUP1
0123PUSH40xcba57358
0128EQ
0129PUSH20x084a
012cJUMPI
012dDUP1
012ePUSH40xcba8bdf7
0133EQ
0134PUSH20x040f
0137JUMPI
0138DUP1
0139PUSH40xebb3eedb
013eEQ
013fPUSH20x0363
0142JUMPI
0143DUP1
0144PUSH40xebf0c717
0149EQ
014aPUSH20x0346
014dJUMPI
014eDUP1
014fPUSH40xf23a6e61
0154EQ
0155PUSH20x02f0
0158JUMPI
0159PUSH40xf47f54cb
015eEQ
015fPUSH20x0166
0162JUMPI
0163PUSH0
0164DUP1
0165REVERT
0166JUMPDEST
0167CALLVALUE
0168PUSH20x02ec
016bJUMPI
016cPUSH20x0174
016fCALLDATASIZE
0170PUSH20x1b47
0173JUMP
0174JUMPDEST
0175SWAP4
0176SWAP3
0177SWAP2
0178SWAP1
0179DUP3
017aISZERO
017bPUSH20x02dd
017eJUMPI
017fPUSH10x01
0181SLOAD
0182SWAP2
0183DUP3
0184ISZERO
0185PUSH20x02ce
0188JUMPI
0189PUSH10x01
018bPUSH10x01
018dPUSH10x40
018fSHL
0190SUB
0191SWAP2
0192PUSH20x0287
0195PUSH20x028d
0198SWAP3
0199PUSH10x02
019bSLOAD
019cSWAP6
019dDUP6
019eDUP8
019fAND
01a0SWAP4
01a1PUSH10x06
01a3SLOAD
01a4SWAP11
01a5DUP12
01a6DUP12
01a7PUSH20x01de
01aaDUP13
01abPUSH20x01d0
01aePUSH10x40
01b0MLOAD
01b1SWAP4
01b2DUP5
01b3SWAP3
01b4PUSH10x20
01b6DUP5
01b7ADD
01b8SWAP7
01b9DUP14
01baDUP9
01bbMSTORE
01bcPUSH10x40
01beDUP6
01bfADD
01c0MSTORE
01c1PUSH10x60
01c3DUP1
01c4DUP6
01c5ADD
01c6MSTORE
01c7PUSH10x80
01c9DUP5
01caADD
01cbSWAP2
01ccPUSH20x1e73
01cfJUMP
01d0JUMPDEST
01d1SUB
01d2PUSH10x1f
01d4NOT
01d5DUP2
01d6ADD
01d7DUP4
01d8MSTORE
01d9DUP3
01daPUSH20x1be1
01ddJUMP
01deJUMPDEST
01dfMLOAD
01e0SWAP1
01e1KECCAK256
01e2PUSH10x40
01e4MLOAD
01e5PUSH10x20
01e7DUP2
01e8ADD
01e9SWAP2
01eaPUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
020bDUP4
020cMSTORE
020dCHAINID
020ePUSH10x40
0210DUP4
0211ADD
0212MSTORE
0213ADDRESS
0214PUSH10x60
0216DUP4
0217ADD
0218MSTORE
0219PUSH320x5985b2aa0699a556c4b84df321b016abe612f656b53dfdb4741aaa1912f686b4
023aPUSH10x80
023cDUP4
023dADD
023eMSTORE
023fDUP11
0240DUP8
0241AND
0242PUSH10xa0
0244DUP4
0245ADD
0246MSTORE
0247PUSH10xc0
0249DUP3
024aADD
024bMSTORE
024cPUSH10xc0
024eDUP2
024fMSTORE
0250PUSH20x025a
0253PUSH10xe0
0255DUP3
0256PUSH20x1be1
0259JUMP
025aJUMPDEST
025bMLOAD
025cSWAP1
025dKECCAK256
025eSWAP1
025fPUSH0
0260SLOAD
0261SWAP3
0262PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0283PUSH20x218f
0286JUMP
0287JUMPDEST
0288POP
0289PUSH20x1e97
028cJUMP
028dJUMPDEST
028eAND
028fSWAP1
0290PUSH10x01
0292PUSH10x01
0294PUSH10x40
0296SHL
0297SUB
0298NOT
0299AND
029aOR
029bPUSH10x02
029dSSTORE
029ePUSH0
029fJUMPDEST
02a0DUP2
02a1DUP2
02a2LT
02a3PUSH20x02b1
02a6JUMPI
02a7PUSH10x20
02a9DUP5
02aaPUSH10x40
02acMLOAD
02adSWAP1
02aeDUP2
02afMSTORE
02b0RETURN
02b1JUMPDEST
02b2DUP1
02b3PUSH20x02c8
02b6PUSH20x02c2
02b9PUSH10x01
02bbSWAP4
02bcDUP6
02bdDUP8
02bePUSH20x1eb5
02c1JUMP
02c2JUMPDEST
02c3CALLDATALOAD
02c4PUSH20x2436
02c7JUMP
02c8JUMPDEST
02c9ADD
02caPUSH20x029f
02cdJUMP
02ceJUMPDEST
02cfPUSH40x82d4481f
02d4PUSH10xe0
02d6SHL
02d7PUSH0
02d8MSTORE
02d9PUSH10x04
02dbPUSH0
02dcREVERT
02ddJUMPDEST
02dePUSH40xc2e5347d
02e3PUSH10xe0
02e5SHL
02e6PUSH0
02e7MSTORE
02e8PUSH10x04
02eaPUSH0
02ebREVERT
02ecJUMPDEST
02edPUSH0
02eeDUP1
02efREVERT
02f0JUMPDEST
02f1CALLVALUE
02f2PUSH20x02ec
02f5JUMPI
02f6PUSH10xa0
02f8CALLDATASIZE
02f9PUSH10x03
02fbNOT
02fcADD
02fdSLT
02fePUSH20x02ec
0301JUMPI
0302PUSH20x0309
0305PUSH20x1a4b
0308JUMP
0309JUMPDEST
030aPOP
030bPUSH20x0312
030ePUSH20x1a61
0311JUMP
0312JUMPDEST
0313POP
0314PUSH10x84
0316CALLDATALOAD
0317PUSH10x01
0319PUSH10x01
031bPUSH10x40
031dSHL
031eSUB
031fDUP2
0320GT
0321PUSH20x02ec
0324JUMPI
0325PUSH20x0332
0328SWAP1
0329CALLDATASIZE
032aSWAP1
032bPUSH10x04
032dADD
032ePUSH20x1a8b
0331JUMP
0332JUMPDEST
0333POP
0334POP
0335PUSH10x40
0337MLOAD
0338PUSH40xf23a6e61
033dPUSH10xe0
033fSHL
0340DUP2
0341MSTORE
0342PUSH10x20
0344SWAP1
0345RETURN
0346JUMPDEST
0347CALLVALUE
0348PUSH20x02ec
034bJUMPI
034cPUSH0
034dCALLDATASIZE
034ePUSH10x03
0350NOT
0351ADD
0352SLT
0353PUSH20x02ec
0356JUMPI
0357PUSH10x20
0359PUSH10x07
035bSLOAD
035cPUSH10x40
035eMLOAD
035fSWAP1
0360DUP2
0361MSTORE
0362RETURN
0363JUMPDEST
0364CALLVALUE
0365PUSH20x02ec
0368JUMPI
0369PUSH20x0371
036cCALLDATASIZE
036dPUSH20x1ab8
0370JUMP
0371JUMPDEST
0372PUSH10x06
0374SLOAD
0375DUP1
0376DUP3
0377GT
0378PUSH20x03f9
037bJUMPI
037cPOP
037dDUP1
037eDUP3
037fGT
0380PUSH20x03e3
0383JUMPI
0384PUSH20x0395
0387PUSH20x0390
038aDUP4
038bDUP4
038cPUSH20x1bc7
038fJUMP
0390JUMPDEST
0391PUSH20x1c2d
0394JUMP
0395JUMPDEST
0396SWAP2
0397DUP1
0398JUMPDEST
0399DUP3
039aDUP2
039bLT
039cPUSH20x03b9
039fJUMPI
03a0PUSH10x40
03a2MLOAD
03a3PUSH10x20
03a5DUP1
03a6DUP3
03a7MSTORE
03a8DUP2
03a9SWAP1
03aaPUSH20x03b5
03adSWAP1
03aeDUP3
03afADD
03b0DUP8
03b1PUSH20x1ace
03b4JUMP
03b5JUMPDEST
03b6SUB
03b7SWAP1
03b8RETURN
03b9JUMPDEST
03baDUP1
03bbPUSH10x01
03bdSWAP2
03bePUSH0
03bfMSTORE
03c0PUSH10x04
03c2PUSH10x20
03c4MSTORE
03c5PUSH10x40
03c7PUSH0
03c8KECCAK256
03c9SLOAD
03caPUSH20x03dc
03cdPUSH20x03d6
03d0DUP6
03d1DUP5
03d2PUSH20x1bc7
03d5JUMP
03d6JUMPDEST
03d7DUP8
03d8PUSH20x1c80
03dbJUMP
03dcJUMPDEST
03ddMSTORE
03deADD
03dfPUSH20x0398
03e2JUMP
03e3JUMPDEST
03e4SWAP1
03e5PUSH40x88c73b29
03eaPUSH10xe0
03ecSHL
03edPUSH0
03eeMSTORE
03efPUSH10x04
03f1MSTORE
03f2PUSH10x24
03f4MSTORE
03f5PUSH10x44
03f7PUSH0
03f8REVERT
03f9JUMPDEST
03faSWAP1
03fbPUSH40x5b8d5fdb
0400PUSH10xe1
0402SHL
0403PUSH0
0404MSTORE
0405PUSH10x04
0407MSTORE
0408PUSH10x24
040aMSTORE
040bPUSH10x44
040dPUSH0
040eREVERT
040fJUMPDEST
0410CALLVALUE
0411PUSH20x02ec
0414JUMPI
0415PUSH10x80
0417CALLDATASIZE
0418PUSH10x03
041aNOT
041bADD
041cSLT
041dPUSH20x02ec
0420JUMPI
0421PUSH10x04
0423CALLDATALOAD
0424PUSH10x01
0426PUSH10x01
0428PUSH10x40
042aSHL
042bSUB
042cDUP2
042dGT
042ePUSH20x02ec
0431JUMPI
0432PUSH20x043f
0435SWAP1
0436CALLDATASIZE
0437SWAP1
0438PUSH10x04
043aADD
043bPUSH20x1b01
043eJUMP
043fJUMPDEST
0440PUSH10x24
0442CALLDATALOAD
0443SWAP1
0444PUSH20x044b
0447PUSH20x1b31
044aJUMP
044bJUMPDEST
044cPUSH10x64
044eCALLDATALOAD
044fPUSH10x01
0451PUSH10x01
0453PUSH10x40
0455SHL
0456SUB
0457DUP2
0458GT
0459PUSH20x02ec
045cJUMPI
045dPUSH20x046a
0460SWAP1
0461CALLDATASIZE
0462SWAP1
0463PUSH10x04
0465ADD
0466PUSH20x1b01
0469JUMP
046aJUMPDEST
046bPUSH10x40
046dMLOAD
046ePUSH40x28305db1
0473PUSH10xe2
0475SHL
0476DUP2
0477MSTORE
0478PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0499PUSH10x01
049bPUSH10x01
049dPUSH10xa0
049fSHL
04a0SUB
04a1AND
04a2SWAP3
04a3SWAP1
04a4PUSH10x20
04a6DUP2
04a7PUSH10x04
04a9DUP2
04aaDUP8
04abGAS
04acSTATICCALL
04adSWAP1
04aeDUP2
04afISZERO
04b0PUSH20x0708
04b3JUMPI
04b4PUSH0
04b5SWAP2
04b6PUSH20x081b
04b9JUMPI
04baJUMPDEST
04bbPOP
04bcDUP1
04bdISZERO
04bePUSH20x07b5
04c1JUMPI
04c2JUMPDEST
04c3PUSH20x0619
04c6JUMPI
04c7JUMPDEST
04c8POP
04c9POP
04caPOP
04cbPOP
04ccPUSH10x06
04ceSLOAD
04cfPUSH20x060a
04d2JUMPI
04d3PUSH0
04d4DUP3
04d5DUP1
04d6JUMPDEST
04d7PUSH20x05e6
04daJUMPI
04dbPOP
04dcDUP2
04ddDUP2
04deSUB
04dfPUSH20x05d1
04e2JUMPI
04e3POP
04e4PUSH0
04e5DUP1
04e6PUSH10xff
04e8JUMPDEST
04e9PUSH10x01
04ebDUP1
04ecDUP7
04edDUP4
04eeSHR
04efAND
04f0EQ
04f1PUSH20x056e
04f4JUMPI
04f5JUMPDEST
04f6DUP1
04f7ISZERO
04f8PUSH20x051d
04fbJUMPI
04fcDUP1
04fdISZERO
04fePUSH20x0509
0501JUMPI
0502PUSH0
0503NOT
0504ADD
0505PUSH20x04e8
0508JUMP
0509JUMPDEST
050aPUSH40x4e487b71
050fPUSH10xe0
0511SHL
0512PUSH0
0513MSTORE
0514PUSH10x11
0516PUSH10x04
0518MSTORE
0519PUSH10x24
051bPUSH0
051cREVERT
051dJUMPDEST
051ePUSH320x67f9b61bf7b39fd24dd60467083f89ea77979db358db2804069474590a36c035
053fPUSH10x40
0541DUP7
0542DUP7
0543DUP2
0544PUSH10x06
0546SSTORE
0547DUP2
0548ISZERO
0549PUSH0
054aEQ
054bPUSH20x0560
054eJUMPI
054fPUSH0
0550JUMPDEST
0551PUSH10x07
0553SSTORE
0554DUP3
0555MLOAD
0556SWAP2
0557DUP3
0558MSTORE
0559PUSH10x20
055bDUP3
055cADD
055dMSTORE
055eLOG1
055fSTOP
0560JUMPDEST
0561PUSH20x0569
0564DUP3
0565PUSH20x206e
0568JUMP
0569JUMPDEST
056aPUSH20x0550
056dJUMP
056eJUMPDEST
056fSWAP1
0570PUSH20x057a
0573DUP4
0574DUP6
0575DUP9
0576PUSH20x1eb5
0579JUMP
057aJUMPDEST
057bCALLDATALOAD
057cISZERO
057dPUSH20x05c2
0580JUMPI
0581PUSH20x05bc
0584SWAP1
0585PUSH20x0598
0588PUSH20x0590
058bDUP6
058cPUSH20x212a
058fJUMP
0590JUMPDEST
0591SWAP5
0592DUP7
0593DUP10
0594PUSH20x1eb5
0597JUMP
0598JUMPDEST
0599CALLDATALOAD
059aDUP4
059bPUSH0
059cMSTORE
059dPUSH10x03
059fPUSH10x20
05a1MSTORE
05a2PUSH10x40
05a4PUSH0
05a5KECCAK256
05a6DUP3
05a7DUP6
05a8SHR
05a9PUSH0
05aaMSTORE
05abPUSH10x20
05adMSTORE
05aePUSH10x40
05b0PUSH0
05b1KECCAK256
05b2SSTORE
05b3PUSH10x01
05b5DUP4
05b6SHL
05b7SWAP1
05b8PUSH20x1bd4
05bbJUMP
05bcJUMPDEST
05bdSWAP1
05bePUSH20x04f5
05c1JUMP
05c2JUMPDEST
05c3PUSH40x4425ca13
05c8PUSH10xe0
05caSHL
05cbPUSH0
05ccMSTORE
05cdPUSH10x04
05cfPUSH0
05d0REVERT
05d1JUMPDEST
05d2PUSH40xecc9b8ed
05d7PUSH10xe0
05d9SHL
05daPUSH0
05dbMSTORE
05dcPUSH10x04
05deMSTORE
05dfPUSH10x24
05e1MSTORE
05e2PUSH10x44
05e4PUSH0
05e5REVERT
05e6JUMPDEST
05e7PUSH10x01
05e9DUP1
05eaDUP3
05ebAND
05ecEQ
05edPUSH20x05fa
05f0JUMPI
05f1JUMPDEST
05f2PUSH10x01
05f4SHR
05f5DUP1
05f6PUSH20x04d6
05f9JUMP
05faJUMPDEST
05fbSWAP1
05fcPUSH20x0604
05ffSWAP1
0600PUSH20x212a
0603JUMP
0604JUMPDEST
0605SWAP1
0606PUSH20x05f1
0609JUMP
060aJUMPDEST
060bPUSH40xdc63d81f
0610PUSH10xe0
0612SHL
0613PUSH0
0614MSTORE
0615PUSH10x04
0617PUSH0
0618REVERT
0619JUMPDEST
061aPUSH10x40
061cMLOAD
061dPUSH10x20
061fDUP2
0620ADD
0621SWAP1
0622PUSH10x40
0624DUP3
0625MSTORE
0626PUSH20x064b
0629DUP2
062aPUSH20x0637
062dPUSH10x60
062fDUP3
0630ADD
0631DUP11
0632DUP14
0633PUSH20x1e73
0636JUMP
0637JUMPDEST
0638DUP11
0639PUSH10x40
063bDUP4
063cADD
063dMSTORE
063eSUB
063fPUSH10x1f
0641NOT
0642DUP2
0643ADD
0644DUP4
0645MSTORE
0646DUP3
0647PUSH20x1be1
064aJUMP
064bJUMPDEST
064cMLOAD
064dSWAP1
064eKECCAK256
064fDUP4
0650EXTCODESIZE
0651ISZERO
0652PUSH20x02ec
0655JUMPI
0656SWAP1
0657DUP3
0658PUSH10x01
065aPUSH10x01
065cPUSH10x40
065eSHL
065fSUB
0660SWAP6
0661SWAP4
0662SWAP3
0663PUSH10x40
0665MLOAD
0666SWAP7
0667DUP8
0668SWAP6
0669PUSH40x22f3f447
066ePUSH10xe1
0670SHL
0671DUP8
0672MSTORE
0673PUSH10x84
0675DUP8
0676ADD
0677SWAP3
0678PUSH320x405bbda3343b6e69c32fb7eafff8f0a1e55a5ee2ec35458b3abc776b26681952
0699PUSH10x04
069bDUP10
069cADD
069dMSTORE
069ePUSH10x24
06a0DUP9
06a1ADD
06a2MSTORE
06a3AND
06a4PUSH10x44
06a6DUP7
06a7ADD
06a8MSTORE
06a9PUSH10x80
06abPUSH10x64
06adDUP7
06aeADD
06afMSTORE
06b0MSTORE
06b1PUSH10xa4
06b3DUP4
06b4ADD
06b5PUSH10xa0
06b7PUSH10x04
06b9DUP5
06baPUSH10x05
06bcSHL
06bdDUP7
06beADD
06bfADD
06c0ADD
06c1SWAP3
06c2DUP3
06c3PUSH0
06c4SWAP1
06c5PUSH10x7e
06c7NOT
06c8DUP2
06c9CALLDATASIZE
06caSUB
06cbADD
06ccJUMPDEST
06cdDUP4
06ceDUP4
06cfLT
06d0PUSH20x0713
06d3JUMPI
06d4POP
06d5POP
06d6POP
06d7POP
06d8POP
06d9POP
06daSWAP2
06dbDUP2
06dcPUSH0
06ddDUP2
06deDUP6
06dfDUP3
06e0SWAP7
06e1POP
06e2SUB
06e3SWAP3
06e4GAS
06e5CALL
06e6DUP1
06e7ISZERO
06e8PUSH20x0708
06ebJUMPI
06ecPUSH20x06f8
06efJUMPI
06f0JUMPDEST
06f1DUP1
06f2DUP1
06f3DUP1
06f4PUSH20x04c7
06f7JUMP
06f8JUMPDEST
06f9PUSH0
06faPUSH20x0702
06fdSWAP2
06fePUSH20x1be1
0701JUMP
0702JUMPDEST
0703DUP4
0704PUSH20x06f0
0707JUMP
0708JUMPDEST
0709PUSH10x40
070bMLOAD
070cRETURNDATASIZE
070dPUSH0
070eDUP3
070fRETURNDATACOPY
0710RETURNDATASIZE
0711SWAP1
0712REVERT
0713JUMPDEST
0714PUSH10xa3
0716NOT
0717DUP11
0718DUP9
0719SUB
071aADD
071bDUP6
071cMSTORE
071dSWAP5
071eSWAP7
071fPOP
0720SWAP3
0721SWAP5
0722SWAP2
0723SWAP4
0724SWAP1
0725SWAP3
0726SWAP2
0727DUP7
0728CALLDATALOAD
0729DUP3
072aDUP2
072bSLT
072cISZERO
072dPUSH20x02ec
0730JUMPI
0731DUP4
0732ADD
0733PUSH10x01
0735PUSH10x01
0737PUSH10xa0
0739SHL
073aSUB
073bPUSH20x0743
073eDUP3
073fPUSH20x1a77
0742JUMP
0743JUMPDEST
0744AND
0745DUP3
0746MSTORE
0747PUSH10x20
0749DUP2
074aADD
074bCALLDATALOAD
074cSWAP2
074dPUSH10xff
074fDUP4
0750AND
0751DUP1
0752SWAP4
0753SUB
0754PUSH20x02ec
0757JUMPI
0758PUSH20x07a3
075bPUSH10x20
075dSWAP3
075eDUP3
075fPUSH10x01
0761SWAP6
0762DUP6
0763DUP1
0764SWAP6
0765ADD
0766MSTORE
0767PUSH20x0795
076aPUSH20x078a
076dPUSH20x0779
0770PUSH10x40
0772DUP6
0773ADD
0774DUP6
0775PUSH20x1f15
0778JUMP
0779JUMPDEST
077aPUSH10x80
077cPUSH10x40
077eDUP7
077fADD
0780MSTORE
0781PUSH10x80
0783DUP6
0784ADD
0785SWAP2
0786PUSH20x1f46
0789JUMP
078aJUMPDEST
078bSWAP3
078cPUSH10x60
078eDUP2
078fADD
0790SWAP1
0791PUSH20x1f15
0794JUMP
0795JUMPDEST
0796SWAP2
0797PUSH10x60
0799DUP2
079aDUP6
079bSUB
079cSWAP2
079dADD
079eMSTORE
079fPUSH20x1f46
07a2JUMP
07a3JUMPDEST
07a4SWAP9
07a5ADD
07a6SWAP7
07a7ADD
07a8SWAP4
07a9ADD
07aaSWAP1
07abSWAP2
07acDUP9
07adSWAP7
07aeSWAP6
07afSWAP5
07b0SWAP3
07b1PUSH20x06cc
07b4JUMP
07b5JUMPDEST
07b6POP
07b7PUSH10x40
07b9MLOAD
07baPUSH40xf5778b03
07bfPUSH10xe0
07c1SHL
07c2DUP2
07c3MSTORE
07c4PUSH10x20
07c6DUP2
07c7PUSH10x04
07c9DUP2
07caDUP8
07cbGAS
07ccSTATICCALL
07cdSWAP1
07ceDUP2
07cfISZERO
07d0PUSH20x0708
07d3JUMPI
07d4PUSH0
07d5SWAP2
07d6PUSH20x07ec
07d9JUMPI
07daJUMPDEST
07dbPOP
07dcPUSH10x01
07dePUSH10x01
07e0PUSH10xa0
07e2SHL
07e3SUB
07e4AND
07e5CALLER
07e6EQ
07e7ISZERO
07e8PUSH20x04c2
07ebJUMP
07ecJUMPDEST
07edPUSH20x080e
07f0SWAP2
07f1POP
07f2PUSH10x20
07f4RETURNDATASIZE
07f5PUSH10x20
07f7GT
07f8PUSH20x0814
07fbJUMPI
07fcJUMPDEST
07fdPUSH20x0806
0800DUP2
0801DUP4
0802PUSH20x1be1
0805JUMP
0806JUMPDEST
0807DUP2
0808ADD
0809SWAP1
080aPUSH20x1ef6
080dJUMP
080eJUMPDEST
080fDUP9
0810PUSH20x07da
0813JUMP
0814JUMPDEST
0815POP
0816RETURNDATASIZE
0817PUSH20x07fc
081aJUMP
081bJUMPDEST
081cPUSH20x083d
081fSWAP2
0820POP
0821PUSH10x20
0823RETURNDATASIZE
0824PUSH10x20
0826GT
0827PUSH20x0843
082aJUMPI
082bJUMPDEST
082cPUSH20x0835
082fDUP2
0830DUP4
0831PUSH20x1be1
0834JUMP
0835JUMPDEST
0836DUP2
0837ADD
0838SWAP1
0839PUSH20x1ede
083cJUMP
083dJUMPDEST
083eDUP9
083fPUSH20x04ba
0842JUMP
0843JUMPDEST
0844POP
0845RETURNDATASIZE
0846PUSH20x082b
0849JUMP
084aJUMPDEST
084bCALLVALUE
084cPUSH20x02ec
084fJUMPI
0850PUSH0
0851CALLDATASIZE
0852PUSH10x03
0854NOT
0855ADD
0856SLT
0857PUSH20x02ec
085aJUMPI
085bPUSH10x20
085dPUSH0
085eSLOAD
085fPUSH10x40
0861MLOAD
0862SWAP1
0863DUP2
0864MSTORE
0865RETURN
0866JUMPDEST
0867CALLVALUE
0868PUSH20x02ec
086bJUMPI
086cPUSH10x20
086eCALLDATASIZE
086fPUSH10x03
0871NOT
0872ADD
0873SLT
0874PUSH20x02ec
0877JUMPI
0878PUSH10x20
087aPUSH20x0884
087dPUSH10x04
087fCALLDATALOAD
0880PUSH20x206e
0883JUMP
0884JUMPDEST
0885PUSH10x40
0887MLOAD
0888SWAP1
0889DUP2
088aMSTORE
088bRETURN
088cJUMPDEST
088dCALLVALUE
088ePUSH20x02ec
0891JUMPI
0892PUSH0
0893CALLDATASIZE
0894PUSH10x03
0896NOT
0897ADD
0898SLT
0899PUSH20x02ec
089cJUMPI
089dPUSH10x40
089fMLOAD
08a0PUSH320x000000000000000000000000c0876d136341091581a489ce7f746692dddf498f
08c1PUSH10x01
08c3PUSH10x01
08c5PUSH10xa0
08c7SHL
08c8SUB
08c9AND
08caDUP2
08cbMSTORE
08ccPUSH10x20
08ceSWAP1
08cfRETURN
08d0JUMPDEST
08d1CALLVALUE
08d2PUSH20x02ec
08d5JUMPI
08d6PUSH10xa0
08d8CALLDATASIZE
08d9PUSH10x03
08dbNOT
08dcADD
08ddSLT
08dePUSH20x02ec
08e1JUMPI
08e2PUSH20x08e9
08e5PUSH20x1a4b
08e8JUMP
08e9JUMPDEST
08eaPOP
08ebPUSH20x08f2
08eePUSH20x1a61
08f1JUMP
08f2JUMPDEST
08f3POP
08f4PUSH10x44
08f6CALLDATALOAD
08f7PUSH10x01
08f9PUSH10x01
08fbPUSH10x40
08fdSHL
08feSUB
08ffDUP2
0900GT
0901PUSH20x02ec
0904JUMPI
0905PUSH20x0912
0908SWAP1
0909CALLDATASIZE
090aSWAP1
090bPUSH10x04
090dADD
090ePUSH20x1b01
0911JUMP
0912JUMPDEST
0913POP
0914POP
0915PUSH10x64
0917CALLDATALOAD
0918PUSH10x01
091aPUSH10x01
091cPUSH10x40
091eSHL
091fSUB
0920DUP2
0921GT
0922PUSH20x02ec
0925JUMPI
0926PUSH20x0933
0929SWAP1
092aCALLDATASIZE
092bSWAP1
092cPUSH10x04
092eADD
092fPUSH20x1b01
0932JUMP
0933JUMPDEST
0934POP
0935POP
0936PUSH10x84
0938CALLDATALOAD
0939PUSH10x01
093bPUSH10x01
093dPUSH10x40
093fSHL
0940SUB
0941DUP2
0942GT
0943PUSH20x02ec
0946JUMPI
0947PUSH20x0954
094aSWAP1
094bCALLDATASIZE
094cSWAP1
094dPUSH10x04
094fADD
0950PUSH20x1a8b
0953JUMP
0954JUMPDEST
0955POP
0956POP
0957PUSH10x40
0959MLOAD
095aPUSH40xbc197c81
095fPUSH10xe0
0961SHL
0962DUP2
0963MSTORE
0964PUSH10x20
0966SWAP1
0967RETURN
0968JUMPDEST
0969CALLVALUE
096aPUSH20x02ec
096dJUMPI
096ePUSH0
096fCALLDATASIZE
0970PUSH10x03
0972NOT
0973ADD
0974SLT
0975PUSH20x02ec
0978JUMPI
0979PUSH10x20
097bPUSH10x40
097dMLOAD
097ePUSH320x27c91cbb7cc32319dd47788e8b096cc02ee8ccca641645266d7046769a12fbc3
099fDUP2
09a0MSTORE
09a1RETURN
09a2JUMPDEST
09a3CALLVALUE
09a4PUSH20x02ec
09a7JUMPI
09a8PUSH0
09a9CALLDATASIZE
09aaPUSH10x03
09acNOT
09adADD
09aeSLT
09afPUSH20x02ec
09b2JUMPI
09b3PUSH10x20
09b5PUSH10x01
09b7PUSH10x01
09b9PUSH10x40
09bbSHL
09bcSUB
09bdPUSH10x02
09bfSLOAD
09c0AND
09c1PUSH10x40
09c3MLOAD
09c4SWAP1
09c5DUP2
09c6MSTORE
09c7RETURN
09c8JUMPDEST
09c9CALLVALUE
09caPUSH20x02ec
09cdJUMPI
09cePUSH10xa0
09d0CALLDATASIZE
09d1PUSH10x03
09d3NOT
09d4ADD
09d5SLT
09d6PUSH20x02ec
09d9JUMPI
09daPUSH10x04
09dcCALLDATALOAD
09ddPUSH10x04
09dfDUP2
09e0LT
09e1ISZERO
09e2PUSH20x02ec
09e5JUMPI
09e6PUSH20x09ed
09e9PUSH20x1a61
09ecJUMP
09edJUMPDEST
09eeSWAP1
09efPUSH10x64
09f1CALLDATALOAD
09f2SWAP1
09f3PUSH10x84
09f5CALLDATALOAD
09f6PUSH10x01
09f8PUSH10x01
09faPUSH10xa0
09fcSHL
09fdSUB
09feDUP2
09ffAND
0a00SWAP2
0a01PUSH10x44
0a03CALLDATALOAD
0a04SWAP2
0a05DUP4
0a06DUP2
0a07SUB
0a08PUSH20x02ec
0a0bJUMPI
0a0cPUSH20x0a13
0a0fPUSH20x26e4
0a12JUMP
0a13JUMPDEST
0a14PUSH10x40
0a16MLOAD
0a17PUSH40xf5778b03
0a1cPUSH10xe0
0a1eSHL
0a1fDUP2
0a20MSTORE
0a21PUSH10x20
0a23DUP2
0a24PUSH10x04
0a26DUP2
0a27PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0a48PUSH10x01
0a4aPUSH10x01
0a4cPUSH10xa0
0a4eSHL
0a4fSUB
0a50AND
0a51GAS
0a52STATICCALL
0a53SWAP1
0a54DUP2
0a55ISZERO
0a56PUSH20x0708
0a59JUMPI
0a5aPUSH0
0a5bSWAP2
0a5cPUSH20x0c7c
0a5fJUMPI
0a60JUMPDEST
0a61POP
0a62DUP5
0a63ISZERO
0a64SWAP1
0a65DUP2
0a66ISZERO
0a67PUSH20x0c58
0a6aJUMPI
0a6bJUMPDEST
0a6cPOP
0a6dPUSH20x0c45
0a70JUMPI
0a71PUSH20x0a7b
0a74DUP4
0a75DUP8
0a76DUP5
0a77PUSH20x1ec5
0a7aJUMP
0a7bJUMPDEST
0a7cSWAP5
0a7dPUSH0
0a7eNOT
0a7fDUP2
0a80SUB
0a81PUSH20x0c40
0a84JUMPI
0a85POP
0a86DUP5
0a87JUMPDEST
0a88DUP1
0a89SWAP6
0a8aDUP2
0a8bISZERO
0a8cPUSH20x0c31
0a8fJUMPI
0a90DUP1
0a91DUP3
0a92GT
0a93PUSH20x0c0a
0a96JUMPI
0a97POP
0a98PUSH0
0a99SWAP2
0a9aDUP4
0a9bPUSH20x0b28
0a9eJUMPI
0a9fPOP
0aa0POP
0aa1PUSH0
0aa2DUP1
0aa3DUP1
0aa4DUP1
0aa5DUP9
0aa6DUP9
0aa7GAS
0aa8CALL
0aa9PUSH20x0ab0
0aacPUSH20x203f
0aafJUMP
0ab0JUMPDEST
0ab1POP
0ab2ISZERO
0ab3PUSH20x0b15
0ab6JUMPI
0ab7JUMPDEST
0ab8PUSH20x0b01
0abbJUMPI
0abcPUSH10x40
0abeDUP1
0abfMLOAD
0ac0SWAP3
0ac1DUP4
0ac2MSTORE
0ac3PUSH10x20
0ac5DUP4
0ac6DUP2
0ac7ADD
0ac8DUP7
0ac9SWAP1
0acaMSTORE
0acbSWAP6
0accPUSH10x01
0acePUSH10x01
0ad0PUSH10xa0
0ad2SHL
0ad3SUB
0ad4AND
0ad5SWAP3
0ad6PUSH320x7643c83e539cea2f6bf506545392e52cfd5f917e327efbcd0ba28f29c28d042e
0af7SWAP2
0af8SWAP1
0af9LOG4
0afaPUSH10x40
0afcMLOAD
0afdSWAP1
0afeDUP2
0affMSTORE
0b00RETURN
0b01JUMPDEST
0b02PUSH40x4e487b71
0b07PUSH10xe0
0b09SHL
0b0aPUSH0
0b0bMSTORE
0b0cPUSH10x21
0b0ePUSH10x04
0b10MSTORE
0b11PUSH10x24
0b13PUSH0
0b14REVERT
0b15JUMPDEST
0b16PUSH40x65f4a9ef
0b1bPUSH10xe1
0b1dSHL
0b1ePUSH0
0b1fMSTORE
0b20PUSH0
0b21PUSH10x04
0b23MSTORE
0b24PUSH10x24
0b26PUSH0
0b27REVERT
0b28JUMPDEST
0b29PUSH0
0b2aSWAP3
0b2bPOP
0b2cSWAP1
0b2dPUSH10x01
0b2fDUP5
0b30SUB
0b31PUSH20x0b78
0b34JUMPI
0b35POP
0b36PUSH10x40
0b38MLOAD
0b39PUSH40xa9059cbb
0b3ePUSH10xe0
0b40SHL
0b41PUSH10x20
0b43DUP3
0b44ADD
0b45MSTORE
0b46PUSH10x01
0b48PUSH10x01
0b4aPUSH10xa0
0b4cSHL
0b4dSUB
0b4eSWAP1
0b4fSWAP2
0b50AND
0b51PUSH10x24
0b53DUP3
0b54ADD
0b55MSTORE
0b56PUSH10x44
0b58DUP2
0b59ADD
0b5aDUP7
0b5bSWAP1
0b5cMSTORE
0b5dPUSH20x0b73
0b60SWAP1
0b61PUSH20x0b6d
0b64DUP2
0b65PUSH10x64
0b67DUP2
0b68ADD
0b69PUSH20x01d0
0b6cJUMP
0b6dJUMPDEST
0b6eDUP8
0b6fPUSH20x2882
0b72JUMP
0b73JUMPDEST
0b74PUSH20x0ab7
0b77JUMP
0b78JUMPDEST
0b79PUSH0
0b7aSWAP7
0b7bSWAP3
0b7cPOP
0b7dSWAP1
0b7ePOP
0b7fPUSH10x02
0b81DUP4
0b82SUB
0b83PUSH20x0bbf
0b86JUMPI
0b87POP
0b88POP
0b89PUSH10x01
0b8bSWAP4
0b8cPUSH20x0b73
0b8fPUSH10x40
0b91MLOAD
0b92PUSH40x23b872dd
0b97PUSH10xe0
0b99SHL
0b9aPUSH10x20
0b9cDUP3
0b9dADD
0b9eMSTORE
0b9fADDRESS
0ba0PUSH10x24
0ba2DUP3
0ba3ADD
0ba4MSTORE
0ba5DUP6
0ba6PUSH10x44
0ba8DUP3
0ba9ADD
0baaMSTORE
0babDUP5
0bacPUSH10x64
0baeDUP3
0bafADD
0bb0MSTORE
0bb1PUSH10x64
0bb3DUP2
0bb4MSTORE
0bb5PUSH20x0b6d
0bb8PUSH10x84
0bbaDUP3
0bbbPUSH20x1be1
0bbeJUMP
0bbfJUMPDEST
0bc0PUSH20x0b73
0bc3SWAP1
0bc4PUSH10x40
0bc6SWAP7
0bc7SWAP3
0bc8SWAP7
0bc9MLOAD
0bcaSWAP1
0bcbPUSH40x79212195
0bd0PUSH10xe1
0bd2SHL
0bd3PUSH10x20
0bd5DUP4
0bd6ADD
0bd7MSTORE
0bd8ADDRESS
0bd9PUSH10x24
0bdbDUP4
0bdcADD
0bddMSTORE
0bdeDUP7
0bdfPUSH10x44
0be1DUP4
0be2ADD
0be3MSTORE
0be4DUP6
0be5PUSH10x64
0be7DUP4
0be8ADD
0be9MSTORE
0beaPUSH10x84
0becDUP3
0bedADD
0beeMSTORE
0befPUSH10xa0
0bf1PUSH10xa4
0bf3DUP3
0bf4ADD
0bf5MSTORE
0bf6PUSH0
0bf7PUSH10xc4
0bf9DUP3
0bfaADD
0bfbMSTORE
0bfcPUSH10xc4
0bfeDUP2
0bffMSTORE
0c00PUSH20x0b6d
0c03PUSH10xe4
0c05DUP3
0c06PUSH20x1be1
0c09JUMP
0c0aJUMPDEST
0c0bPUSH40x21909681
0c10PUSH10xe0
0c12SHL
0c13PUSH0
0c14SWAP1
0c15DUP2
0c16MSTORE
0c17PUSH10x01
0c19PUSH10x01
0c1bPUSH10xa0
0c1dSHL
0c1eSUB
0c1fDUP10
0c20AND
0c21PUSH10x04
0c23MSTORE
0c24PUSH10x24
0c26SWAP3
0c27SWAP1
0c28SWAP3
0c29MSTORE
0c2aPUSH10x44
0c2cMSTORE
0c2dPUSH10x64
0c2fSWAP1
0c30REVERT
0c31JUMPDEST
0c32PUSH40x7c2e506f
0c37PUSH10xe1
0c39SHL
0c3aPUSH0
0c3bMSTORE
0c3cPUSH10x04
0c3ePUSH0
0c3fREVERT
0c40JUMPDEST
0c41PUSH20x0a87
0c44JUMP
0c45JUMPDEST
0c46DUP4
0c47PUSH40x15150d4d
0c4cPUSH10xe3
0c4eSHL
0c4fPUSH0
0c50MSTORE
0c51PUSH10x04
0c53MSTORE
0c54PUSH10x24
0c56PUSH0
0c57REVERT
0c58JUMPDEST
0c59PUSH10x01
0c5bPUSH10x01
0c5dPUSH10xa0
0c5fSHL
0c60SUB
0c61AND
0c62DUP6
0c63EQ
0c64ISZERO
0c65SWAP1
0c66POP
0c67DUP1
0c68PUSH20x0c72
0c6bJUMPI
0c6cJUMPDEST
0c6dDUP8
0c6ePUSH20x0a6b
0c71JUMP
0c72JUMPDEST
0c73POP
0c74CALLER
0c75DUP5
0c76EQ
0c77ISZERO
0c78PUSH20x0c6c
0c7bJUMP
0c7cJUMPDEST
0c7dPUSH20x0c95
0c80SWAP2
0c81POP
0c82PUSH10x20
0c84RETURNDATASIZE
0c85PUSH10x20
0c87GT
0c88PUSH20x0814
0c8bJUMPI
0c8cPUSH20x0806
0c8fDUP2
0c90DUP4
0c91PUSH20x1be1
0c94JUMP
0c95JUMPDEST
0c96DUP8
0c97PUSH20x0a60
0c9aJUMP
0c9bJUMPDEST
0c9cCALLVALUE
0c9dPUSH20x02ec
0ca0JUMPI
0ca1PUSH10x80
0ca3CALLDATASIZE
0ca4PUSH10x03
0ca6NOT
0ca7ADD
0ca8SLT
0ca9PUSH20x02ec
0cacJUMPI
0cadPUSH10x24
0cafCALLDATALOAD
0cb0PUSH10x04
0cb2CALLDATALOAD
0cb3PUSH20x0cba
0cb6PUSH20x1b31
0cb9JUMP
0cbaJUMPDEST
0cbbPUSH10x64
0cbdCALLDATALOAD
0cbePUSH10x01
0cc0PUSH10x01
0cc2PUSH10x40
0cc4SHL
0cc5SUB
0cc6DUP2
0cc7GT
0cc8PUSH20x02ec
0ccbJUMPI
0cccPUSH20x0cd9
0ccfSWAP1
0cd0CALLDATASIZE
0cd1SWAP1
0cd2PUSH10x04
0cd4ADD
0cd5PUSH20x1b01
0cd8JUMP
0cd9JUMPDEST
0cdaPUSH10x40
0cdcMLOAD
0cddPUSH40x28305db1
0ce2PUSH10xe2
0ce4SHL
0ce5DUP2
0ce6MSTORE
0ce7PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0d08PUSH10x01
0d0aPUSH10x01
0d0cPUSH10xa0
0d0eSHL
0d0fSUB
0d10AND
0d11SWAP4
0d12SWAP3
0d13SWAP1
0d14PUSH10x20
0d16DUP2
0d17PUSH10x04
0d19DUP2
0d1aDUP9
0d1bGAS
0d1cSTATICCALL
0d1dSWAP1
0d1eDUP2
0d1fISZERO
0d20PUSH20x0708
0d23JUMPI
0d24PUSH0
0d25SWAP2
0d26PUSH20x0f9d
0d29JUMPI
0d2aJUMPDEST
0d2bPOP
0d2cDUP1
0d2dISZERO
0d2ePUSH20x0f47
0d31JUMPI
0d32JUMPDEST
0d33PUSH20x0df7
0d36JUMPI
0d37JUMPDEST
0d38POP
0d39POP
0d3aPOP
0d3bDUP3
0d3cPUSH20x0d79
0d3fJUMPI
0d40JUMPDEST
0d41PUSH320xbfc08a458e488f0e56f7ff4bfe317bed1ba5d3f7ef5a2bda241528695f6fcdf3
0d62PUSH10x40
0d64DUP4
0d65DUP6
0d66DUP2
0d67PUSH0
0d68SSTORE
0d69DUP1
0d6aPUSH10x01
0d6cSSTORE
0d6dDUP3
0d6eMLOAD
0d6fSWAP2
0d70DUP3
0d71MSTORE
0d72PUSH10x20
0d74DUP3
0d75ADD
0d76MSTORE
0d77LOG1
0d78STOP
0d79JUMPDEST
0d7aPUSH10x20
0d7cPUSH10x24
0d7eSWAP2
0d7fPUSH10x40
0d81MLOAD
0d82SWAP3
0d83DUP4
0d84DUP1
0d85SWAP3
0d86PUSH40x342f6163
0d8bPUSH10xe0
0d8dSHL
0d8eDUP3
0d8fMSTORE
0d90DUP7
0d91PUSH10x04
0d93DUP4
0d94ADD
0d95MSTORE
0d96GAS
0d97STATICCALL
0d98SWAP1
0d99DUP2
0d9aISZERO
0d9bPUSH20x0708
0d9eJUMPI
0d9fPUSH0
0da0SWAP2
0da1PUSH20x0dc5
0da4JUMPI
0da5JUMPDEST
0da6POP
0da7DUP3
0da8DUP2
0da9LT
0daaISZERO
0dabPUSH20x0d40
0daeJUMPI
0dafSWAP1
0db0POP
0db1PUSH40x3770da33
0db6PUSH10xe1
0db8SHL
0db9PUSH0
0dbaMSTORE
0dbbPUSH10x04
0dbdMSTORE
0dbePUSH10x24
0dc0MSTORE
0dc1PUSH10x44
0dc3PUSH0
0dc4REVERT
0dc5JUMPDEST
0dc6SWAP1
0dc7POP
0dc8PUSH10x20
0dcaDUP2
0dcbRETURNDATASIZE
0dccPUSH10x20
0dceGT
0dcfPUSH20x0def
0dd2JUMPI
0dd3JUMPDEST
0dd4DUP2
0dd5PUSH20x0de0
0dd8PUSH10x20
0ddaSWAP4
0ddbDUP4
0ddcPUSH20x1be1
0ddfJUMP
0de0JUMPDEST
0de1DUP2
0de2ADD
0de3SUB
0de4SLT
0de5PUSH20x02ec
0de8JUMPI
0de9MLOAD
0deaDUP4
0debPUSH20x0da5
0deeJUMP
0defJUMPDEST
0df0RETURNDATASIZE
0df1SWAP2
0df2POP
0df3PUSH20x0dd3
0df6JUMP
0df7JUMPDEST
0df8PUSH10x40
0dfaMLOAD
0dfbPUSH10x20
0dfdDUP2
0dfeADD
0dffSWAP1
0e00DUP7
0e01DUP3
0e02MSTORE
0e03DUP8
0e04PUSH10x40
0e06DUP3
0e07ADD
0e08MSTORE
0e09PUSH10x40
0e0bDUP2
0e0cMSTORE
0e0dPUSH20x0e17
0e10PUSH10x60
0e12DUP3
0e13PUSH20x1be1
0e16JUMP
0e17JUMPDEST
0e18MLOAD
0e19SWAP1
0e1aKECCAK256
0e1bDUP5
0e1cEXTCODESIZE
0e1dISZERO
0e1ePUSH20x02ec
0e21JUMPI
0e22SWAP1
0e23DUP3
0e24PUSH10x01
0e26PUSH10x01
0e28PUSH10x40
0e2aSHL
0e2bSUB
0e2cSWAP5
0e2dSWAP3
0e2ePUSH10x40
0e30MLOAD
0e31SWAP6
0e32DUP7
0e33SWAP5
0e34PUSH40x22f3f447
0e39PUSH10xe1
0e3bSHL
0e3cDUP7
0e3dMSTORE
0e3ePUSH10x84
0e40DUP7
0e41ADD
0e42SWAP3
0e43PUSH320x27c91cbb7cc32319dd47788e8b096cc02ee8ccca641645266d7046769a12fbc3
0e64PUSH10x04
0e66DUP9
0e67ADD
0e68MSTORE
0e69PUSH10x24
0e6bDUP8
0e6cADD
0e6dMSTORE
0e6eAND
0e6fPUSH10x44
0e71DUP6
0e72ADD
0e73MSTORE
0e74PUSH10x80
0e76PUSH10x64
0e78DUP6
0e79ADD
0e7aMSTORE
0e7bMSTORE
0e7cPUSH10xa4
0e7eDUP3
0e7fADD
0e80PUSH10xa0
0e82PUSH10x04
0e84DUP6
0e85PUSH10x05
0e87SHL
0e88DUP6
0e89ADD
0e8aADD
0e8bADD
0e8cSWAP4
0e8dDUP3
0e8ePUSH0
0e8fSWAP1
0e90PUSH10x7e
0e92NOT
0e93DUP2
0e94CALLDATASIZE
0e95SUB
0e96ADD
0e97JUMPDEST
0e98DUP4
0e99DUP4
0e9aLT
0e9bPUSH20x0ed1
0e9eJUMPI
0e9fPOP
0ea0POP
0ea1POP
0ea2POP
0ea3POP
0ea4POP
0ea5DUP1
0ea6DUP3
0ea7PUSH0
0ea8SWAP4
0ea9POP
0eaaSUB
0eabDUP2
0eacDUP4
0eadDUP7
0eaeGAS
0eafCALL
0eb0DUP1
0eb1ISZERO
0eb2PUSH20x0708
0eb5JUMPI
0eb6PUSH20x0ec1
0eb9JUMPI
0ebaJUMPDEST
0ebbDUP1
0ebcDUP1
0ebdPUSH20x0d37
0ec0JUMP
0ec1JUMPDEST
0ec2PUSH0
0ec3PUSH20x0ecb
0ec6SWAP2
0ec7PUSH20x1be1
0ecaJUMP
0ecbJUMPDEST
0eccDUP4
0ecdPUSH20x0eba
0ed0JUMP
0ed1JUMPDEST
0ed2PUSH10xa3
0ed4NOT
0ed5DUP10
0ed6DUP10
0ed7SUB
0ed8ADD
0ed9DUP6
0edaMSTORE
0edbSWAP5
0edcSWAP7
0eddSWAP4
0edeSWAP6
0edfPOP
0ee0SWAP2
0ee1SWAP4
0ee2SWAP1
0ee3SWAP3
0ee4DUP7
0ee5CALLDATALOAD
0ee6DUP3
0ee7DUP2
0ee8SLT
0ee9ISZERO
0eeaPUSH20x02ec
0eedJUMPI
0eeeDUP4
0eefADD
0ef0PUSH10x01
0ef2PUSH10x01
0ef4PUSH10xa0
0ef6SHL
0ef7SUB
0ef8PUSH20x0f00
0efbDUP3
0efcPUSH20x1a77
0effJUMP
0f00JUMPDEST
0f01AND
0f02DUP3
0f03MSTORE
0f04PUSH10x20
0f06DUP2
0f07ADD
0f08CALLDATALOAD
0f09SWAP2
0f0aPUSH10xff
0f0cDUP4
0f0dAND
0f0eDUP1
0f0fSWAP4
0f10SUB
0f11PUSH20x02ec
0f14JUMPI
0f15PUSH20x0f36
0f18PUSH10x20
0f1aSWAP3
0f1bDUP3
0f1cPUSH10x01
0f1eSWAP6
0f1fDUP6
0f20DUP1
0f21SWAP6
0f22ADD
0f23MSTORE
0f24PUSH20x0795
0f27PUSH20x078a
0f2aPUSH20x0779
0f2dPUSH10x40
0f2fDUP6
0f30ADD
0f31DUP6
0f32PUSH20x1f15
0f35JUMP
0f36JUMPDEST
0f37SWAP9
0f38ADD
0f39SWAP7
0f3aADD
0f3bSWAP4
0f3cADD
0f3dSWAP1
0f3eSWAP2
0f3fDUP8
0f40SWAP6
0f41SWAP5
0f42SWAP3
0f43PUSH20x0e97
0f46JUMP
0f47JUMPDEST
0f48POP
0f49PUSH10x40
0f4bMLOAD
0f4cPUSH40xf5778b03
0f51PUSH10xe0
0f53SHL
0f54DUP2
0f55MSTORE
0f56PUSH10x20
0f58DUP2
0f59PUSH10x04
0f5bDUP2
0f5cDUP9
0f5dGAS
0f5eSTATICCALL
0f5fSWAP1
0f60DUP2
0f61ISZERO
0f62PUSH20x0708
0f65JUMPI
0f66PUSH0
0f67SWAP2
0f68PUSH20x0f7e
0f6bJUMPI
0f6cJUMPDEST
0f6dPOP
0f6ePUSH10x01
0f70PUSH10x01
0f72PUSH10xa0
0f74SHL
0f75SUB
0f76AND
0f77CALLER
0f78EQ
0f79ISZERO
0f7aPUSH20x0d32
0f7dJUMP
0f7eJUMPDEST
0f7fPUSH20x0f97
0f82SWAP2
0f83POP
0f84PUSH10x20
0f86RETURNDATASIZE
0f87PUSH10x20
0f89GT
0f8aPUSH20x0814
0f8dJUMPI
0f8ePUSH20x0806
0f91DUP2
0f92DUP4
0f93PUSH20x1be1
0f96JUMP
0f97JUMPDEST
0f98DUP8
0f99PUSH20x0f6c
0f9cJUMP
0f9dJUMPDEST
0f9ePUSH20x0fb6
0fa1SWAP2
0fa2POP
0fa3PUSH10x20
0fa5RETURNDATASIZE
0fa6PUSH10x20
0fa8GT
0fa9PUSH20x0843
0facJUMPI
0fadPUSH20x0835
0fb0DUP2
0fb1DUP4
0fb2PUSH20x1be1
0fb5JUMP
0fb6JUMPDEST
0fb7DUP8
0fb8PUSH20x0d2a
0fbbJUMP
0fbcJUMPDEST
0fbdCALLVALUE
0fbePUSH20x02ec
0fc1JUMPI
0fc2PUSH0
0fc3CALLDATASIZE
0fc4PUSH10x03
0fc6NOT
0fc7ADD
0fc8SLT
0fc9PUSH20x02ec
0fccJUMPI
0fcdPUSH10x20
0fcfPUSH10x06
0fd1SLOAD
0fd2PUSH10x40
0fd4MLOAD
0fd5SWAP1
0fd6DUP2
0fd7MSTORE
0fd8RETURN
0fd9JUMPDEST
0fdaCALLVALUE
0fdbPUSH20x02ec
0fdeJUMPI
0fdfPUSH0
0fe0CALLDATASIZE
0fe1PUSH10x03
0fe3NOT
0fe4ADD
0fe5SLT
0fe6PUSH20x02ec
0fe9JUMPI
0feaPUSH10x40
0fecPUSH10x07
0feeSLOAD
0fefPUSH10x06
0ff1SLOAD
0ff2DUP3
0ff3MLOAD
0ff4SWAP2
0ff5DUP3
0ff6MSTORE
0ff7PUSH10x20
0ff9DUP3
0ffaADD
0ffbMSTORE
0ffcRETURN
0ffdJUMPDEST
0ffeCALLVALUE
0fffPUSH20x02ec
1002JUMPI
1003PUSH0
1004CALLDATASIZE
1005PUSH10x03
1007NOT
1008ADD
1009SLT
100aPUSH20x02ec
100dJUMPI
100ePUSH10x40
1010MLOAD
1011PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
1032PUSH10x01
1034PUSH10x01
1036PUSH10xa0
1038SHL
1039SUB
103aAND
103bDUP2
103cMSTORE
103dPUSH10x20
103fSWAP1
1040RETURN
1041JUMPDEST
1042CALLVALUE
1043PUSH20x02ec
1046JUMPI
1047PUSH0
1048CALLDATASIZE
1049PUSH10x03
104bNOT
104cADD
104dSLT
104ePUSH20x02ec
1051JUMPI
1052PUSH10x20
1054PUSH10x40
1056MLOAD
1057PUSH320x405bbda3343b6e69c32fb7eafff8f0a1e55a5ee2ec35458b3abc776b26681952
1078DUP2
1079MSTORE
107aRETURN
107bJUMPDEST
107cCALLVALUE
107dPUSH20x02ec
1080JUMPI
1081PUSH10x20
1083CALLDATASIZE
1084PUSH10x03
1086NOT
1087ADD
1088SLT
1089PUSH20x02ec
108cJUMPI
108dPUSH10x40
108fPUSH20x1099
1092PUSH10x04
1094CALLDATALOAD
1095PUSH20x1ffa
1098JUMP
1099JUMPDEST
109aDUP3
109bMLOAD
109cSWAP2
109dDUP3
109eMSTORE
109fISZERO
10a0ISZERO
10a1PUSH10x20
10a3DUP3
10a4ADD
10a5MSTORE
10a6RETURN
10a7JUMPDEST
10a8CALLVALUE
10a9PUSH20x02ec
10acJUMPI
10adPUSH10x20
10afCALLDATASIZE
10b0PUSH10x03
10b2NOT
10b3ADD
10b4SLT
10b5PUSH20x02ec
10b8JUMPI
10b9PUSH20x03b5
10bcPUSH20x10c6
10bfPUSH10x04
10c1CALLDATALOAD
10c2PUSH20x1f66
10c5JUMP
10c6JUMPDEST
10c7PUSH10x40
10c9MLOAD
10caSWAP2
10cbDUP3
10ccSWAP2
10cdPUSH10x20
10cfDUP4
10d0MSTORE
10d1PUSH10x20
10d3DUP4
10d4ADD
10d5SWAP1
10d6PUSH20x1ace
10d9JUMP
10daJUMPDEST
10dbCALLVALUE
10dcPUSH20x02ec
10dfJUMPI
10e0PUSH20x10e8
10e3CALLDATASIZE
10e4PUSH20x1b47
10e7JUMP
10e8JUMPDEST
10e9PUSH10x40
10ebMLOAD
10ecPUSH40x28305db1
10f1PUSH10xe2
10f3SHL
10f4DUP2
10f5MSTORE
10f6SWAP4
10f7SWAP5
10f8SWAP4
10f9PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
111aPUSH10x01
111cPUSH10x01
111ePUSH10xa0
1120SHL
1121SUB
1122AND
1123SWAP3
1124SWAP1
1125PUSH10x20
1127DUP2
1128PUSH10x04
112aDUP2
112bDUP8
112cGAS
112dSTATICCALL
112eSWAP1
112fDUP2
1130ISZERO
1131PUSH20x0708
1134JUMPI
1135PUSH0
1136SWAP2
1137PUSH20x134f
113aJUMPI
113bJUMPDEST
113cPOP
113dDUP1
113eISZERO
113fPUSH20x12f9
1142JUMPI
1143JUMPDEST
1144PUSH20x11a6
1147JUMPI
1148JUMPDEST
1149POP
114aPOP
114bPOP
114cPOP
114dPUSH10x06
114fSLOAD
1150PUSH20x060a
1153JUMPI
1154DUP2
1155ISZERO
1156PUSH20x02dd
1159JUMPI
115aPUSH0
115bJUMPDEST
115cDUP3
115dDUP2
115eLT
115fPUSH20x118f
1162JUMPI
1163PUSH320x1c295873c1ce4ce2ac720f43d6909e66b931b42e9246b862278eba9624c0bf05
1184PUSH10x20
1186DUP5
1187PUSH10x40
1189MLOAD
118aSWAP1
118bDUP2
118cMSTORE
118dLOG1
118eSTOP
118fJUMPDEST
1190DUP1
1191PUSH20x11a0
1194PUSH20x02c2
1197PUSH10x01
1199SWAP4
119aDUP7
119bDUP7
119cPUSH20x1eb5
119fJUMP
11a0JUMPDEST
11a1ADD
11a2PUSH20x115b
11a5JUMP
11a6JUMPDEST
11a7PUSH10x40
11a9MLOAD
11aaPUSH10x20
11acDUP2
11adADD
11aeSWAP1
11afPUSH10x20
11b1DUP3
11b2MSTORE
11b3PUSH20x11c4
11b6DUP2
11b7PUSH20x01d0
11baPUSH10x40
11bcDUP3
11bdADD
11beDUP12
11bfDUP12
11c0PUSH20x1e73
11c3JUMP
11c4JUMPDEST
11c5MLOAD
11c6SWAP1
11c7KECCAK256
11c8DUP4
11c9EXTCODESIZE
11caISZERO
11cbPUSH20x02ec
11ceJUMPI
11cfSWAP1
11d0DUP3
11d1PUSH10x01
11d3PUSH10x01
11d5PUSH10x40
11d7SHL
11d8SUB
11d9SWAP6
11daSWAP4
11dbSWAP3
11dcPUSH10x40
11deMLOAD
11dfSWAP7
11e0DUP8
11e1SWAP6
11e2PUSH40x22f3f447
11e7PUSH10xe1
11e9SHL
11eaDUP8
11ebMSTORE
11ecPUSH10x84
11eeDUP8
11efADD
11f0SWAP3
11f1PUSH320x9abdf9961fd14fd177480eccbad16b2d7f231898b2d763c8e8b50364d8b3b171
1212PUSH10x04
1214DUP10
1215ADD
1216MSTORE
1217PUSH10x24
1219DUP9
121aADD
121bMSTORE
121cAND
121dPUSH10x44
121fDUP7
1220ADD
1221MSTORE
1222PUSH10x80
1224PUSH10x64
1226DUP7
1227ADD
1228MSTORE
1229MSTORE
122aPUSH10xa4
122cDUP4
122dADD
122ePUSH10xa0
1230PUSH10x04
1232DUP5
1233PUSH10x05
1235SHL
1236DUP7
1237ADD
1238ADD
1239ADD
123aSWAP3
123bDUP3
123cPUSH0
123dSWAP1
123ePUSH10x7e
1240NOT
1241DUP2
1242CALLDATASIZE
1243SUB
1244ADD
1245JUMPDEST
1246DUP4
1247DUP4
1248LT
1249PUSH20x1281
124cJUMPI
124dPOP
124ePOP
124fPOP
1250POP
1251POP
1252POP
1253SWAP2
1254DUP2
1255PUSH0
1256DUP2
1257DUP6
1258DUP3
1259SWAP7
125aPOP
125bSUB
125cSWAP3
125dGAS
125eCALL
125fDUP1
1260ISZERO
1261PUSH20x0708
1264JUMPI
1265PUSH20x1271
1268JUMPI
1269JUMPDEST
126aDUP1
126bDUP1
126cDUP1
126dPUSH20x1148
1270JUMP
1271JUMPDEST
1272PUSH0
1273PUSH20x127b
1276SWAP2
1277PUSH20x1be1
127aJUMP
127bJUMPDEST
127cDUP3
127dPUSH20x1269
1280JUMP
1281JUMPDEST
1282PUSH10xa3
1284NOT
1285DUP11
1286DUP9
1287SUB
1288ADD
1289DUP6
128aMSTORE
128bSWAP5
128cSWAP7
128dPOP
128eSWAP3
128fSWAP5
1290SWAP2
1291SWAP4
1292SWAP1
1293SWAP3
1294SWAP2
1295DUP7
1296CALLDATALOAD
1297DUP3
1298DUP2
1299SLT
129aISZERO
129bPUSH20x02ec
129eJUMPI
129fDUP4
12a0ADD
12a1PUSH10x01
12a3PUSH10x01
12a5PUSH10xa0
12a7SHL
12a8SUB
12a9PUSH20x12b1
12acDUP3
12adPUSH20x1a77
12b0JUMP
12b1JUMPDEST
12b2AND
12b3DUP3
12b4MSTORE
12b5PUSH10x20
12b7DUP2
12b8ADD
12b9CALLDATALOAD
12baSWAP2
12bbPUSH10xff
12bdDUP4
12beAND
12bfDUP1
12c0SWAP4
12c1SUB
12c2PUSH20x02ec
12c5JUMPI
12c6PUSH20x12e7
12c9PUSH10x20
12cbSWAP3
12ccDUP3
12cdPUSH10x01
12cfSWAP6
12d0DUP6
12d1DUP1
12d2SWAP6
12d3ADD
12d4MSTORE
12d5PUSH20x0795
12d8PUSH20x078a
12dbPUSH20x0779
12dePUSH10x40
12e0DUP6
12e1ADD
12e2DUP6
12e3PUSH20x1f15
12e6JUMP
12e7JUMPDEST
12e8SWAP9
12e9ADD
12eaSWAP7
12ebADD
12ecSWAP4
12edADD
12eeSWAP1
12efSWAP2
12f0DUP9
12f1SWAP7
12f2SWAP6
12f3SWAP5
12f4SWAP3
12f5PUSH20x1245
12f8JUMP
12f9JUMPDEST
12faPOP
12fbPUSH10x40
12fdMLOAD
12fePUSH40xf5778b03
1303PUSH10xe0
1305SHL
1306DUP2
1307MSTORE
1308PUSH10x20
130aDUP2
130bPUSH10x04
130dDUP2
130eDUP8
130fGAS
1310STATICCALL
1311SWAP1
1312DUP2
1313ISZERO
1314PUSH20x0708
1317JUMPI
1318PUSH0
1319SWAP2
131aPUSH20x1330
131dJUMPI
131eJUMPDEST
131fPOP
1320PUSH10x01
1322PUSH10x01
1324PUSH10xa0
1326SHL
1327SUB
1328AND
1329CALLER
132aEQ
132bISZERO
132cPUSH20x1143
132fJUMP
1330JUMPDEST
1331PUSH20x1349
1334SWAP2
1335POP
1336PUSH10x20
1338RETURNDATASIZE
1339PUSH10x20
133bGT
133cPUSH20x0814
133fJUMPI
1340PUSH20x0806
1343DUP2
1344DUP4
1345PUSH20x1be1
1348JUMP
1349JUMPDEST
134aDUP8
134bPUSH20x131e
134eJUMP
134fJUMPDEST
1350PUSH20x1368
1353SWAP2
1354POP
1355PUSH10x20
1357RETURNDATASIZE
1358PUSH10x20
135aGT
135bPUSH20x0843
135eJUMPI
135fPUSH20x0835
1362DUP2
1363DUP4
1364PUSH20x1be1
1367JUMP
1368JUMPDEST
1369DUP8
136aPUSH20x113b
136dJUMP
136eJUMPDEST
136fCALLVALUE
1370PUSH20x02ec
1373JUMPI
1374PUSH10x60
1376CALLDATASIZE
1377PUSH10x03
1379NOT
137aADD
137bSLT
137cPUSH20x02ec
137fJUMPI
1380PUSH10x04
1382CALLDATALOAD
1383PUSH10x04
1385DUP2
1386LT
1387ISZERO
1388PUSH20x02ec
138bJUMPI
138cPUSH20x0884
138fPUSH10x20
1391SWAP2
1392PUSH20x1399
1395PUSH20x1a61
1398JUMP
1399JUMPDEST
139aPUSH10x44
139cCALLDATALOAD
139dSWAP2
139ePUSH20x1ec5
13a1JUMP
13a2JUMPDEST
13a3CALLVALUE
13a4PUSH20x02ec
13a7JUMPI
13a8PUSH0
13a9CALLDATASIZE
13aaPUSH10x03
13acNOT
13adADD
13aeSLT
13afPUSH20x02ec
13b2JUMPI
13b3PUSH20x03b5
13b6PUSH20x10c6
13b9PUSH10x06
13bbSLOAD
13bcPUSH20x1f66
13bfJUMP
13c0JUMPDEST
13c1CALLVALUE
13c2PUSH20x02ec
13c5JUMPI
13c6PUSH0
13c7CALLDATASIZE
13c8PUSH10x03
13caNOT
13cbADD
13ccSLT
13cdPUSH20x02ec
13d0JUMPI
13d1PUSH10x20
13d3PUSH10x40
13d5MLOAD
13d6PUSH320xb66ca34dc0d9a9daa6230aee35894330ccfa7e4eaa29a198577eed0b26a41205
13f7DUP2
13f8MSTORE
13f9RETURN
13faJUMPDEST
13fbCALLVALUE
13fcPUSH20x02ec
13ffJUMPI
1400PUSH20x1408
1403CALLDATASIZE
1404PUSH20x1ab8
1407JUMP
1408JUMPDEST
1409PUSH20x1424
140cPUSH20x141e
140fPUSH20x1418
1412DUP4
1413DUP6
1414PUSH20x1c94
1417JUMP
1418JUMPDEST
1419SWAP4
141aPUSH20x1bac
141dJUMP
141eJUMPDEST
141fSWAP2
1420PUSH20x206e
1423JUMP
1424JUMPDEST
1425PUSH20x1440
1428PUSH10x40
142aMLOAD
142bSWAP4
142cDUP5
142dSWAP4
142eDUP5
142fMSTORE
1430PUSH10x60
1432PUSH10x20
1434DUP6
1435ADD
1436MSTORE
1437PUSH10x60
1439DUP5
143aADD
143bSWAP1
143cPUSH20x1ace
143fJUMP
1440JUMPDEST
1441SWAP1
1442PUSH10x40
1444DUP4
1445ADD
1446MSTORE
1447SUB
1448SWAP1
1449RETURN
144aJUMPDEST
144bCALLVALUE
144cPUSH20x02ec
144fJUMPI
1450PUSH0
1451CALLDATASIZE
1452PUSH10x03
1454NOT
1455ADD
1456SLT
1457PUSH20x02ec
145aJUMPI
145bPUSH10x20
145dPUSH10x01
145fSLOAD
1460PUSH10x40
1462MLOAD
1463SWAP1
1464DUP2
1465MSTORE
1466RETURN
1467JUMPDEST
1468CALLVALUE
1469PUSH20x02ec
146cJUMPI
146dPUSH10x80
146fCALLDATASIZE
1470PUSH10x03
1472NOT
1473ADD
1474SLT
1475PUSH20x02ec
1478JUMPI
1479PUSH10x04
147bCALLDATALOAD
147cPUSH10x01
147ePUSH10x01
1480PUSH10x40
1482SHL
1483SUB
1484DUP2
1485GT
1486PUSH20x02ec
1489JUMPI
148aPUSH20x1497
148dSWAP1
148eCALLDATASIZE
148fSWAP1
1490PUSH10x04
1492ADD
1493PUSH20x1b01
1496JUMP
1497JUMPDEST
1498SWAP1
1499PUSH10x24
149bCALLDATALOAD
149cPUSH10x01
149ePUSH10x01
14a0PUSH10x40
14a2SHL
14a3SUB
14a4DUP2
14a5GT
14a6PUSH20x02ec
14a9JUMPI
14aaPUSH20x14b7
14adSWAP1
14aeCALLDATASIZE
14afSWAP1
14b0PUSH10x04
14b2ADD
14b3PUSH20x1b01
14b6JUMP
14b7JUMPDEST
14b8PUSH20x14c2
14bbSWAP4
14bcSWAP2
14bdSWAP4
14bePUSH20x1b31
14c1JUMP
14c2JUMPDEST
14c3SWAP4
14c4PUSH10x64
14c6CALLDATALOAD
14c7PUSH10x01
14c9PUSH10x01
14cbPUSH10x40
14cdSHL
14ceSUB
14cfDUP2
14d0GT
14d1PUSH20x02ec
14d4JUMPI
14d5PUSH20x14e2
14d8SWAP1
14d9CALLDATASIZE
14daSWAP1
14dbPUSH10x04
14ddADD
14dePUSH20x1b01
14e1JUMP
14e2JUMPDEST
14e3SWAP6
14e4DUP4
14e5ISZERO
14e6PUSH20x02dd
14e9JUMPI
14eaDUP4
14ebDUP6
14ecSUB
14edPUSH20x196e
14f0JUMPI
14f1PUSH10x01
14f3SLOAD
14f4DUP1
14f5ISZERO
14f6PUSH20x02ce
14f9JUMPI
14faPUSH10x02
14fcSWAP8
14fdSWAP6
14feSWAP8
14ffSWAP7
1500SWAP4
1501SWAP7
1502SLOAD
1503SWAP3
1504PUSH10x06
1506SLOAD
1507SWAP7
1508PUSH10x40
150aMLOAD
150bPUSH10x01
150dPUSH10x01
150fPUSH10x40
1511SHL
1512SUB
1513DUP7
1514AND
1515PUSH10x20
1517DUP3
1518ADD
1519MSTORE
151aDUP9
151bPUSH10x40
151dDUP3
151eADD
151fMSTORE
1520PUSH10x80
1522PUSH10x60
1524DUP3
1525ADD
1526MSTORE
1527PUSH20x1534
152aPUSH10xa0
152cDUP3
152dADD
152eDUP13
152fDUP10
1530PUSH20x1e73
1533JUMP
1534JUMPDEST
1535PUSH10x1f
1537NOT
1538DUP3
1539DUP3
153aSUB
153bADD
153cPUSH10x80
153eDUP4
153fADD
1540MSTORE
1541DUP9
1542DUP2
1543MSTORE
1544PUSH10x20
1546DUP2
1547ADD
1548SWAP1
1549PUSH10x20
154bDUP11
154cPUSH10x05
154eSHL
154fDUP3
1550ADD
1551ADD
1552SWAP2
1553DUP13
1554SWAP2
1555PUSH0
1556JUMPDEST
1557DUP13
1558DUP2
1559LT
155aPUSH20x1902
155dJUMPI
155ePOP
155fPOP
1560POP
1561POP
1562SWAP1
1563PUSH20x157c
1566DUP2
1567PUSH20x1603
156aSWAP8
156bSWAP7
156cSWAP6
156dSWAP5
156eSWAP4
156fSUB
1570PUSH10x1f
1572NOT
1573DUP2
1574ADD
1575DUP4
1576MSTORE
1577DUP3
1578PUSH20x1be1
157bJUMP
157cJUMPDEST
157dPUSH10x20
157fDUP2
1580MLOAD
1581SWAP2
1582ADD
1583KECCAK256
1584PUSH10x40
1586MLOAD
1587PUSH10x20
1589DUP2
158aADD
158bSWAP2
158cPUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
15adDUP4
15aeMSTORE
15afCHAINID
15b0PUSH10x40
15b2DUP4
15b3ADD
15b4MSTORE
15b5ADDRESS
15b6PUSH10x60
15b8DUP4
15b9ADD
15baMSTORE
15bbPUSH320x2e1c2ff2f9bb13fd926fe3e8b209f98e6c873bb259534a2148ca355409247cba
15dcPUSH10x80
15deDUP4
15dfADD
15e0MSTORE
15e1PUSH10x01
15e3PUSH10x01
15e5PUSH10x40
15e7SHL
15e8SUB
15e9DUP8
15eaAND
15ebPUSH10xa0
15edDUP4
15eeADD
15efMSTORE
15f0PUSH10xc0
15f2DUP3
15f3ADD
15f4MSTORE
15f5PUSH10xc0
15f7DUP2
15f8MSTORE
15f9PUSH20x025a
15fcPUSH10xe0
15feDUP3
15ffPUSH20x1be1
1602JUMP
1603JUMPDEST
1604POP
1605PUSH10x01
1607PUSH10x01
1609PUSH10x40
160bSHL
160cSUB
160dPUSH20x1617
1610DUP2
1611DUP4
1612AND
1613PUSH20x1e97
1616JUMP
1617JUMPDEST
1618PUSH80xffffffffffffffff
1621NOT
1622SWAP1
1623SWAP3
1624AND
1625SWAP2
1626AND
1627OR
1628PUSH10x02
162aSSTORE
162bPUSH0
162cSWAP5
162dPUSH320x000000000000000000000000c0876d136341091581a489ce7f746692dddf498f
164ePUSH10x01
1650PUSH10x01
1652PUSH10xa0
1654SHL
1655SUB
1656AND
1657JUMPDEST
1658DUP4
1659DUP8
165aLT
165bISZERO
165cPUSH20x18f7
165fJUMPI
1660DUP7
1661PUSH10x05
1663SHL
1664DUP7
1665ADD
1666CALLDATALOAD
1667PUSH10x1e
1669NOT
166aDUP8
166bCALLDATASIZE
166cSUB
166dADD
166eDUP2
166fSLT
1670ISZERO
1671PUSH20x02ec
1674JUMPI
1675DUP7
1676ADD
1677DUP1
1678CALLDATALOAD
1679SWAP1
167aPUSH10x01
167cPUSH10x01
167ePUSH10x40
1680SHL
1681SUB
1682DUP3
1683GT
1684PUSH20x02ec
1687JUMPI
1688PUSH10x20
168aADD
168bSWAP1
168cDUP1
168dPUSH10x05
168fSHL
1690CALLDATASIZE
1691SUB
1692DUP3
1693SGT
1694PUSH20x02ec
1697JUMPI
1698DUP1
1699ISZERO
169aPUSH20x18e4
169dJUMPI
169ePUSH20x16a8
16a1DUP10
16a2DUP6
16a3DUP8
16a4PUSH20x1eb5
16a7JUMP
16a8JUMPDEST
16a9CALLDATALOAD
16aaISZERO
16abPUSH20x18d1
16aeJUMPI
16afPUSH10x01
16b1DUP2
16b2ADD
16b3DUP1
16b4DUP3
16b5GT
16b6PUSH20x0509
16b9JUMPI
16baPUSH20x16c2
16bdSWAP1
16bePUSH20x1c2d
16c1JUMP
16c2JUMPDEST
16c3SWAP2
16c4PUSH20x16ce
16c7DUP11
16c8DUP7
16c9DUP9
16caPUSH20x1eb5
16cdJUMP
16ceJUMPDEST
16cfCALLDATALOAD
16d0PUSH20x16d8
16d3DUP5
16d4PUSH20x1c5f
16d7JUMP
16d8JUMPDEST
16d9MSTORE
16daPUSH0
16dbJUMPDEST
16dcDUP3
16ddDUP2
16deLT
16dfPUSH20x185a
16e2JUMPI
16e3POP
16e4POP
16e5POP
16e6DUP1
16e7MLOAD
16e8ISZERO
16e9PUSH20x184b
16ecJUMPI
16edJUMPDEST
16eeDUP1
16efMLOAD
16f0PUSH10x01
16f2DUP2
16f3GT
16f4ISZERO
16f5PUSH20x181f
16f8JUMPI
16f9DUP1
16faPUSH10x01
16fcSHR
16fdSWAP1
16fePUSH10x01
1700DUP2
1701AND
1702SWAP3
1703PUSH20x170f
1706PUSH20x0390
1709DUP6
170aDUP6
170bPUSH20x1bd4
170eJUMP
170fJUMPDEST
1710SWAP4
1711PUSH0
1712JUMPDEST
1713DUP5
1714DUP2
1715LT
1716PUSH20x179f
1719JUMPI
171aPOP
171bPUSH10x01
171dEQ
171ePUSH20x172a
1721JUMPI
1722JUMPDEST
1723POP
1724POP
1725POP
1726PUSH20x16ed
1729JUMP
172aJUMPDEST
172bPUSH0
172cNOT
172dDUP3
172eADD
172fSWAP2
1730DUP3
1731GT
1732PUSH20x0509
1735JUMPI
1736PUSH20x1796
1739SWAP2
173aPUSH20x1742
173dSWAP2
173ePUSH20x1c80
1741JUMP
1742JUMPDEST
1743MLOAD
1744PUSH10x40
1746MLOAD
1747PUSH10x20
1749DUP2
174aADD
174bSWAP2
174cPUSH10x01
174ePUSH10xf9
1750SHL
1751DUP4
1752MSTORE
1753PUSH320xc976f483968b324bd57de8efa226478a3634db61776dacd4da866f8fa37c0fd5
1774PUSH10x21
1776DUP4
1777ADD
1778MSTORE
1779PUSH10x41
177bDUP3
177cADD
177dMSTORE
177ePUSH10x41
1780DUP2
1781MSTORE
1782PUSH20x178c
1785PUSH10x61
1787DUP3
1788PUSH20x1be1
178bJUMP
178cJUMPDEST
178dMLOAD
178eSWAP1
178fKECCAK256
1790SWAP2
1791DUP4
1792PUSH20x1c80
1795JUMP
1796JUMPDEST
1797MSTORE
1798DUP9
1799DUP1
179aDUP1
179bPUSH20x1722
179eJUMP
179fJUMPDEST
17a0DUP1
17a1PUSH10x01
17a3SWAP2
17a4DUP3
17a5SHL
17a6PUSH20x17bc
17a9DUP4
17aaPUSH20x17b3
17adDUP4
17aeDUP9
17afPUSH20x1c80
17b2JUMP
17b3JUMPDEST
17b4MLOAD
17b5SWAP3
17b6OR
17b7DUP7
17b8PUSH20x1c80
17bbJUMP
17bcJUMPDEST
17bdMLOAD
17bePUSH10x40
17c0MLOAD
17c1SWAP1
17c2PUSH10x20
17c4DUP3
17c5ADD
17c6SWAP3
17c7DUP6
17c8PUSH10xf8
17caSHL
17cbDUP5
17ccMSTORE
17cdPUSH320xc976f483968b324bd57de8efa226478a3634db61776dacd4da866f8fa37c0fd5
17eePUSH10x21
17f0DUP5
17f1ADD
17f2MSTORE
17f3PUSH10x41
17f5DUP4
17f6ADD
17f7MSTORE
17f8PUSH10x61
17faDUP3
17fbADD
17fcMSTORE
17fdPUSH10x61
17ffDUP2
1800MSTORE
1801PUSH20x180b
1804PUSH10x81
1806DUP3
1807PUSH20x1be1
180aJUMP
180bJUMPDEST
180cMLOAD
180dSWAP1
180eKECCAK256
180fPUSH20x1818
1812DUP3
1813DUP10
1814PUSH20x1c80
1817JUMP
1818JUMPDEST
1819MSTORE
181aADD
181bPUSH20x1712
181eJUMP
181fJUMPDEST
1820POP
1821SWAP7
1822PUSH20x183d
1825PUSH20x1837
1828PUSH10x01
182aSWAP4
182bSWAP7
182cSWAP10
182dSWAP9
182eSWAP6
182fSWAP9
1830SWAP8
1831SWAP5
1832SWAP8
1833PUSH20x1c5f
1836JUMP
1837JUMPDEST
1838MLOAD
1839PUSH20x2436
183cJUMP
183dJUMPDEST
183eADD
183fSWAP6
1840SWAP3
1841SWAP5
1842SWAP2
1843SWAP5
1844SWAP4
1845SWAP1
1846SWAP4
1847PUSH20x1657
184aJUMP
184bJUMPDEST
184cPUSH40x4f297b61
1851PUSH10xe1
1853SHL
1854PUSH0
1855MSTORE
1856PUSH10x04
1858PUSH0
1859REVERT
185aJUMPDEST
185bPUSH20x1865
185eDUP2
185fDUP5
1860DUP5
1861PUSH20x1eb5
1864JUMP
1865JUMPDEST
1866CALLDATALOAD
1867DUP6
1868EXTCODESIZE
1869ISZERO
186aPUSH20x02ec
186dJUMPI
186ePUSH10x40
1870MLOAD
1871SWAP1
1872PUSH40xaf6f8c1b
1877PUSH10xe0
1879SHL
187aDUP3
187bMSTORE
187cPUSH10x04
187eDUP3
187fADD
1880MSTORE
1881PUSH0
1882DUP2
1883PUSH10x24
1885DUP2
1886DUP4
1887DUP11
1888GAS
1889CALL
188aDUP1
188bISZERO
188cPUSH20x0708
188fJUMPI
1890PUSH20x18c1
1893JUMPI
1894JUMPDEST
1895POP
1896PUSH20x18a0
1899DUP2
189aDUP5
189bDUP5
189cPUSH20x1eb5
189fJUMP
18a0JUMPDEST
18a1CALLDATALOAD
18a2SWAP1
18a3PUSH10x01
18a5DUP2
18a6ADD
18a7SWAP2
18a8DUP3
18a9DUP3
18aaGT
18abPUSH20x0509
18aeJUMPI
18afPUSH20x18ba
18b2PUSH10x01
18b4SWAP4
18b5DUP8
18b6PUSH20x1c80
18b9JUMP
18baJUMPDEST
18bbMSTORE
18bcADD
18bdPUSH20x16db
18c0JUMP
18c1JUMPDEST
18c2PUSH0
18c3PUSH20x18cb
18c6SWAP2
18c7PUSH20x1be1
18caJUMP
18cbJUMPDEST
18ccDUP12
18cdPUSH20x1894
18d0JUMP
18d1JUMPDEST
18d2DUP9
18d3PUSH40x22566cfd
18d8PUSH10xe0
18daSHL
18dbPUSH0
18dcMSTORE
18ddPUSH10x04
18dfMSTORE
18e0PUSH10x24
18e2PUSH0
18e3REVERT
18e4JUMPDEST
18e5DUP9
18e6PUSH40xc9cdeff5
18ebPUSH10xe0
18edSHL
18eePUSH0
18efMSTORE
18f0PUSH10x04
18f2MSTORE
18f3PUSH10x24
18f5PUSH0
18f6REVERT
18f7JUMPDEST
18f8PUSH10x20
18faDUP6
18fbPUSH10x40
18fdMLOAD
18feSWAP1
18ffDUP2
1900MSTORE
1901RETURN
1902JUMPDEST
1903SWAP1
1904SWAP2
1905SWAP3
1906SWAP4
1907SWAP13
1908SWAP15
1909SWAP13
190aPUSH10x1f
190cSWAP15
190dSWAP12
190eSWAP15
190fNOT
1910DUP4
1911DUP3
1912SUB
1913ADD
1914DUP5
1915MSTORE
1916PUSH10x1e
1918NOT
1919DUP13
191aCALLDATASIZE
191bSUB
191cADD
191dDUP6
191eCALLDATALOAD
191fSLT
1920ISZERO
1921PUSH20x02ec
1924JUMPI
1925DUP12
1926DUP6
1927CALLDATALOAD
1928ADD
1929SWAP1
192aPUSH10x20
192cDUP3
192dCALLDATALOAD
192eSWAP3
192fADD
1930SWAP2
1931PUSH10x01
1933PUSH10x01
1935PUSH10x40
1937SHL
1938SUB
1939DUP2
193aGT
193bPUSH20x02ec
193eJUMPI
193fDUP1
1940PUSH10x05
1942SHL
1943CALLDATASIZE
1944SUB
1945DUP4
1946SGT
1947PUSH20x02ec
194aJUMPI
194bPUSH20x195a
194ePUSH10x20
1950SWAP3
1951DUP4
1952SWAP3
1953PUSH10x01
1955SWAP6
1956PUSH20x1e73
1959JUMP
195aJUMPDEST
195bSWAP7
195cADD
195dSWAP5
195eADD
195fSWAP2
1960ADD
1961SWAP15
1962SWAP13
1963SWAP15
1964SWAP14
1965SWAP11
1966SWAP14
1967SWAP2
1968SWAP1
1969SWAP2
196aPUSH20x1556
196dJUMP
196eJUMPDEST
196fDUP4
1970DUP6
1971PUSH40x5b2d6423
1976PUSH10xe1
1978SHL
1979PUSH0
197aMSTORE
197bPUSH10x04
197dMSTORE
197ePUSH10x24
1980MSTORE
1981PUSH10x44
1983PUSH0
1984REVERT
1985JUMPDEST
1986CALLVALUE
1987PUSH20x02ec
198aJUMPI
198bPUSH20x03b5
198ePUSH20x10c6
1991PUSH20x1999
1994CALLDATASIZE
1995PUSH20x1ab8
1998JUMP
1999JUMPDEST
199aSWAP1
199bPUSH20x1c94
199eJUMP
199fJUMPDEST
19a0CALLVALUE
19a1PUSH20x02ec