Final Testnetexplorer K_J · Final Testnet · 48359
en

Contract

0xd9c87db0b8c9fbccd06315c3f374c10c430d0a72

Address
0xd9c87db0b8c9fbccd06315c3f374c10c430d0a72
Kind
verified contract FinalAssetRegistry
Balance
0 vETH
Nonce
1
Code
31,138 bytes codehash 0x883b87d3a8705947a38ddc0f46179317efc4da52f4cf87bd6a3d579a257f6878

account tree

Tree
1 · accounts
Present
no leaf
Key
0x35452076db95c72fedf6b4b3729398c571efdd91b8ad375aea8f1d29785629b8
Live root
0x18f30182962d8af79e7ab628ce200be69d28f54119890d737c7e736de62e703c
This address holds no leaf in the account tree. Every Final Wallet — service identities included — has one, so an absent leaf means an ordinary account rather than a wallet.
transactionseventstoken transferscontract

source verified

Contract
FinalAssetRegistry exact match · immutables masked
Compiler
v0.8.33+commit.64118f21
Optimizer
enabled · 200 runs
EVM version
prague
Verified
2026-09-06T09:21:18.248Z
Provenance
preverify-final-chain (forge artifact, bytecode compared against live code)

contracts/finalchain/FinalAssetRegistry.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 asset registry as part
//    of a Final DeFi Protocol chain, and may publish entries to it under the
//    quorum the chain recognises.
// 2. Integrators, indexers, and node operators may read the asset set, its
//    per-chain parameters, and the roots it publishes, 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 asset registry or a competing asset or
//    risk-parameter 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";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalStateTrees, IChainSource} from "./FinalStateTrees.sol";
import {FinalPlaneSweep} from "./FinalPlaneSweep.sol";

/**
 * @title FinalAssetRegistry
 * @notice Which networks we settle on, and which assets exist on them. On
 * Final Chain, as state — not as a list in a process.
 *
 * ## What this replaces
 *
 * Three hardcoded rosters, each authoritative for something and none of them a
 * record anyone could prove:
 *
 * | was | where | how it failed |
 * |---|---|---|
 * | the supported chain set | `chainRegistry.js` + a JS Merkle fold | the root a chain verified against was folded in `log2(n)`; the chain's own verifier is `verifyTaggedSortedProof`. Two implementations of one construction. |
 * | the vAsset roster | `buildAssetRegistry(records)` over a caller-supplied array | whoever called it decided the set. "The publisher should pass the right list" is not a control. |
 * | the PHI morphable roster | `PHI_MORPH_ASSETS`, an env var | unset on every environment, so five publishers idled; set wrong, and they would have priced against pools nobody checked. |
 * | the monitored fee assets | `MONITORED_ASSETS`, a literal with MAINNET defaults | applied silently on Sepolia, where those pool addresses have no code, so the test fleet sampled reserves that could never exist. |
 *
 * Each was a different shape of the same mistake: a set that decides what the
 * protocol will accept, held somewhere that cannot be proven, versioned or
 * audited, and readable only by the process that happened to hold it.
 *
 * ## The tree IS the roster
 *
 * An entry here is state. It is seeded once, mutated by `add` and `remove`
 * under a quorum, and every mutation writes tree 6 in the same transaction —
 * so there is no window in which the record and the root disagree, and no
 * second copy for them to disagree with.
 *
 * `remove` is a tombstone, not a deletion, and that is deliberate twice over.
 * `FinalStateTrees` gives every key a PERMANENT slot on first write, so a
 * deletion is not available to implement. It is also the wrong thing to want:
 * a consumer asking "is this asset supported?" needs a provable NO, and an
 * absent leaf proves nothing — it is indistinguishable from a leaf that was
 * never published, from a chain that is behind, and from a proof built against
 * the wrong epoch. `enabled=false` is a leaf, and a leaf can be proven.
 *
 * ## Two trees, and they are not redundant
 *
 * **Tree 6 holds the rows.** One leaf per chain and per asset, carrying every
 * attribute a consumer needs. That is what makes membership provable on a chain
 * that cannot read this one.
 *
 * **Tree 5 holds the two registry roots** that `FinalSettlement.syncChain` and
 * `registerAsset` verify against. Those are not tree 6's root: they are roots
 * over the chain leaves alone and the asset leaves alone, in the sorted-pair
 * tagged shape `FinalMerkle.verifyTaggedSortedProof` runs — a different
 * construction from tree 6's fixed depth-20 slotted tree, over a different leaf
 * set. Publishing tree 6's root as a registry root would produce a value that
 * verifies nothing, with both sides internally consistent.
 *
 * They are computed HERE, on chain, for the reason every other root on this
 * chain is: the alternative is a second implementation in JavaScript, and a
 * divergence between two folders of one construction presents as a proof that
 * verifies nowhere with nothing pointing at the cause.
 *
 * ## Tree 6 is ALL of the protocol's configuration
 *
 * Six key kinds. Kinds 0–2 are the rosters (chains, assets, assets on chains),
 * kind 3 is every numeric policy, and kinds 4 and 5 are the price plane's
 * wiring: one row per price VENUE with that venue's own ticker and, for a DEX
 * pool, every TWAP term of that pool; and one row per DEX protocol deployment
 * per chain, so a pool is verified against its factory by a chain read and the
 * treasury's router is a reference rather than an address of its own. A chain
 * row is the complete protocol description of a chain — base asset, finality
 * rule, block timer, multicall, gas model. With this, no service reads an
 * environment variable for anything but its own RPC and keys; the RPC stays
 * off the tree by rule, because where one operator's fleet reads a chain is
 * not a protocol fact and tree 6 is public state.
 *
 * ## Gas
 *
 * Re-folding both registries on every mutation is O(n log n) hashes. That is
 * affordable because this is our own chain, the cadence is "rarely", and the
 * sets are tens of entries. Do not carry the pattern to a chain where a fold is
 * paid by a user.
 */
contract FinalAssetRegistry is IChainSource, FinalPlaneSweep {
    // ------------------------------------------------------------- constants

    /// @dev Chain-registry leaf space. Byte-for-byte what `FinalSettlement`
    /// hashes, because a root computed under a different domain verifies
    /// nowhere and the mismatch is invisible until a proof is spent.
    bytes32 internal constant DOMAIN_CHAIN_LEAF = keccak256("FINAL_CHAIN_REGISTRY_LEAF_v01");
    /// @dev Asset-registry leaf space. Disjoint from the chain space so a chain
    /// record can never be replayed as an asset record.
    bytes32 internal constant DOMAIN_ASSET_LEAF = keccak256("FINAL_ASSET_REGISTRY_LEAF_v01");

    /// @dev Tree 6 key and leaf spaces. Separate from the registry spaces
    /// above: tree 6 answers "is this supported", the registries answer "what
    /// are its attributes", and one leaf must not satisfy the other's proof.
    bytes32 internal constant DOMAIN_ALLOWLIST_KEY = keccak256("FINAL_ALLOWLIST_KEY_v01");
    /// @dev Domain tag for an allowlist leaf. Any change here invalidates every proof already published against
    ///       tree 6, so it is versioned rather than edited.
    bytes32 internal constant DOMAIN_ALLOWLIST_LEAF = keccak256("FINAL_ALLOWLIST_LEAF_v01");
    /// @dev Tree 5 key and leaf spaces.
    bytes32 internal constant DOMAIN_REGISTRY_ROOT_KEY = keccak256("FINAL_REGISTRY_ROOT_KEY_v01");
    /// @dev Domain tag for a registry-root leaf, versioned on the same rule as the allowlist tag.
    bytes32 internal constant DOMAIN_REGISTRY_ROOT_LEAF = keccak256("FINAL_REGISTRY_ROOT_LEAF_v01");

    /// @dev Action tag for the one-shot seeding call, kept distinct from the mutation tag so a seeding approval
    ///       can never be replayed as an ordinary mutation.
    bytes32 internal constant ACTION_SEED = keccak256("FinalAssetRegistry.seed.v01");
    /// @dev Action tag for an ordinary mutation.
    bytes32 internal constant ACTION_MUTATE = keccak256("FinalAssetRegistry.mutate.v01");

    /// @dev A DISTINCT action, sharing `mutate`'s nonce. Distinct so a batch
    ///      approved for one door cannot be replayed through the other; shared
    ///      nonce so the two write paths are totally ordered against each other
    ///      rather than each advancing a counter the other cannot see.
    bytes32 internal constant ACTION_SET_POLICY = keccak256("FinalAssetRegistry.setPolicy.v01");
    /// @dev Action tag for a per-chain execution-terms write.
    bytes32 internal constant ACTION_SET_CHAIN_TERMS = keccak256("FinalAssetRegistry.setChainTerms.v01");
    /// @dev Kinds 4 and 5, same arrangement: their own door, the shared nonce.
    bytes32 internal constant ACTION_SET_SOURCES = keccak256("FinalAssetRegistry.setSources.v01");

    /// @dev Tree index carrying the settlement set.
    uint8 internal constant TREE_SETTLEMENT = 5;
    /// @dev Tree index carrying the allowlist.
    uint8 internal constant TREE_ALLOWLIST = 6;
    /// @dev The branch every registry row lives in — `FinalStateTrees.BRANCH_MAIN`,
    ///      pinned by test. Branch 0 of both trees is the configuration branch.
    uint8 internal constant BRANCH_MAIN_ID = 1;

    /// @dev Which registry a tree-5 leaf is the root of.
    uint8 internal constant REGISTRY_CHAIN = 0;
    /// @dev Registry selector for the ASSET root, the counterpart of `REGISTRY_CHAIN`.
    uint8 internal constant REGISTRY_ASSET = 1;

    /// @dev Byte-for-byte `FinalSettlement`'s. A chain reference derived under
    ///      a different domain here would name a chain nothing else can find.
    bytes32 internal constant DOMAIN_CHAIN_REF = keccak256("FINAL_CHAIN_REF_v01");
    /// @notice CAIP namespace hash for EVM chains. A chain in this namespace must declare `VM_EVM`.
    bytes32 public constant CAIP_NAMESPACE_EIP155 = keccak256("eip155");

    // ----------------------------------------------------------------- types

    /// @notice What an asset is FOR. An asset can be more than one.
    ///
    /// @dev A bitfield rather than an enum, because the three uses are
    /// independent and an asset commonly has two of them. Modelling it as an
    /// enum forced a "BOTH" member the day the second combination appeared,
    /// and a third use makes that combinatorial.
    ///
    /// - `USE_SETTLE` — bridgeable; may be issued as a vAsset.
    /// - `USE_MORPH`  — may be morphed by the PHI ledger.
    /// - `USE_FEE`    — accepted as a gateway fee token and priced by the
    ///                  oracle. This is the roster `MONITORED_ASSETS` was.
    /// - `USE_REFILL` — autosold to base crypto.
    /// - `USE_SETTLE_DENIED` — excluded from bridging.
    ///
    /// Scope is not uniform. `USE_MORPH` and `USE_SETTLE_DENIED` are properties
    /// of the ASSET and live on the global row; `USE_FEE` and `USE_REFILL`
    /// differ by network and live on the per-chain row. `GLOBAL_USE_MASK` and
    /// `CHAIN_USE_MASK` are enforced, so a bit cannot be set where nothing would
    /// read it.
    uint8 internal constant USE_SETTLE = 1;
    /// @notice `uses` bit: the asset may back a morph.
    uint8 internal constant USE_MORPH = 2;
    /// @notice `uses` bit: the asset may pay fees.
    uint8 internal constant USE_FEE = 4;
    /// @dev Autosold to base crypto on this chain. **Separate from `USE_FEE` on
    ///      purpose**: accepting an asset as payment and dumping it for base
    ///      crypto are different decisions, and they stop matching the first
    ///      time we accept a fee token we do not want to sell.
    uint8 internal constant USE_REFILL = 8;
    /// @dev Excluded from bridging. A DENY bit, not an allow bit — the asset
    ///      registry is default-allow minus exclusions, so absence means
    ///      bridgeable and only an explicit leaf can say otherwise. An absent
    ///      leaf proves nothing, which is why exclusion has to be written down
    ///      rather than inferred from a missing row.
    uint8 internal constant USE_SETTLE_DENIED = 16;

    /// @dev Bits meaningful on the GLOBAL asset row (kind 1). Properties of the
    ///      asset itself, true wherever it is.
    uint8 internal constant GLOBAL_USE_MASK = USE_SETTLE | USE_MORPH | USE_SETTLE_DENIED;
    /// @dev Bits meaningful on the PER-CHAIN row (kind 2). Properties of the
    ///      asset ON a network.
    uint8 internal constant CHAIN_USE_MASK = USE_FEE | USE_REFILL;

    /// @notice `ChainEntry.role` values.
    uint8 public constant CHAIN_ROLE_FULL = 0;
    /// @notice `ChainEntry.role`: observed only — read and priced, never settled on.
    uint8 public constant CHAIN_ROLE_OBSERVED = 1;
    /// @notice Bounds of `AssetChainEntry.maxLeveragePct` when set — the same
    ///         numbers as `FinalStateRecords.MIN_LEVERAGE_PCT` / `MAX_LEVERAGE_PCT`
    ///         (a cap outside the records' own bound would never bind).
    uint16 public constant LEVERAGE_CAP_MIN_PCT = 100;
    /// @notice Upper bound of `AssetChainEntry.maxLeveragePct` when set.
    uint16 public constant LEVERAGE_CAP_MAX_PCT = 500;

    /// @notice How a chain's finality is decided. Zero is unset.
    uint8 public constant FINALITY_TAG_FINALIZED = 1;
    /// @notice Finality by confirmation depth.
    uint8 public constant FINALITY_CONFIRMATIONS = 2;
    /// @notice Finality by settlement on the chain's parent.
    uint8 public constant FINALITY_L2_SETTLED = 3;

    /// @notice How a chain prices gas. Zero is unset.
    uint8 public constant GAS_MODEL_EIP1559 = 1;
    /// @notice Legacy single gas price.
    uint8 public constant GAS_MODEL_LEGACY = 2;
    /// @notice Execution gas plus a separate parent-chain data fee.
    uint8 public constant GAS_MODEL_L2_WITH_L1_FEE = 3;

    /// @dev `vmKind` values. Zero is refused on an enabled row: a chain whose
    ///      execution model nobody named is one every consumer would guess at,
    ///      and the guesses would all be "EVM" right up until the first chain
    ///      that is not. Extend by number; never renumber.
    uint8 public constant VM_EVM = 1;
    /// @notice Execution model: SVM.
    uint8 public constant VM_SVM = 2;
    /// @notice Execution model: Move.
    uint8 public constant VM_MOVE = 3;

    /// @notice DEX protocol families a kind-5 row may describe. Zero is unset.
    /// @dev The READ SHAPE of a pool — v2 reserves, v3 `slot0` + `observe`, v4
    ///      StateView — comes from this, never from the pool row.
    uint8 public constant PROTOCOL_UNISWAP_V2 = 1;
    /// @notice DEX family: Uniswap v3 — concentrated liquidity, priced from `slot0` and `observe`.
    uint8 public constant PROTOCOL_UNISWAP_V3 = 2;
    /// @notice DEX family: Uniswap v4 — priced through the state view.
    uint8 public constant PROTOCOL_UNISWAP_V4 = 3;
    /// @notice DEX family: Velodrome.
    uint8 public constant PROTOCOL_VELODROME = 4;
    /// @notice DEX family: Curve.
    uint8 public constant PROTOCOL_CURVE = 5;

    /// @notice The tree-4 refresh a roster row gets by default, and the one a
    ///         morph or fee asset gets. Milliseconds.
    /// @dev A stale price on a morphable or fee asset is a free option against
    ///      the collateral or the float, so those refresh every second and are
    ///      refused after three of their own ticks. Everything else keeps the
    ///      round.
    uint32 public constant PRICE_CADENCE_DEFAULT_MS = 10_000;
    /// @notice Default staleness bound: a price older than this is refused.
    uint32 public constant PRICE_MAX_AGE_DEFAULT_MS = 120_000;
    /// @notice Fast cadence, for an asset that backs a morph or pays fees.
    uint32 public constant PRICE_CADENCE_FAST_MS = 1_000;
    /// @notice Fast staleness bound — three of its own ticks, so a stalled publisher is caught immediately.
    uint32 public constant PRICE_MAX_AGE_FAST_MS = 3_000;

    /// @notice One network we settle on — the complete protocol description of
    ///         a chain.
    ///
    /// @dev Everything a service needs to READ a chain correctly is here, so
    /// none of it is a per-process setting: base asset, finality rule, the
    /// block timer, multicall, the gas model. Only the RPC endpoint stays out,
    /// by rule — it is where one operator's fleet reads the chain, resolved by
    /// convention from `chainRef`, not a fact about the chain.
    struct ChainEntry {
        /// @dev CAIP-style reference, not an EIP-155 id — the registry spans
        /// non-EVM namespaces and an integer chain id cannot name those.
        bytes32 chainRef;
        /// @dev The two halves `chainRef` is the hash OF, carried so the entry
        ///      is self-describing.
        ///
        ///      A hash cannot be inverted, so a consumer holding only
        ///      `chainRef` cannot learn which chain it names — it can only
        ///      re-hash candidates from a list it already has, which is the
        ///      list this registry exists to replace. Carrying the preimage is
        ///      what makes "read the chain set from the tree" a complete answer
        ///      instead of a lookup that still needs the old array.
        ///
        ///      Checked on write: `chainRefFor(namespace, reference)` must
        ///      equal `chainRef`. An entry claiming a reference it does not
        ///      hash to would send every consumer to the wrong chain with a
        ///      correct-looking proof.
        bytes32 caipNamespace;
        /// @dev The reference half of the CAIP pair. Checked on write together with `caipNamespace`: the two must
        ///       hash to `chainRef`, or the entry would send every consumer to the wrong chain under a correct proof.
        bytes32 caipReference;
        /// @dev `FinalSettlement` on that chain, widened to 32 bytes.
        bytes32 settlement;
        /// @dev Matches `FinalSettlement.AccountSpace`.
        uint8 accountSpace;
        /// @dev Asset id of the gas / base asset — what fees are priced in.
        ///      Zero while the chain's assets are not yet seeded: the chain row
        ///      has to exist before an asset can name it as origin.
        bytes32 nativeAsset;
        /// @dev Asset id of its wrapped form — the pool leg.
        bytes32 wrappedNative;
        /// @dev `FINALITY_*`.
        uint8 finalityKind;
        /// @dev Confirmations for `FINALITY_CONFIRMATIONS`; zero for a tag rule.
        uint64 finalityParam;
        /// @dev The chain's block timer — the cadence of everything read per
        ///      block. Every DEX / TWAP source on the chain is read once per
        ///      block, the window → blocks conversion uses it, and the RPC
        ///      health probe paces on it.
        uint32 blockTimeMs;
        /// @dev Read the gas price every N blocks (1 = every block).
        uint16 gasReadBlocks;
        /// @dev `eth_feeHistory` window the gas quote is composed over.
        uint16 gasHistoryBlocks;
        /// @dev Multicall3 on that chain, widened.
        bytes32 multicall;
        /// @dev `GAS_MODEL_*`.
        uint8 gasModel;
        /// @dev For an L2: the chain whose L1 fee component applies, else zero.
        bytes32 l1ChainRef;
        /// @dev `FinalGateway` on that chain, widened to 32 bytes. On EVM
        ///      chains it is today the same CREATE2 address everywhere, which
        ///      is exactly the assumption a non-EVM chain breaks — so the
        ///      registry carries it per chain and a consumer reads it here
        ///      rather than deriving it. Zero while the chain's gateway is not
        ///      yet deployed, same rule as `settlement`.
        bytes32 gateway;
        /// @dev `VM_*` — the chain's execution model. What a consumer branches
        ///      on to pick an adapter; `caipNamespace` names the namespace and
        ///      this names the machine, and on eip155 they must agree (VM_EVM).
        uint8 vmKind;
        /// @dev False retires the chain. A retired row is kept rather than deleted, because an absent leaf proves
        ///       nothing and a consumer must be able to prove that a chain was withdrawn.
        bool enabled;
        /// @dev Monotonic per entry. What makes a stale proof refusable rather
        /// than merely old, and what a consumer's ring compares against.
        uint64 epoch;
        /// @dev The height the fleet's resyncs start from on this chain: nothing
        ///      of ours exists below it, so no window is scanned below it.
        ///      Zero means unknown, and the scan starts from the cursor.
        uint64 startHeight;
        /// @dev `CHAIN_ROLE_FULL` (0): settlement, accounts, fees — `settlement`
        ///      required. `CHAIN_ROLE_OBSERVED` (1): the fleet READS it — gas per
        ///      block time, marks — but settles nothing there and needs no
        ///      contracts of ours on it — a chain the oracles price without any
        ///      wallet deployment.
        uint8 role;
        /// @dev The address that pays for execution on this chain — the fee
        ///      lane — widened to 32 bytes so a non-EVM chain fits.
        ///
        ///      Zero means `gateway` IS the fee lane, which is the ordinary
        ///      case: on every chain we have deployed, `FinalGateway`'s
        ///      paymaster module holds the float and settles the fee. The word
        ///      is written only where the two differ, so a consumer resolves
        ///      the fee lane as `paymaster == 0 ? gateway : paymaster` and a
        ///      chain row that predates the field answers the same address it
        ///      always did rather than a zero a client would misread as "no
        ///      fee lane here".
        bytes32 paymaster;
    }

    /// @notice One asset, with every attribute a remote chain cannot read.
    ///
    /// @dev `decimals`, `name` and `symbol` are here for the reason
    /// `FinalSettlement.AssetLeaf` carries them: the token lives somewhere
    /// else, so a chain registering it cannot check them, and a
    /// caller-supplied `decimals` on a permissionless entrypoint is a
    /// mint-multiplier attack no on-chain check could catch.
    ///
    /// The venue an asset is priced against is NOT here any more: it is a
    /// kind-4 row per venue, because the median needs several and each has its
    /// own ticker and terms.
    struct AssetEntry {
        /// @dev The chain the asset is native to — where its real balance is custodied.
        bytes32 originChainRef;
        /// @dev The asset's identifier on its origin chain, widened to 32 bytes so a non-EVM token fits.
        bytes32 originToken;
        uint8 decimals;
        string name;
        string symbol;
        /// @dev `USE_*` bitfield.
        uint8 uses;
        /// @dev How often this asset's tree-4 row is republished, and how old a
        ///      price may be before the banded agreement refuses it. Properties
        ///      of the asset, inherited by its kind-4 venues, so no service
        ///      decides on its own how fresh a price is. Zero on write selects
        ///      the default for the asset's uses.
        uint32 priceCadenceMs;
        /// @dev How old this asset's price may be before the banded agreement refuses it. Zero on write selects the
        ///       default for the asset's uses.
        uint32 maxAgeMs;
        /// @dev False retires the asset, as a tombstone rather than a deletion.
        bool enabled;
        /// @dev Monotonic per entry, so a stale proof is refusable rather than merely old.
        uint64 epoch;
    }

    /// @notice How a price venue is reached.
    ///
    /// @dev An enum and not a bool, because "DEX or CEX" was the question until
    ///      the first aggregator, and a third answer should not need a second
    ///      field. `None` is the zero value so an unset venue is legible rather
    ///      than reading as a DEX at address zero.
    enum VenueKind {
        None,
        Dex,
        Cex,
        Aggregator
    }

    /// @notice One asset's policy on ONE chain.
    ///
    /// @dev `uses` carries only `CHAIN_USE_MASK` bits; the global row carries
    ///      the rest. The venue that used to sit here is a kind-4 row.
    struct AssetChainEntry {
        /// @dev The asset this row is about.
        bytes32 assetId;
        /// @dev The chain this row is about. One asset has one row per chain it is configured on.
        bytes32 chainRef;
        /// @dev `USE_FEE` and/or `USE_REFILL`. Other bits are refused.
        uint8 uses;
        /// @dev False retires this asset's configuration on this chain, leaving the global row untouched.
        bool enabled;
        /// @dev Monotonic per entry.
        uint64 epoch;
        /// @dev Leverage ceiling for morphs on THIS asset on THIS chain, in
        ///      percent (300 = 3×); 0 = no per-chain cap, the records' global
        ///      bound alone applies. Enforced by `FinalStateRecords.setPhiAccounts`
        ///      through `leverageCapPct`, so leverage is bounded per asset AND
        ///      per chain rather than once globally.
        uint16 maxLeveragePct;
        /// @dev The asset's own contract ON THIS CHAIN, widened to 32 bytes.
        ///
        ///      The global row carries `originToken`, which is the address on
        ///      the chain the asset is NATIVE to and is the wrong address
        ///      everywhere else. A client acting on the asset here needs the
        ///      one deployed here, and deriving it is not possible for a
        ///      bridged token whose address nobody controls. Zero means the
        ///      asset has no contract of its own on this chain — the native
        ///      gas asset, or an asset reached only as its vAsset.
        bytes32 token;
        /// @dev The morph contract for this asset on this chain, widened.
        ///
        ///      Zero means the asset is not morphable here, which is the state
        ///      of every asset whose global row lacks `USE_MORPH` and of a
        ///      morphable asset on a chain where the contract is not deployed
        ///      yet. A consumer must treat the two as one answer: no address,
        ///      no morph.
        bytes32 morph;
        /// @dev The `FinalVAsset` clone standing in for this asset on this
        ///      chain, widened. Zero on the asset's ORIGIN chain, where the
        ///      real token is custodied and no stand-in exists, and on any
        ///      chain the clone has not been issued on yet.
        bytes32 vAsset;
    }

    /// @notice One price source for one asset — kind 4.
    ///
    /// @dev One row per VENUE, because the median wants several and every DEX
    /// pool has its own right TWAP terms: a deep mainnet pool tolerates a long
    /// window, a thin L2 pool needs a shorter one and a liquidity floor. All
    /// of it was a roster file, a set of constants and fourteen environment
    /// variables; now it is a leaf, and the median reads every enabled row for
    /// the asset. `symbol` is the ticker THIS venue uses, so an alias
    /// (WETH-vs-ETH) is a field rather than a code path.
    struct PriceSourceEntry {
        /// @dev The asset this venue prices.
        bytes32 assetId;
        /// @dev Zero for a CEX row — a CEX is on no chain.
        bytes32 chainRef;
        /// @dev Distinguishes rows for one asset on one chain.
        bytes32 venueId;
        /// @dev `VenueKind`.
        uint8 venueKind;
        /// @dev The pool address, widened, or the CEX id.
        bytes32 venue;
        /// @dev The kind-5 deployment a DEX pool is verified against
        ///      (`factory.getPool(token0, token1, fee) == venue`). Zero on a CEX.
        bytes32 protocolId;
        /// @dev The ticker this venue quotes the asset under.
        bytes32 symbol;
        /// @dev What the venue quotes against — an asset id.
        bytes32 quoteAsset;
        /// @dev Weight in the composition, basis points of the total.
        uint16 weight;
        // DEX rows only — the TWAP terms of THIS pool. Zero on a CEX row.
        /// @dev The pool's token ordering. `slot0` always prices token0 in
        ///      token1, and reading it the wrong way round is a well-formed
        ///      wrong price at full weight.
        bytes32 baseToken;
        /// @dev The pool's other side. Together with `baseToken` it fixes which direction of the pair this row
        ///       quotes.
        bytes32 quoteToken;
        /// @dev Whether the base asset is the pool's token0. Reading the ordering the wrong way round produces a
        ///       well-formed but inverted price at full weight, which is why it is stored rather than inferred.
        bool baseIsToken0;
        /// @dev The window `observe` / the cumulative pair spans, and the
        ///      shortest window still accepted before the row reports
        ///      unavailable rather than a price.
        uint32 twapWindowSeconds;
        /// @dev The shortest window still accepted. Below it the row reports unavailable rather than a price, so a
        ///       freshly deployed pool contributes nothing instead of contributing a thin one.
        uint32 twapMinWindowSeconds;
        /// @dev Active liquidity below which the row is skipped — a v3 pool's
        ///      depth is its in-range L, not its balances.
        uint128 minLiquidity;
        /// @dev Spot-vs-TWAP spread above which the row is refused as manipulated.
        uint16 maxSpotDeviationBps;
        /// @dev False retires this venue from the composition.
        bool enabled;
        /// @dev Monotonic per entry.
        uint64 epoch;
    }

    /// @notice One DEX protocol's deployment on one chain — kind 5.
    ///
    /// @dev What reading or verifying a pool needs and a pool row cannot carry:
    /// the factory that proves a pool address is genuine, the quoter for a
    /// quote, the router the treasury swaps through, the position manager.
    /// Per protocol per chain, so the addresses hardcoded in the oracle and
    /// treasury workers go, and the treasury's swap target becomes a reference
    /// to `router` here — still gated on the gateway's own allowlist, because
    /// the tree names it and the contract gates it.
    struct DexProtocolEntry {
        /// @dev The chain this deployment is on.
        bytes32 chainRef;
        /// @dev Which protocol family this row describes, from the `PROTOCOL_*` set.
        bytes32 protocolId;
        /// @dev `PROTOCOL_*`.
        uint8 protocolKind;
        /// @dev The family's factory, widened. A DEX price row is verified against it, so a pool that the factory
        ///       does not vouch for cannot enter the composition.
        bytes32 factory;
        /// @dev The family's quoter, where it has one. Zero when the family does not.
        bytes32 quoter;
        bytes32 router;
        /// @dev The family's position manager, where it has one. Zero when the family does not.
        bytes32 positionManager;
        /// @dev False retires this deployment.
        bool enabled;
        /// @dev Monotonic per entry.
        uint64 epoch;
    }

    // ----------------------------------------------------------------- state

    /// @notice The identity registry this contract resolves quorum members through.
    /// @dev Immutable: the registry is what decides who may publish here, so a rotatable pointer would make
    ///       the quorum only as strong as whoever could re-point it.
    FinalIdentityRegistry public immutable registry;
    /// @notice The state trees this registry publishes its roots into.
    /// @dev Immutable for the same reason as `registry` — a re-pointable tree would let published state be
    ///       redirected to a tree nothing else reads.
    FinalStateTrees public immutable trees;

    /// @dev Registrar-quorum action, verified by the registry with this
    /// contract as the verifying contract.
    bytes32 public constant ACTION_CONFIGURE = keccak256("FINAL_ASSET_REGISTRY_CONFIGURE_v01");

    /// @dev Bootstrap admin, cleared by `seal`. Mirrors the registry's window.
    address public admin;

    /// @dev Which role may mutate, and how many approvals it takes.
    uint256 public publisherRole;
    /// @dev How many approvals from `publisherRole` a mutation takes.
    uint256 public threshold;

    /// @dev Replay domain for mutations. Bound into every digest.
    uint64 public nonce;

    /// @dev Insertion-ordered chain references, so the set can be enumerated and folded deterministically. The
    ///       fold order is part of the published root, so entries are appended and never reordered.
    bytes32[] internal _chainRefs;
    /**
     * @notice One global policy scalar — kind 3.
     *
     * @dev The fourth key kind, for the numbers that are POLICY and are not a
     * property of a chain or an asset: a treasury swap ceiling, a subsidy
     * budget. They lived in environment variables, which makes a limit
     * something an operator can change alone, silently, per revision — and a
     * limit nobody had to agree to is not a limit, it is a default.
     *
     * A scalar rather than a typed field per parameter, because the alternative
     * is a contract change for every new bound, and a contract change is the
     * one thing that must not be the price of tightening a limit. The units are
     * the caller's: `TREASURY_SWAP_MAX_WEI_PER_TICK` is wei, and the name says
     * so. A consumer that reads the wrong parameter reads a number of the wrong
     * magnitude, which is why the reader is one shared module and not a
     * `getUint` at each call site.
     *
     * `enabled` is a tombstone. A retired bound must prove it was retired: an
     * absent leaf proves nothing, and a consumer that treats absence as
     * "unlimited" is exactly the failure a policy row exists to prevent.
     */
    struct PolicyEntry {
        /// @dev Identifier of the bound this row carries.
        bytes32 paramId;
        /// @dev The bound's value, in whatever unit `paramId` names. The shared reader is what knows the unit.
        uint256 value;
        bool enabled;
        /// @dev Monotonic per entry.
        uint64 epoch;
    }

    /// @notice Per-chain EXECUTION TERMS — kind 7. What a leg on this chain
    ///         executes under and what its fee quote is bounded by. Operator-
    ///         tunable: a newer record (epoch) supersedes, never a deploy. The
    ///         default lane and every other lane available on a chain live in
    ///         the tree and change there, per chain, without a redeploy. A zero
    ///         numeric field means "the fee schedule's default".
    struct ChainTermsEntry {
        /// @dev The chain these execution terms apply to.
        bytes32 chainRef;
        /// @dev The lane a leg on this chain takes unless the intent names
        ///      another AVAILABLE one. Lane ids and their meaning are the
        ///      intent plane's own table; lane 0 is the relayer-staged
        ///      post-quantum envelope through the chain's gateway.
        uint8 defaultLane;
        /// @dev Bitmask of the lanes available on this chain (bit i = lane i);
        ///      must include `defaultLane`. Any bit is storable — a new lane is a
        ///      record, not a registry redeploy.
        uint16 lanes;
        /// @dev How long a fee quote for this chain stays valid, seconds.
        uint32 quoteWindowSeconds;
        /// @dev Per-chain float premium on top of cost, basis points.
        uint16 floatPremiumBps;
        /// @dev Admission floor for a leg on this chain, USD micros.
        uint64 admissionFloorUsdMicros;
        /// @dev Legs one intent may carry on this chain; 0 = no per-chain cap.
        uint16 maxLegsPerIntent;
        /// @dev False retires the terms. A retired bound must prove it was retired, so the row stays as a tombstone.
        bool enabled;
        /// @dev Monotonic per entry.
        uint64 epoch;
    }

    /// @dev Chain reference to its entry.
    mapping(bytes32 chainRef => ChainEntry) internal _chains;
    /// @dev Membership set for chains, so a zero-valued entry is distinguishable from an absent one.
    mapping(bytes32 chainRef => bool) internal _chainKnown;

    /// @dev Insertion-ordered asset ids, folded in this order into the published asset root.
    bytes32[] internal _assetIds;
    /// @dev Asset id to its entry.
    mapping(bytes32 assetId => AssetEntry) internal _assets;
    /// @dev Membership set for assets.
    mapping(bytes32 assetId => bool) internal _assetKnown;

    /// @dev Per-chain policy and venue wiring, `assetId -> chainRef -> entry`.
    ///      Sparse on purpose: an asset with no row on a chain is simply not a
    ///      fee or refill asset there, which is the common case.
    mapping(bytes32 assetId => mapping(bytes32 chainRef => AssetChainEntry)) internal _assetChains;

    /// @dev Kind 3 — a global policy scalar, keyed by a name.
    mapping(bytes32 paramId => PolicyEntry) internal _policy;
    /// @dev Membership set for policy bounds.
    mapping(bytes32 paramId => bool) internal _policyKnown;
    /// @dev Insertion-ordered policy ids.
    bytes32[] internal _policyIds;
    /// @dev Chain reference to its execution terms.
    mapping(bytes32 chainRef => ChainTermsEntry) internal _chainTerms;

    /// @dev Kind 4 — price sources, keyed by their tree-6 key so one mapping
    ///      serves rows on a chain and CEX rows alike; `_sourceKeys` is the
    ///      iteration order and `_sourceKeysFor[assetId]` the per-asset view.
    mapping(bytes32 key => PriceSourceEntry) internal _sources;
    /// @dev Membership set for price-source rows, keyed by the composite of asset, chain and venue.
    mapping(bytes32 key => bool) internal _sourceKnown;
    /// @dev Insertion-ordered price-source keys.
    bytes32[] internal _sourceKeys;
    /// @dev Per-asset list of its price-source keys, so a median can be taken without scanning every row.
    mapping(bytes32 assetId => bytes32[]) internal _sourceKeysFor;

    /// @dev Kind 5 — DEX protocol deployments, `chainRef -> protocolId -> entry`.
    mapping(bytes32 chainRef => mapping(bytes32 protocolId => DexProtocolEntry)) internal _protocols;
    /// @dev Per-chain list of the DEX deployments configured on it.
    mapping(bytes32 chainRef => bytes32[]) internal _protocolIdsOn;

    /// @notice The roots `FinalSettlement` consumes. Recomputed on every write.
    bytes32 public chainRegistryRoot;
    /// @notice Root of the published asset set, republished on every mutation.
    /// @dev Copied onto each execution chain, so two chains cannot disagree about which assets exist.
    bytes32 public assetRegistryRoot;
    /// @notice Bumped on every mutation; published beside each root so a
    /// consumer can tell which moment a proof was built against.
    uint64 public registryEpoch;

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

    /// @notice The publisher role and threshold were configured.
    /// @param role Role whose members may mutate this registry.
    /// @param threshold Approvals a mutation requires.
    event RegistryConfigured(uint256 role, uint256 threshold);
    /// @notice A chain entry was written or retired.
    /// @param chainRef The chain.
    /// @param enabled Whether the entry is live after this write.
    /// @param epoch The entry's new epoch.
    event ChainSet(bytes32 indexed chainRef, bool enabled, uint64 epoch);
    /// @notice An asset entry was written or retired.
    /// @param assetId The asset.
    /// @param uses The asset's global use bits after this write.
    /// @param enabled Whether the entry is live after this write.
    /// @param epoch The entry's new epoch.
    event AssetSet(bytes32 indexed assetId, uint8 uses, bool enabled, uint64 epoch);
    /// @notice One asset's per-chain configuration was written or retired.
    /// @param assetId The asset.
    /// @param chainRef The chain it was configured on.
    /// @param uses The per-chain use bits after this write.
    /// @param enabled Whether the row is live after this write.
    /// @param epoch The row's new epoch.
    event AssetChainSet(
        bytes32 indexed assetId, bytes32 indexed chainRef, uint8 uses, bool enabled, uint64 epoch
    );
    /// @notice A policy bound was written or retired.
    /// @param paramId The bound.
    /// @param value Its new value.
    /// @param enabled Whether the bound is live after this write.
    /// @param epoch The row's new epoch.
    event PolicySet(bytes32 indexed paramId, uint256 value, bool enabled, uint64 epoch);
    /// @notice A chain's execution terms were written.
    /// @param chainRef The chain.
    /// @param defaultLane The lane a leg takes unless the intent names another available one.
    /// @param lanes Bitmask of lanes available on this chain.
    /// @param quoteWindowSeconds How long a fee quote for this chain stays valid.
    /// @param floatPremiumBps Per-chain float premium on top of cost.
    /// @param epoch The row's new epoch.
    event ChainTermsSet(
        bytes32 indexed chainRef, uint8 defaultLane, uint16 lanes, uint32 quoteWindowSeconds, uint16 floatPremiumBps, uint64 epoch
    );
    /// @notice A price venue was written or retired.
    /// @param assetId The asset priced.
    /// @param chainRef The chain the venue is on; zero for an off-chain venue.
    /// @param venueId Distinguishes venues for one asset on one chain.
    /// @param enabled Whether the venue is live after this write.
    /// @param epoch The row's new epoch.
    event PriceSourceSet(
        bytes32 indexed assetId, bytes32 indexed chainRef, bytes32 indexed venueId, bool enabled, uint64 epoch
    );
    /// @notice A DEX deployment was written or retired.
    /// @param chainRef The chain.
    /// @param protocolId The protocol family.
    /// @param enabled Whether the deployment is live after this write.
    /// @param epoch The row's new epoch.
    event DexProtocolSet(bytes32 indexed chainRef, bytes32 indexed protocolId, bool enabled, uint64 epoch);
    /// @notice Both roots were republished after a mutation.
    /// @dev The pair is emitted together because a consumer verifying one against the other's epoch would be
    ///       verifying against a moment that never existed.
    /// @param chainRoot Root of the chain set.
    /// @param assetRoot Root of the asset set.
    /// @param epoch The registry epoch both roots were folded at.
    event RootsPublished(bytes32 chainRoot, bytes32 assetRoot, uint64 epoch);
    /// @notice The bootstrap admin was cleared, permanently. Every later mutation requires the quorum.
    event Sealed();

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

    /// @notice Thrown when a bootstrap-only entrypoint is reached by anyone but the admin.
    /// @param caller The rejected caller.
    error NotAdmin(address caller);
    /// @notice The default lane is not among the chain's available lanes.
    error LaneNotAvailable(bytes32 chainRef, uint8 defaultLane, uint16 lanes);
    /// @notice The epoch can be seeded only into a registry that holds nothing.
    error NotFresh();
    /// @notice A fresh registry took over the previous registry's epoch.
    event EpochSeeded(uint64 epoch);
    /// @notice Thrown when a bootstrap-only entrypoint is reached after sealing.
    /// @dev Sealing is one-way by design: a re-openable bootstrap window is not a bootstrap window.
    error AlreadySealed();
    /// @notice Thrown when the requested threshold exceeds the number of live role members.
    /// @dev Refused up front, because a threshold nobody can reach would freeze the registry with no way back.
    /// @param live Members currently holding the role.
    /// @param asked The threshold requested.
    error ThresholdUnreachable(uint256 live, uint256 asked);
    /// @notice Thrown when a mutation is attempted before the publisher role and threshold are configured.
    error NotConfigured();
    /// @notice Thrown when a chain that was never registered is referenced.
    /// @param chainRef The unknown chain.
    error UnknownChain(bytes32 chainRef);
    /// @notice Thrown when an asset that was never registered is referenced.
    /// @param assetId The unknown asset.
    error UnknownAsset(bytes32 assetId);
    /// @notice Thrown when a batch call carries no entries.
    /// @dev Refused rather than treated as a no-op, so an empty batch cannot silently burn a quorum nonce.
    error EmptyBatch();
    /// @notice Thrown when a row names a chain that has no entry.
    /// @param chainRef The missing chain.
    error ChainNotRegistered(bytes32 chainRef);
    /// @notice Thrown when a chain entry's CAIP pair does not hash to the reference it claims.
    /// @dev The check that keeps a self-describing entry honest; without it a correct-looking proof would name
    ///       the wrong chain.
    /// @param claimed The reference the entry states.
    /// @param derived The reference its CAIP pair actually hashes to.
    error ChainRefMismatch(bytes32 claimed, bytes32 derived);
    /// @notice Thrown when a row names an asset that has no entry.
    /// @param assetId The missing asset.
    error AssetNotRegistered(bytes32 assetId);
    /// @dev A use bit set in the wrong scope — a global bit on a per-chain row
    ///      or the reverse. Refused rather than stored, because a bit nothing
    ///      reads still looks accepted to the operator who set it.
    error UseBitOutOfScope(uint8 uses);
    /// @dev A kind-4 row is malformed for its venue kind: a CEX row on a chain
    ///      or with a protocol, a DEX row with no chain, no pool, no protocol,
    ///      or a protocol the chain has no row for, a zero weight, a zero window.
    error InvalidPriceSource(bytes32 assetId, bytes32 chainRef, bytes32 venueId);
    /// @dev A kind-5 row names no protocol, no factory, or an unknown kind.
    error InvalidDexProtocol(bytes32 chainRef, bytes32 protocolId);
    /// @notice Thrown when a chain's execution model contradicts its CAIP namespace.
    /// @dev A chain in the EVM namespace must declare the EVM model; disagreeing would send consumers to the
    ///       wrong adapter with a valid proof.
    /// @param chainRef The chain.
    /// @param vmKind The rejected execution model.
    error VmKindMismatch(bytes32 chainRef, uint8 vmKind);
    /// @dev A chain row's protocol description is incomplete for an enabled
    ///      chain: no block timer, no finality rule, no gas model.
    error IncompleteChainDescription(bytes32 chainRef);
    /// @notice `role` is not one of the `CHAIN_ROLE_*` values.
    error InvalidChainRole(bytes32 chainRef, uint8 role);
    /// @notice An enabled FULL chain named no settlement contract.
    error SettlementRequired(bytes32 chainRef);
    /// @notice A per-chain leverage cap outside `[LEVERAGE_CAP_MIN_PCT, LEVERAGE_CAP_MAX_PCT]`.
    error LeverageCapOutOfRange(bytes32 assetId, bytes32 chainRef, uint16 maxLeveragePct);
    /// @notice An address word on a row for an EVM chain carries more than 20
    ///         significant bytes.
    /// @dev The address fields are `bytes32` so a non-EVM chain fits, and on
    ///      an EVM chain that width is exactly the room for a mistake: a word
    ///      whose upper 12 bytes are not zero is not an address, and every
    ///      consumer widening it back with `address(uint160(word))` would
    ///      silently TRUNCATE to a different, well-formed address. There is no
    ///      later check — the truncated address looks correct everywhere — so
    ///      it is refused at the one place the full word is still visible.
    /// @param chainRef The EVM chain the row is on.
    /// @param field The field name, as written in the struct.
    /// @param word The rejected word.
    error NotAnEvmAddress(bytes32 chainRef, string field, bytes32 word);

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

    /**
     * @dev The precompile probe is the point of having a constructor. A
     * registry deployed where ML-DSA cannot be verified would accept no quorum
     * it was ever given, and the first symptom would be a roster nobody can
     * change.
     */
    constructor(FinalIdentityRegistry registry_, FinalStateTrees trees_, address admin_) {
        FinalChainPrecompiles.assertAvailable();
        registry = registry_;
        trees = trees_;
        admin = admin_;
    }

    // ------------------------------------------------------------- bootstrap

    /**
     * @notice Set which role may mutate the registry, and how many approvals.
     * @dev Bootstrap only. A threshold above the live member count is refused
     * rather than stored: that is not a strict quorum, it is a registry that
     * reverts on every write with the revert naming the threshold rather than
     * the roster.
     */
    function configure(
        uint256 role,
        uint256 k,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        // The admin alone while this contract's window is open; the sealed
        // `ROLE_REGISTRAR` quorum afterwards, exactly as on the registry and
        // the trees. Before the quorum path existed, `seal()` froze this
        // configuration forever — a publisher set that could never re-threshold.
        if (msg.sender != admin) {
            registry.requireRegistrarQuorum(
                ACTION_CONFIGURE, keccak256(abi.encode(role, k)), anchorBlock, approvals
            );
        }
        if (k != 0) {
            uint256 live = registry.liveMemberCount(role);
            if (live < k) revert ThresholdUnreachable(live, k);
        }
        publisherRole = role;
        threshold = k;
        emit RegistryConfigured(role, k);
    }

    /// @notice Close the bootstrap window. One way.
    function seal() external {
        if (msg.sender != admin) revert NotAdmin(msg.sender);
        admin = address(0);
        emit Sealed();
    }

    /**
     * @notice Take over the previous registry's `registryEpoch`, so the first
     *         rows this registry publishes carry an epoch every consumer's
     *         ring already accepts as newer. A redeploy does NOT wipe: this
     *         is how a fresh contract is brought up already ahead of the
     *         consumers rather than behind them. Bootstrap admin only, and
     *         only while this registry is still empty.
     */
    function seedEpoch(uint64 epoch_) external {
        if (msg.sender != admin) revert NotAdmin(msg.sender);
        if (registryEpoch != 0 || _chainRefs.length != 0 || _assetIds.length != 0) revert NotFresh();
        registryEpoch = epoch_;
        emit EpochSeeded(epoch_);
    }

    // -------------------------------------------------------------- mutation

    /**
     * @notice Add or update chains and assets, and remove them by disabling.
     *
     * @dev One entrypoint for add, update and remove, because they are the same
     * write. A separate `remove` would be a second path to the same storage
     * with its own quorum check to get wrong, and "removed" here is a field
     * rather than an absence.
     *
     * The digest binds the nonce AND the full batch. Binding only the batch
     * would make an approval to enable an asset an approval to re-enable it at
     * any later block — which, for a roster that gates settlement, is the whole
     * attack.
     *
     * An asset whose `originChainRef` names a chain this registry does not hold
     * is refused. The asset registry's default-allow is bounded by the chain
     * set's default-deny, and that only holds if the reference resolves.
     *
     * The address words a row carries — `paymaster` on a chain row, `token` /
     * `morph` / `vAsset` on a per-chain row — are refused when the chain is in
     * the EVM namespace and the word does not fit in twenty bytes
     * (`NotAnEvmAddress`). Zero is always accepted: it is how each of them says
     * "nothing here", and the fields are optional by design.
     */
    function mutate(
        ChainEntry[] calldata chainUpdates,
        AssetEntry[] calldata assetUpdates,
        AssetChainEntry[] calldata assetChainUpdates,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        uint256 k = threshold;
        if (k == 0) revert NotConfigured();
        if (chainUpdates.length == 0 && assetUpdates.length == 0 && assetChainUpdates.length == 0) {
            revert EmptyBatch();
        }

        uint64 n = nonce;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this),
                ACTION_MUTATE,
                anchorBlock,
                keccak256(abi.encode(n, chainUpdates, assetUpdates, assetChainUpdates))
            ),
            publisherRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce = n + 1;

        uint64 e = registryEpoch + 1;
        registryEpoch = e;

        // One extra slot per kind-2 row: the fee-asset cadence rule below may
        // republish that row's kind-1 leaf in the same write. Unused slots are
        // trimmed before the writer call.
        uint256 rows = chainUpdates.length + assetUpdates.length + 2 * assetChainUpdates.length;
        bytes32[] memory keys = new bytes32[](rows);
        bytes32[] memory leaves = new bytes32[](rows);
        uint256 w;

        for (uint256 i = 0; i < chainUpdates.length; i++) {
            ChainEntry calldata c = chainUpdates[i];
            // The reference must hash to the ref it is filed under. Unchecked,
            // an entry could name Ethereum and carry Polygon's preimage — every
            // proof would verify and every consumer would go to the wrong
            // chain, which is the failure a hash-only entry cannot even have
            // because it cannot be read at all.
            if (chainRefFor(c.caipNamespace, c.caipReference) != c.chainRef) {
                revert ChainRefMismatch(c.chainRef, chainRefFor(c.caipNamespace, c.caipReference));
            }
            // An enabled chain must be readable: the oracles pace on the block
            // timer, the settlement lane waits on the finality rule, the fee
            // quote needs the gas model, and every adapter branches on the VM
            // kind. A tombstone may leave them zero.
            if (c.enabled && (c.blockTimeMs == 0 || c.finalityKind == 0 || c.gasModel == 0 || c.vmKind == 0)) {
                revert IncompleteChainDescription(c.chainRef);
            }
            if (c.role > CHAIN_ROLE_OBSERVED) revert InvalidChainRole(c.chainRef, c.role);
            // A FULL chain settles; an OBSERVED one is only read, so it may
            // name no contracts of ours at all.
            if (c.enabled && c.role == CHAIN_ROLE_FULL && c.settlement == bytes32(0)) {
                revert SettlementRequired(c.chainRef);
            }
            // The namespace and the machine must agree where the namespace
            // decides it: an eip155 row claiming any other VM would send every
            // adapter to the wrong codepath with a self-consistent row.
            if (c.caipNamespace == keccak256("eip155") && c.vmKind != VM_EVM) {
                revert VmKindMismatch(c.chainRef, c.vmKind);
            }
            // The fee lane, where the row names one of its own. Widened like
            // every other address here, so it is checked like every other one.
            if (c.caipNamespace == CAIP_NAMESPACE_EIP155) {
                _requireEvmWord(c.chainRef, "paymaster", c.paymaster);
            }
            if (c.l1ChainRef != bytes32(0) && !_chainKnown[c.l1ChainRef]) revert ChainNotRegistered(c.l1ChainRef);
            if (!_chainKnown[c.chainRef]) {
                _chainKnown[c.chainRef] = true;
                _chainRefs.push(c.chainRef);
            }
            // Calldata straight into storage, the epoch stamped there, the leaf
            // hashed from storage: no memory copy of the whole row.
            ChainEntry storage sc = _chains[c.chainRef];
            sc.chainRef = c.chainRef;
            sc.caipNamespace = c.caipNamespace;
            sc.caipReference = c.caipReference;
            sc.settlement = c.settlement;
            sc.accountSpace = c.accountSpace;
            sc.nativeAsset = c.nativeAsset;
            sc.wrappedNative = c.wrappedNative;
            sc.finalityKind = c.finalityKind;
            sc.finalityParam = c.finalityParam;
            sc.blockTimeMs = c.blockTimeMs;
            sc.gasReadBlocks = c.gasReadBlocks;
            sc.gasHistoryBlocks = c.gasHistoryBlocks;
            sc.multicall = c.multicall;
            sc.gasModel = c.gasModel;
            sc.l1ChainRef = c.l1ChainRef;
            sc.gateway = c.gateway;
            sc.vmKind = c.vmKind;
            sc.enabled = c.enabled;
            sc.epoch = e;
            sc.startHeight = c.startHeight;
            sc.role = c.role;
            sc.paymaster = c.paymaster;
            keys[w] = allowlistKeyForChain(c.chainRef);
            leaves[w] = _allowlistLeafForChain(sc);
            w++;
            emit ChainSet(c.chainRef, c.enabled, e);
        }

        for (uint256 i = 0; i < assetUpdates.length; i++) {
            AssetEntry memory a = assetUpdates[i];
            if (!_chainKnown[a.originChainRef]) revert ChainNotRegistered(a.originChainRef);
            if (a.uses & ~GLOBAL_USE_MASK != 0) revert UseBitOutOfScope(a.uses);
            // The cadence is a property of the asset's USES, chosen here rather
            // than by each publisher: a morphable asset is what liquidations
            // mark against, so it refreshes every second whether or not the
            // operator remembered to say so. A fee asset is per chain (kind 2),
            // which is why the kind-2 loop below applies the same rule.
            if (a.priceCadenceMs == 0) {
                a.priceCadenceMs = (a.uses & USE_MORPH) != 0 ? PRICE_CADENCE_FAST_MS : PRICE_CADENCE_DEFAULT_MS;
            }
            if (a.maxAgeMs == 0) {
                a.maxAgeMs = (a.uses & USE_MORPH) != 0 ? PRICE_MAX_AGE_FAST_MS : PRICE_MAX_AGE_DEFAULT_MS;
            }
            a.epoch = e;
            bytes32 id = assetIdFor(a.originChainRef, a.originToken);
            if (!_assetKnown[id]) {
                _assetKnown[id] = true;
                _assetIds.push(id);
            }
            _assets[id] = a;
            keys[w] = allowlistKeyForAsset(id);
            leaves[w] = _allowlistLeafForAsset(id, a);
            w++;
            emit AssetSet(id, a.uses, a.enabled, e);
        }

        for (uint256 i = 0; i < assetChainUpdates.length; i++) {
            AssetChainEntry memory ac = assetChainUpdates[i];
            // Both halves must exist. A per-chain row naming an asset the
            // registry does not know, or a chain it does not settle on, is a
            // policy statement about nothing — and it would sit in the tree
            // looking authoritative.
            if (!_chainKnown[ac.chainRef]) revert ChainNotRegistered(ac.chainRef);
            if (!_assetKnown[ac.assetId]) revert AssetNotRegistered(ac.assetId);
            // One scope per bit. A global bit arriving on a per-chain row would
            // be stored and never read, which is worse than a refusal: the
            // operator sees it accepted.
            if (ac.uses & ~CHAIN_USE_MASK != 0) revert UseBitOutOfScope(ac.uses);
            if (
                ac.maxLeveragePct != 0
                    && (ac.maxLeveragePct < LEVERAGE_CAP_MIN_PCT || ac.maxLeveragePct > LEVERAGE_CAP_MAX_PCT)
            ) revert LeverageCapOutOfRange(ac.assetId, ac.chainRef, ac.maxLeveragePct);
            // The row's addresses are addresses ON ITS CHAIN, so the width
            // rule comes from the chain's namespace, which the registry
            // already holds — the row does not carry one and must not, or two
            // rows on one chain could disagree about how wide its addresses
            // are.
            if (_chains[ac.chainRef].caipNamespace == CAIP_NAMESPACE_EIP155) {
                _requireEvmWord(ac.chainRef, "token", ac.token);
                _requireEvmWord(ac.chainRef, "morph", ac.morph);
                _requireEvmWord(ac.chainRef, "vAsset", ac.vAsset);
            }
            ac.epoch = e;
            _assetChains[ac.assetId][ac.chainRef] = ac;
            keys[w] = allowlistKeyForAssetOnChain(ac.assetId, ac.chainRef);
            leaves[w] = _allowlistLeafForAssetOnChain(ac);
            w++;
            emit AssetChainSet(ac.assetId, ac.chainRef, ac.uses, ac.enabled, e);
            // A fee asset anywhere is a fast asset everywhere: the paymaster
            // converts at its price, so the global row's cadence tightens the
            // first time a chain accepts it as a fee token. Written as its own
            // leaf so the kind-1 proof moves with the fact.
            if (ac.enabled && (ac.uses & USE_FEE) != 0) {
                AssetEntry storage ga = _assets[ac.assetId];
                if (ga.priceCadenceMs > PRICE_CADENCE_FAST_MS || ga.maxAgeMs > PRICE_MAX_AGE_FAST_MS) {
                    ga.priceCadenceMs = PRICE_CADENCE_FAST_MS;
                    ga.maxAgeMs = PRICE_MAX_AGE_FAST_MS;
                    ga.epoch = e;
                    keys[w] = allowlistKeyForAsset(ac.assetId);
                    leaves[w] = _allowlistLeafForAsset(ac.assetId, ga);
                    w++;
                    emit AssetSet(ac.assetId, ga.uses, ga.enabled, e);
                }
            }
        }

        // Trim the unused tail so the writer sees exactly the rows written.
        assembly ("memory-safe") {
            mstore(keys, w)
            mstore(leaves, w)
        }
        trees.setLeavesAsWriter(TREE_ALLOWLIST, BRANCH_MAIN_ID, keys, leaves);
        _republishRoots(e);
    }

    /// @dev Refuses a widened address that cannot be one on an EVM chain.
    ///      Zero passes: it is the "not deployed here" value every address
    ///      field on these rows uses, and refusing it would make the fields
    ///      mandatory rather than optional.
    /// @param chainRef The EVM chain the word is an address on.
    /// @param field The field name, carried into the revert so an operator
    ///        reading a failed batch knows which of four words was wrong.
    /// @param word The word to check.
    function _requireEvmWord(bytes32 chainRef, string memory field, bytes32 word) private pure {
        if (uint256(word) > type(uint160).max) revert NotAnEvmAddress(chainRef, field, word);
    }

    /**
     * @notice Set or retire price sources (kind 4) and DEX protocol deployments
     *         (kind 5).
     *
     * @dev Its own door for the reasons `setPolicy` is: `mutate`'s digest binds
     * its three arrays, and neither kind is copied to other chains, so the
     * registry roots in tree 5 must not move for them. Shared nonce, distinct
     * action.
     *
     * Protocols are applied before sources within the batch, so a pool and the
     * deployment it is verified against can land in one write.
     */
    function setSources(
        DexProtocolEntry[] calldata protocolUpdates,
        PriceSourceEntry[] calldata sourceUpdates,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        uint256 k = threshold;
        if (k == 0) revert NotConfigured();
        if (protocolUpdates.length == 0 && sourceUpdates.length == 0) revert EmptyBatch();

        uint64 n = nonce;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this),
                ACTION_SET_SOURCES,
                anchorBlock,
                keccak256(abi.encode(n, protocolUpdates, sourceUpdates))
            ),
            publisherRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce = n + 1;

        uint64 e = registryEpoch + 1;
        registryEpoch = e;

        uint256 rows = protocolUpdates.length + sourceUpdates.length;
        bytes32[] memory keys = new bytes32[](rows);
        bytes32[] memory leaves = new bytes32[](rows);
        uint256 w;

        for (uint256 i = 0; i < protocolUpdates.length; i++) {
            DexProtocolEntry calldata p = protocolUpdates[i];
            if (!_chainKnown[p.chainRef]) revert ChainNotRegistered(p.chainRef);
            if (p.protocolId == bytes32(0) || (p.enabled && (p.protocolKind == 0 || p.factory == bytes32(0)))) {
                revert InvalidDexProtocol(p.chainRef, p.protocolId);
            }
            DexProtocolEntry storage sp = _protocols[p.chainRef][p.protocolId];
            if (sp.epoch == 0) _protocolIdsOn[p.chainRef].push(p.protocolId);
            sp.chainRef = p.chainRef;
            sp.protocolId = p.protocolId;
            sp.protocolKind = p.protocolKind;
            sp.factory = p.factory;
            sp.quoter = p.quoter;
            sp.router = p.router;
            sp.positionManager = p.positionManager;
            sp.enabled = p.enabled;
            sp.epoch = e;
            keys[w] = allowlistKeyForProtocol(p.chainRef, p.protocolId);
            leaves[w] = _allowlistLeafForProtocol(sp);
            w++;
            emit DexProtocolSet(p.chainRef, p.protocolId, p.enabled, e);
        }

        for (uint256 i = 0; i < sourceUpdates.length; i++) {
            PriceSourceEntry calldata s = sourceUpdates[i];
            if (!_assetKnown[s.assetId]) revert AssetNotRegistered(s.assetId);
            _assertPriceSource(s);
            bytes32 key = allowlistKeyForSource(s.assetId, s.chainRef, s.venueId);
            if (!_sourceKnown[key]) {
                _sourceKnown[key] = true;
                _sourceKeys.push(key);
                _sourceKeysFor[s.assetId].push(key);
            }
            PriceSourceEntry storage ss = _sources[key];
            ss.assetId = s.assetId;
            ss.chainRef = s.chainRef;
            ss.venueId = s.venueId;
            ss.venueKind = s.venueKind;
            ss.venue = s.venue;
            ss.protocolId = s.protocolId;
            ss.symbol = s.symbol;
            ss.quoteAsset = s.quoteAsset;
            ss.weight = s.weight;
            ss.baseToken = s.baseToken;
            ss.quoteToken = s.quoteToken;
            ss.baseIsToken0 = s.baseIsToken0;
            ss.twapWindowSeconds = s.twapWindowSeconds;
            ss.twapMinWindowSeconds = s.twapMinWindowSeconds;
            ss.minLiquidity = s.minLiquidity;
            ss.maxSpotDeviationBps = s.maxSpotDeviationBps;
            ss.enabled = s.enabled;
            ss.epoch = e;
            keys[w] = key;
            leaves[w] = _allowlistLeafForSource(ss);
            w++;
            emit PriceSourceSet(s.assetId, s.chainRef, s.venueId, s.enabled, e);
        }

        trees.setLeavesAsWriter(TREE_ALLOWLIST, BRANCH_MAIN_ID, keys, leaves);
    }

    /// @dev The shape rules of a kind-4 row, by venue kind. A tombstone
    ///      (`enabled == false`) only needs its identity.
    function _assertPriceSource(PriceSourceEntry calldata s) private view {
        if (s.venueId == bytes32(0)) revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
        if (!s.enabled) return;
        if (s.weight == 0 || s.venue == bytes32(0)) revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
        if (s.venueKind == uint8(VenueKind.Cex)) {
            // A CEX is on no chain and verifies against no factory.
            if (s.chainRef != bytes32(0) || s.protocolId != bytes32(0)) {
                revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
            }
            return;
        }
        if (s.venueKind != uint8(VenueKind.Dex) && s.venueKind != uint8(VenueKind.Aggregator)) {
            revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
        }
        // A pool is on a chain the registry knows, verified against a
        // deployment that chain has a row for, with a real window.
        if (!_chainKnown[s.chainRef]) revert ChainNotRegistered(s.chainRef);
        DexProtocolEntry storage p = _protocols[s.chainRef][s.protocolId];
        if (s.protocolId == bytes32(0) || !p.enabled) revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
        if (s.baseToken == bytes32(0) || s.quoteToken == bytes32(0)) {
            revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
        }
        if (s.twapWindowSeconds == 0 || s.twapMinWindowSeconds == 0 || s.twapMinWindowSeconds > s.twapWindowSeconds) {
            revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
        }
    }

    /**
     * @notice Set or retire global policy scalars — tree 6, kind 3.
     *
     * @dev Deliberately NOT part of `mutate`. Two reasons, and the second is
     * the load-bearing one:
     *
     *  - `mutate`'s digest binds its three arrays, so widening it would
     *    invalidate every approval shape already in use for no gain here; and
     *  - policy scalars are not copied to other chains. `mutate` ends by
     *    republishing the chain and asset roots into tree 5, which is what
     *    `syncChain` copies. A swap ceiling has no business advancing that
     *    root: every chain would see a new registry epoch to catch up to, for a
     *    number none of them read.
     *
     * The nonce is shared with `mutate` so the two paths are totally ordered,
     * and the action is distinct so an approval for one cannot be replayed
     * through the other.
     */
    function setPolicy(
        PolicyEntry[] calldata updates,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        uint256 k = threshold;
        if (k == 0) revert NotConfigured();
        if (updates.length == 0) revert EmptyBatch();

        uint64 n = nonce;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this), ACTION_SET_POLICY, anchorBlock, keccak256(abi.encode(n, updates))
            ),
            publisherRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce = n + 1;

        uint64 e = registryEpoch + 1;
        registryEpoch = e;

        bytes32[] memory keys = new bytes32[](updates.length);
        bytes32[] memory leaves = new bytes32[](updates.length);
        for (uint256 i = 0; i < updates.length; i++) {
            PolicyEntry memory p = updates[i];
            p.epoch = e;
            if (!_policyKnown[p.paramId]) {
                _policyKnown[p.paramId] = true;
                _policyIds.push(p.paramId);
            }
            _policy[p.paramId] = p;
            keys[i] = allowlistKeyForPolicy(p.paramId);
            leaves[i] = _allowlistLeafForPolicy(p);
            emit PolicySet(p.paramId, p.value, p.enabled, e);
        }
        trees.setLeavesAsWriter(TREE_ALLOWLIST, BRANCH_MAIN_ID, keys, leaves);
    }

    /**
     * @notice Publish per-chain execution terms (kind 7) — the default lane,
     *         the available lanes and the fee-quote bounds a leg on that chain
     *         runs under. The same quorum as every other row; a newer epoch
     *         supersedes the old record, which is how these change.
     */
    function setChainTerms(
        ChainTermsEntry[] calldata updates,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        uint256 k = threshold;
        if (k == 0) revert NotConfigured();
        if (updates.length == 0) revert EmptyBatch();

        uint64 n = nonce;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this), ACTION_SET_CHAIN_TERMS, anchorBlock, keccak256(abi.encode(n, updates))
            ),
            publisherRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce = n + 1;

        uint64 e = registryEpoch + 1;
        registryEpoch = e;

        bytes32[] memory keys = new bytes32[](updates.length);
        bytes32[] memory leaves = new bytes32[](updates.length);
        for (uint256 i = 0; i < updates.length; i++) {
            ChainTermsEntry memory t = updates[i];
            if (!_chainKnown[t.chainRef]) revert ChainNotRegistered(t.chainRef);
            if (t.lanes & (uint16(1) << t.defaultLane) == 0) revert LaneNotAvailable(t.chainRef, t.defaultLane, t.lanes);
            t.epoch = e;
            _chainTerms[t.chainRef] = t;
            keys[i] = allowlistKeyForChainTerms(t.chainRef);
            leaves[i] = _allowlistLeafForChainTerms(t);
            emit ChainTermsSet(t.chainRef, t.defaultLane, t.lanes, t.quoteWindowSeconds, t.floatPremiumBps, e);
        }
        trees.setLeavesAsWriter(TREE_ALLOWLIST, BRANCH_MAIN_ID, keys, leaves);
    }

    /**
     * @dev Recompute both registry roots and write them into tree 5.
     *
     * Both are folded over the ENABLED entries only. A disabled entry is a
     * tombstone in tree 6, where a consumer needs to prove the negative; it is
     * not a member of the registry a settlement contract copies from, and
     * including it would make `syncChain` able to copy a revoked chain.
     */
    function _republishRoots(uint64 e) private {
        chainRegistryRoot = _foldChains();
        assetRegistryRoot = _foldAssets();

        bytes32[] memory keys = new bytes32[](2);
        bytes32[] memory leaves = new bytes32[](2);
        keys[0] = registryRootKey(REGISTRY_CHAIN);
        leaves[0] = keccak256(
            abi.encode(DOMAIN_REGISTRY_ROOT_LEAF, REGISTRY_CHAIN, chainRegistryRoot, e)
        );
        keys[1] = registryRootKey(REGISTRY_ASSET);
        leaves[1] = keccak256(
            abi.encode(DOMAIN_REGISTRY_ROOT_LEAF, REGISTRY_ASSET, assetRegistryRoot, e)
        );
        trees.setLeavesAsWriter(TREE_SETTLEMENT, BRANCH_MAIN_ID, keys, leaves);
        emit RootsPublished(chainRegistryRoot, assetRegistryRoot, e);
    }

    // ------------------------------------------------------------------ keys

    /// @dev `assetIdFor` as `FinalSettlement` derives it. Not a stored field:
    /// a record carrying its own id could disagree with its own contents.
    function assetIdFor(bytes32 originChainRef, bytes32 originToken) public pure returns (bytes32) {
        return keccak256(abi.encode(originChainRef, originToken));
    }

    /// @notice Allowlist key for a chain entry.
    /// @param chainRef The chain.
    /// @return The tree-6 key its leaf is stored under.
    function allowlistKeyForChain(bytes32 chainRef) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(0), chainRef));
    }

    /// @notice Allowlist key for a global asset entry.
    /// @param assetId The asset.
    /// @return The tree-6 key its leaf is stored under.
    function allowlistKeyForAsset(bytes32 assetId) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(1), assetId));
    }

    /// @notice One asset's wiring on one chain — kind 2.
    ///
    /// @dev The third key kind, because paymaster policy and venue integration
    ///      differ by network while morphability and bridging do not. There is
    ///      no inheritance from the global row: a bit is meaningful in exactly
    ///      one scope, so a consumer never has to implement a fallback rule that
    ///      another consumer might implement differently.
    /// @notice One asset's per-chain wiring, or a zeroed entry if it has none.
    function assetOnChain(bytes32 assetId, bytes32 chainRef)
        external
        view
        returns (AssetChainEntry memory)
    {
        return _assetChains[assetId][chainRef];
    }

    /// @notice The morph leverage ceiling for `assetId` on EVM chain `chainId`,
    ///         in percent; 0 when the asset has no per-chain cap there.
    ///         `ILeverageCapSource` for `FinalStateRecords`.
    function leverageCapPct(bytes32 assetId, uint64 chainId) external view returns (uint16) {
        return _assetChains[assetId][evmChainRef(chainId)].maxLeveragePct;
    }

    /// @notice Allowlist key for one asset's row on one chain.
    /// @dev Derived from both ids, so a per-chain row can never collide with the asset's global row.
    /// @param assetId The asset.
    /// @param chainRef The chain.
    /// @return The tree-6 key that row's leaf is stored under.
    function allowlistKeyForAssetOnChain(bytes32 assetId, bytes32 chainRef)
        public
        pure
        returns (bytes32)
    {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(2), assetId, chainRef));
    }

    /// @notice One global policy scalar's tree-6 key — kind 3.
    function allowlistKeyForPolicy(bytes32 paramId) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(3), paramId));
    }

    /// @notice Kind 7: the chain's execution terms.
    function allowlistKeyForChainTerms(bytes32 chainRef) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(7), chainRef));
    }

    /// @notice One price source's tree-6 key — kind 4.
    function allowlistKeyForSource(bytes32 assetId, bytes32 chainRef, bytes32 venueId)
        public
        pure
        returns (bytes32)
    {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(4), assetId, chainRef, venueId));
    }

    /// @notice One DEX protocol deployment's tree-6 key — kind 5.
    function allowlistKeyForProtocol(bytes32 chainRef, bytes32 protocolId) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(5), chainRef, protocolId));
    }

    /// @notice Key under which a published registry root is stored.
    /// @param which `REGISTRY_CHAIN` or `REGISTRY_ASSET`.
    /// @return The tree key for that root.
    function registryRootKey(uint8 which) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_REGISTRY_ROOT_KEY, which));
    }

    // ----------------------------------------------------------- leaf hashes

    /// @dev Kind 0: the complete chain description. Pinned against the
    ///      backend's `chainLeafHash` by test.
    ///
    ///      Field order IS the leaf, so a field is appended at the tail and
    ///      never inserted: an insertion moves every word after it and every
    ///      published proof over this row stops verifying at once, while both
    ///      sides still look internally consistent.
    /// @param c The chain row, read from storage after the epoch is stamped.
    /// @return The leaf.
    function _allowlistLeafForChain(ChainEntry storage c) private view returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ALLOWLIST_LEAF, uint8(0), c.chainRef, c.caipNamespace, c.caipReference,
                c.settlement, c.accountSpace,
                c.nativeAsset, c.wrappedNative, c.finalityKind, c.finalityParam,
                c.blockTimeMs, c.gasReadBlocks, c.gasHistoryBlocks, c.multicall, c.gasModel, c.l1ChainRef,
                c.gateway, c.vmKind,
                c.enabled, c.epoch,
                c.startHeight, c.role, c.paymaster
            )
        );
    }

    /// @dev Kind 1, v2: `pool` / `poolQuote` moved to kind 4; the tree-4
    ///      cadence and staleness joined.
    function _allowlistLeafForAsset(bytes32 id, AssetEntry memory a) private pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ALLOWLIST_LEAF, uint8(1), id, a.originChainRef, a.originToken,
                a.decimals, keccak256(bytes(a.name)), keccak256(bytes(a.symbol)),
                a.uses, a.priceCadenceMs, a.maxAgeMs, a.enabled, a.epoch
            )
        );
    }

    /// @dev Kind 2: this asset's policy and its addresses on one chain. The
    ///      venue is a kind-4 row.
    ///
    ///      The three address words are appended at the tail under the same
    ///      rule the chain leaf follows — order is the leaf, so a field is
    ///      never inserted.
    /// @param e The per-chain row, with the epoch already stamped.
    /// @return The leaf.
    function _allowlistLeafForAssetOnChain(AssetChainEntry memory e) private pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ALLOWLIST_LEAF,
                uint8(2),
                e.assetId,
                e.chainRef,
                e.uses,
                e.enabled,
                e.epoch,
                e.maxLeveragePct,
                e.token,
                e.morph,
                e.vAsset
            )
        );
    }

    /// @dev Folds a policy row into its allowlist leaf. The field order here is part of the published root and
    ///       must match every off-chain producer word for word.
    /// @param p The policy row.
    /// @return The leaf.
    function _allowlistLeafForPolicy(PolicyEntry memory p) private pure returns (bytes32) {
        return keccak256(
            abi.encode(DOMAIN_ALLOWLIST_LEAF, uint8(3), p.paramId, p.value, p.enabled, p.epoch)
        );
    }

    /// @dev Folds a chain-terms row into its allowlist leaf, under the same word-for-word rule.
    /// @param t The terms row.
    /// @return The leaf.
    function _allowlistLeafForChainTerms(ChainTermsEntry memory t) private pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ALLOWLIST_LEAF, uint8(7), t.chainRef, t.defaultLane, t.lanes, t.quoteWindowSeconds,
                t.floatPremiumBps, t.admissionFloorUsdMicros, t.maxLegsPerIntent, t.enabled, t.epoch
            )
        );
    }


    /// @dev Kind 4: identity, venue, composition weight, then every TWAP term
    ///      of the pool. Encoded in two halves because one `abi.encode` over
    ///      twenty words is the same bytes and this reads as the leaf it is.
    function _allowlistLeafForSource(PriceSourceEntry storage s) private view returns (bytes32) {
        return keccak256(
            bytes.concat(
                abi.encode(
                    DOMAIN_ALLOWLIST_LEAF, uint8(4), s.assetId, s.chainRef, s.venueId, s.venueKind, s.venue,
                    s.protocolId, s.symbol, s.quoteAsset, s.weight
                ),
                abi.encode(
                    s.baseToken, s.quoteToken, s.baseIsToken0, s.twapWindowSeconds, s.twapMinWindowSeconds,
                    s.minLiquidity, s.maxSpotDeviationBps, s.enabled, s.epoch
                )
            )
        );
    }

    /// @dev Kind 5.
    function _allowlistLeafForProtocol(DexProtocolEntry storage p) private view returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ALLOWLIST_LEAF, uint8(5), p.chainRef, p.protocolId, p.protocolKind,
                p.factory, p.quoter, p.router, p.positionManager, p.enabled, p.epoch
            )
        );
    }

    /// @dev `FinalSettlement.ChainLeaf`, field for field and in order.
    function chainLeafHash(ChainEntry memory c) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_CHAIN_LEAF, c.chainRef, c.settlement, c.accountSpace, c.epoch));
    }

    /// @dev `FinalSettlement.assetLeafHash`, byte for byte.
    ///
    /// `name` and `symbol` are HASHED, because that is what the consumer does.
    /// This encoded them as raw strings — with a comment asserting the
    /// opposite — so `assetRegistryRoot` was folded over leaves no
    /// `registerAsset` proof could verify against on any settlement chain.
    /// Both sides were internally consistent and no proof verified anywhere,
    /// while the root on the publishing chain looked perfectly healthy.
    /// `AssetLeafParity.t.sol` pins the two against each other.
    ///
    /// Not the same encoding as `_allowlistLeafForAsset`, deliberately: tree 6
    /// commits to the full registry entry, this commits to the six fields
    /// `FinalSettlement` copies.
    function assetLeafHash(AssetEntry memory a) public pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ASSET_LEAF, a.originChainRef, a.originToken,
                a.decimals, keccak256(bytes(a.name)), keccak256(bytes(a.symbol)), a.epoch
            )
        );
    }

    // -------------------------------------------------------------- the fold
    //
    // Sorted-pair, tagged, promoting an odd node — `FinalMerkle`'s shape, which
    // is what `FinalSettlement.verifyTaggedSortedProof` runs. Not tree 6's
    // shape, and the two must not be confused: a fixed depth-20 slotted tree
    // over the same leaves lands on a different root while both sides look
    // right.

    /// @dev Folds the chain set into its root, in insertion order. The order is part of the root, so a sorted
    ///       rebuild produces a different tree that nothing can prove against.
    /// @return The chain root.
    function _foldChains() private view returns (bytes32) {
        uint256 n = _chainRefs.length;
        bytes32[] memory hashes = new bytes32[](n);
        uint256 m;
        for (uint256 i = 0; i < n; i++) {
            ChainEntry memory c = _chains[_chainRefs[i]];
            if (!c.enabled) continue;
            hashes[m++] = chainLeafHash(c);
        }
        return _fold(hashes, m);
    }

    /// @dev Folds the asset set into its root, under the same insertion-order rule.
    /// @return The asset root.
    function _foldAssets() private view returns (bytes32) {
        uint256 n = _assetIds.length;
        bytes32[] memory hashes = new bytes32[](n);
        uint256 m;
        for (uint256 i = 0; i < n; i++) {
            AssetEntry memory a = _assets[_assetIds[i]];
            if (!a.enabled) continue;
            hashes[m++] = assetLeafHash(a);
        }
        return _fold(hashes, m);
    }

    /**
     * @dev Fold `m` leaves of `hashes` into a sorted-pair tagged root.
     *
     * Leaves are sorted first, so the root is a function of the SET rather than
     * of insertion order — two registries holding the same entries must publish
     * the same root however they got there.
     *
     * An empty set folds to zero rather than to a hash of nothing. A consumer
     * comparing against zero can tell "no registry published" from "a registry
     * that happens to be empty"; a hash cannot be distinguished from a real
     * root without knowing the construction.
     */
    function _fold(bytes32[] memory hashes, uint256 m) private pure returns (bytes32) {
        if (m == 0) return bytes32(0);
        // Insertion sort. `m` is tens of entries and the alternative is a
        // quicksort's worst case on an adversarially ordered set, which here
        // would be a set an operator chose.
        for (uint256 i = 1; i < m; i++) {
            bytes32 x = hashes[i];
            uint256 j = i;
            while (j > 0 && hashes[j - 1] > x) {
                hashes[j] = hashes[j - 1];
                j--;
            }
            hashes[j] = x;
        }
        // Tag every leaf once, then pair upward.
        for (uint256 i = 0; i < m; i++) {
            hashes[i] = keccak256(abi.encodePacked(bytes1(0x00), hashes[i]));
        }
        uint256 len = m;
        while (len > 1) {
            uint256 w;
            for (uint256 i = 0; i < len; i += 2) {
                if (i + 1 == len) {
                    // Odd node promoted, not paired with itself: hashing a node
                    // with its own value makes a one-element layer collide with
                    // a two-element layer holding it twice.
                    hashes[w++] = hashes[i];
                    continue;
                }
                (bytes32 lo, bytes32 hi) =
                    hashes[i] < hashes[i + 1] ? (hashes[i], hashes[i + 1]) : (hashes[i + 1], hashes[i]);
                hashes[w++] = keccak256(abi.encodePacked(bytes1(0x01), lo, hi));
            }
            len = w;
        }
        return hashes[0];
    }

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

    /// @notice One policy scalar. `enabled == false` is a RETIRED bound, and a
    /// never-written one is zeroed — which is why the flag is returned and not
    /// inferred from the value. Zero is a legitimate ceiling.
    function policyOf(bytes32 paramId) external view returns (PolicyEntry memory) {
        return _policy[paramId];
    }

    /// @notice Number of policy bounds configured.
    /// @return The count.
    function policyCount() external view returns (uint256) {
        return _policyIds.length;
    }

    /// @notice Reads one policy bound by index.
    /// @dev Index order is insertion order and is stable, so paging over it cannot skip or repeat a row.
    /// @param i Index into the policy list.
    /// @return The policy row.
    function policyAt(uint256 i) external view returns (PolicyEntry memory) {
        return _policy[_policyIds[i]];
    }

    /// @notice A chain's execution terms, or a zeroed entry when none were published.
    function chainTermsOf(bytes32 chainRef) external view returns (ChainTermsEntry memory) {
        return _chainTerms[chainRef];
    }

    /// @notice Number of chains registered.
    /// @return The count.
    function chainCount() external view returns (uint256) {
        return _chainRefs.length;
    }

    /// @notice Number of assets registered.
    /// @return The count.
    function assetCount() external view returns (uint256) {
        return _assetIds.length;
    }

    /// @notice Reads one chain entry by index, in insertion order.
    /// @param i Index into the chain list.
    /// @return The chain entry.
    function chainAt(uint256 i) external view returns (ChainEntry memory) {
        return _chains[_chainRefs[i]];
    }

    /// @notice Reads one asset entry by index, in insertion order.
    /// @param i Index into the asset list.
    /// @return The asset entry.
    function assetAt(uint256 i) external view returns (AssetEntry memory) {
        return _assets[_assetIds[i]];
    }

    /// @notice Reads a chain entry by reference.
    /// @dev Returns a zeroed entry for an unknown chain; use the membership probe to tell the two apart.
    /// @param chainRef The chain.
    /// @return The chain entry.
    function chainOf(bytes32 chainRef) external view returns (ChainEntry memory) {
        if (!_chainKnown[chainRef]) revert UnknownChain(chainRef);
        return _chains[chainRef];
    }

    /// @notice Reads an asset entry by id.
    /// @dev Returns a zeroed entry for an unknown asset; use the membership probe to tell the two apart.
    /// @param assetId The asset.
    /// @return The asset entry.
    function assetOf(bytes32 assetId) external view returns (AssetEntry memory) {
        if (!_assetKnown[assetId]) revert UnknownAsset(assetId);
        return _assets[assetId];
    }

    /**
     * @notice Every enabled asset carrying `use`.
     * @dev The roster a publisher reads instead of an environment variable. It
     * is a view over state rather than a list handed to a process, so two
     * publishers cannot disagree about what the set is.
     */
    function assetsFor(uint8 use) external view returns (AssetEntry[] memory out) {
        uint256 n = _assetIds.length;
        AssetEntry[] memory buf = new AssetEntry[](n);
        uint256 m;
        for (uint256 i = 0; i < n; i++) {
            AssetEntry memory a = _assets[_assetIds[i]];
            if (a.enabled && (a.uses & use) != 0) buf[m++] = a;
        }
        out = new AssetEntry[](m);
        for (uint256 i = 0; i < m; i++) out[i] = buf[i];
    }

    /**
     * @notice Every asset carrying `use` ON `chainRef`, with its venue wiring.
     *
     * @dev The per-chain counterpart to `assetsFor`, and the roster a paymaster
     * actually needs: `USE_FEE` and `USE_REFILL` live only on the per-chain row,
     * so asking `assetsFor(USE_FEE)` would return nothing however many chains
     * accept the asset. Both the global row and the per-chain row must be
     * enabled — a globally revoked asset is revoked everywhere, and leaving that
     * to each consumer to remember is how a disabled asset stays spendable on
     * one chain.
     */
    function assetsOnChainFor(bytes32 chainRef, uint8 use)
        external
        view
        returns (AssetChainEntry[] memory out)
    {
        uint256 n = _assetIds.length;
        AssetChainEntry[] memory buf = new AssetChainEntry[](n);
        uint256 m;
        for (uint256 i = 0; i < n; i++) {
            bytes32 assetId = _assetIds[i];
            if (!_assets[assetId].enabled) continue;
            AssetChainEntry memory ac = _assetChains[assetId][chainRef];
            if (ac.enabled && (ac.uses & use) != 0) buf[m++] = ac;
        }
        out = new AssetChainEntry[](m);
        for (uint256 i = 0; i < m; i++) out[i] = buf[i];
    }

    /// @notice `keccak(DOMAIN_CHAIN_REF, namespace, reference)`, mirroring
    ///         `FinalSettlement.chainRefFor` byte for byte.
    function chainRefFor(bytes32 namespace, bytes32 caipRef) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_CHAIN_REF, namespace, caipRef));
    }

    /// @notice The chain reference for an EVM chain id.
    function evmChainRef(uint256 chainId) public pure returns (bytes32) {
        return chainRefFor(CAIP_NAMESPACE_EIP155, bytes32(chainId));
    }

    /// @notice Every enabled chain's reference — what `FinalStateTrees.syncIdentities`
    ///         builds a service identity's `deployedChains` table from, and
    ///         what a reader enumerates the chain set by (`chainOf` per ref).
    /// @dev Refs rather than rows: an array of the full row is an ABI encoder
    ///      this contract has no room for, and every reader multicalls anyway.
    function enabledChainRefs() external view override returns (bytes32[] memory out) {
        uint256 n = _chainRefs.length;
        bytes32[] memory buf = new bytes32[](n);
        uint256 m;
        for (uint256 i = 0; i < n; i++) {
            if (_chains[_chainRefs[i]].enabled) buf[m++] = _chainRefs[i];
        }
        out = new bytes32[](m);
        for (uint256 i = 0; i < m; i++) out[i] = buf[i];
    }

    // ------------------------------------------------------ kinds 4 and 5, views

    /// @notice One price source, or a zeroed entry if it has none.
    function priceSourceOf(bytes32 assetId, bytes32 chainRef, bytes32 venueId)
        external
        view
        returns (PriceSourceEntry memory)
    {
        return _sources[allowlistKeyForSource(assetId, chainRef, venueId)];
    }

    /// @notice One price source by its tree-6 key.
    function priceSourceByKey(bytes32 key) external view returns (PriceSourceEntry memory) {
        return _sources[key];
    }

    /**
     * @notice The tree-6 keys of every price-source row for an asset, tombstones
     *         included — the roster the median reads, one venue per key, with
     *         the venue's own ticker and terms behind `priceSourceByKey`.
     * @dev Keys rather than rows, for the encoder's sake; a reader multicalls
     * the rows and drops `enabled == false` and rows of a disabled asset.
     */
    function priceSourceKeysFor(bytes32 assetId) external view returns (bytes32[] memory) {
        return _sourceKeysFor[assetId];
    }

    /// @notice Every price-source key ever written, in write order.
    function priceSourceKeys() external view returns (bytes32[] memory) {
        return _sourceKeys;
    }

    /// @notice One DEX protocol deployment, or a zeroed entry if the chain has none.
    function dexProtocolOf(bytes32 chainRef, bytes32 protocolId) external view returns (DexProtocolEntry memory) {
        return _protocols[chainRef][protocolId];
    }

    /// @notice Every DEX protocol id ever written for a chain — what can be
    ///         read there, behind `dexProtocolOf`. Replaces a literal set of
    ///         chain ids in the oracle.
    function dexProtocolIdsOn(bytes32 chainRef) external view returns (bytes32[] memory) {
        return _protocolIdsOn[chainRef];
    }

    // ------------------------------------------------------------------ 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.

    /// @dev The local `admin` first — the same address this contract's own
    /// configuration gate accepts ahead of the registrar quorum — then the
    /// plane rule. Zero once sealed, and `msg.sender` can never be zero, so the
    /// leg closes with the window it belongs to.
    function _requireSweepAuthority() internal view override {
        if (admin != address(0) && msg.sender == admin) return;
        super._requireSweepAuthority();
    }

    /// @dev The local admin, and the proven authority that called. The registry's
    /// bootstrap admin is not named here because this contract answers to its
    /// own admin during the window and to the registrar roster after it.
    function _sweepDestinations() internal view override returns (address, address) {
        return (admin, msg.sender);
    }
}

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/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 grant 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.
    uint8 public constant TREE_COUNT = 8;

    /// @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 grant 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) {
            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 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/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": "trees_",
        "type": "address",
        "internalType": "contract FinalStateTrees"
      },
      {
        "name": "admin_",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "ACTION_CONFIGURE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "CAIP_NAMESPACE_EIP155",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "CHAIN_ROLE_FULL",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "CHAIN_ROLE_OBSERVED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "FINALITY_CONFIRMATIONS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "FINALITY_L2_SETTLED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "FINALITY_TAG_FINALIZED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "GAS_MODEL_EIP1559",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "GAS_MODEL_L2_WITH_L1_FEE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "GAS_MODEL_LEGACY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "LEVERAGE_CAP_MAX_PCT",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "LEVERAGE_CAP_MIN_PCT",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PRICE_CADENCE_DEFAULT_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PRICE_CADENCE_FAST_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PRICE_MAX_AGE_DEFAULT_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PRICE_MAX_AGE_FAST_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_CURVE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_UNISWAP_V2",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_UNISWAP_V3",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_UNISWAP_V4",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_VELODROME",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "VM_EVM",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "VM_MOVE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "VM_SVM",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "admin",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "allowlistKeyForAsset",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForAssetOnChain",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForChain",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForChainTerms",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForPolicy",
    "inputs": [
      {
        "name": "paramId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForProtocol",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "protocolId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForSource",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "venueId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "assetAt",
    "inputs": [
      {
        "name": "i",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.AssetEntry",
        "components": [
          {
            "name": "originChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "originToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "decimals",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "name",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "symbol",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "priceCadenceMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "maxAgeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "assetCount",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "assetIdFor",
    "inputs": [
      {
        "name": "originChainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "originToken",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "assetLeafHash",
    "inputs": [
      {
        "name": "a",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.AssetEntry",
        "components": [
          {
            "name": "originChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "originToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "decimals",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "name",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "symbol",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "priceCadenceMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "maxAgeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "assetOf",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.AssetEntry",
        "components": [
          {
            "name": "originChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "originToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "decimals",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "name",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "symbol",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "priceCadenceMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "maxAgeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "assetOnChain",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.AssetChainEntry",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "maxLeveragePct",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "token",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "morph",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "vAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "assetRegistryRoot",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "assetsFor",
    "inputs": [
      {
        "name": "use",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "out",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.AssetEntry[]",
        "components": [
          {
            "name": "originChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "originToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "decimals",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "name",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "symbol",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "priceCadenceMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "maxAgeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "assetsOnChainFor",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "use",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "out",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.AssetChainEntry[]",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "maxLeveragePct",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "token",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "morph",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "vAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "chainAt",
    "inputs": [
      {
        "name": "i",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.ChainEntry",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "caipNamespace",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "caipReference",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "settlement",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "accountSpace",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "nativeAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "wrappedNative",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "finalityKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "finalityParam",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "blockTimeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "gasReadBlocks",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "gasHistoryBlocks",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "multicall",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "gasModel",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "l1ChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "gateway",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "vmKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "startHeight",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "role",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "paymaster",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "chainCount",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "chainLeafHash",
    "inputs": [
      {
        "name": "c",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.ChainEntry",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "caipNamespace",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "caipReference",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "settlement",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "accountSpace",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "nativeAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "wrappedNative",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "finalityKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "finalityParam",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "blockTimeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "gasReadBlocks",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "gasHistoryBlocks",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "multicall",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "gasModel",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "l1ChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "gateway",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "vmKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "startHeight",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "role",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "paymaster",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "chainOf",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.ChainEntry",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "caipNamespace",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "caipReference",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "settlement",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "accountSpace",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "nativeAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "wrappedNative",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "finalityKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "finalityParam",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "blockTimeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "gasReadBlocks",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "gasHistoryBlocks",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "multicall",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "gasModel",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "l1ChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "gateway",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "vmKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "startHeight",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "role",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "paymaster",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "chainRefFor",
    "inputs": [
      {
        "name": "namespace",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "caipRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "chainRegistryRoot",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "chainTermsOf",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.ChainTermsEntry",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "defaultLane",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "lanes",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "quoteWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "floatPremiumBps",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "admissionFloorUsdMicros",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "maxLegsPerIntent",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "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": "dexProtocolIdsOn",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "dexProtocolOf",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "protocolId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.DexProtocolEntry",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "factory",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoter",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "router",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "positionManager",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "enabledChainRefs",
    "inputs": [],
    "outputs": [
      {
        "name": "out",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "evmChainRef",
    "inputs": [
      {
        "name": "chainId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "leverageCapPct",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "mutate",
    "inputs": [
      {
        "name": "chainUpdates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.ChainEntry[]",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "caipNamespace",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "caipReference",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "settlement",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "accountSpace",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "nativeAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "wrappedNative",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "finalityKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "finalityParam",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "blockTimeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "gasReadBlocks",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "gasHistoryBlocks",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "multicall",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "gasModel",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "l1ChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "gateway",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "vmKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "startHeight",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "role",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "paymaster",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ]
      },
      {
        "name": "assetUpdates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.AssetEntry[]",
        "components": [
          {
            "name": "originChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "originToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "decimals",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "name",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "symbol",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "priceCadenceMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "maxAgeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "name": "assetChainUpdates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.AssetChainEntry[]",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "maxLeveragePct",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "token",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "morph",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "vAsset",
            "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": "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": "policyAt",
    "inputs": [
      {
        "name": "i",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.PolicyEntry",
        "components": [
          {
            "name": "paramId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "value",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "policyCount",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "policyOf",
    "inputs": [
      {
        "name": "paramId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.PolicyEntry",
        "components": [
          {
            "name": "paramId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "value",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "priceSourceByKey",
    "inputs": [
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.PriceSourceEntry",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "venue",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "symbol",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "weight",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "baseToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "baseIsToken0",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "twapWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "twapMinWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "minLiquidity",
            "type": "uint128",
            "internalType": "uint128"
          },
          {
            "name": "maxSpotDeviationBps",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "priceSourceKeys",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "priceSourceKeysFor",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "priceSourceOf",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "venueId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.PriceSourceEntry",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "venue",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "symbol",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "weight",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "baseToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "baseIsToken0",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "twapWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "twapMinWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "minLiquidity",
            "type": "uint128",
            "internalType": "uint128"
          },
          {
            "name": "maxSpotDeviationBps",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "publisherRole",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registry",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registryEpoch",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registryRootKey",
    "inputs": [
      {
        "name": "which",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "seal",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "seedEpoch",
    "inputs": [
      {
        "name": "epoch_",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "setChainTerms",
    "inputs": [
      {
        "name": "updates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.ChainTermsEntry[]",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "defaultLane",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "lanes",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "quoteWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "floatPremiumBps",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "admissionFloorUsdMicros",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "maxLegsPerIntent",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "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": "setPolicy",
    "inputs": [
      {
        "name": "updates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.PolicyEntry[]",
        "components": [
          {
            "name": "paramId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "value",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "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": "setSources",
    "inputs": [
      {
        "name": "protocolUpdates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.DexProtocolEntry[]",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "factory",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoter",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "router",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "positionManager",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "name": "sourceUpdates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.PriceSourceEntry[]",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "venue",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "symbol",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "weight",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "baseToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "baseIsToken0",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "twapWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "twapMinWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "minLiquidity",
            "type": "uint128",
            "internalType": "uint128"
          },
          {
            "name": "maxSpotDeviationBps",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "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": "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": "trees",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalStateTrees"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "event",
    "name": "AssetChainSet",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "uses",
        "type": "uint8",
        "indexed": false,
        "internalType": "uint8"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "AssetSet",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "uses",
        "type": "uint8",
        "indexed": false,
        "internalType": "uint8"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "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": "ChainSet",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "ChainTermsSet",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "defaultLane",
        "type": "uint8",
        "indexed": false,
        "internalType": "uint8"
      },
      {
        "name": "lanes",
        "type": "uint16",
        "indexed": false,
        "internalType": "uint16"
      },
      {
        "name": "quoteWindowSeconds",
        "type": "uint32",
        "indexed": false,
        "internalType": "uint32"
      },
      {
        "name": "floatPremiumBps",
        "type": "uint16",
        "indexed": false,
        "internalType": "uint16"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "DexProtocolSet",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "protocolId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "EpochSeeded",
    "inputs": [
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "PolicySet",
    "inputs": [
      {
        "name": "paramId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "value",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "PriceSourceSet",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "venueId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "RegistryConfigured",
    "inputs": [
      {
        "name": "role",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "threshold",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "RootsPublished",
    "inputs": [
      {
        "name": "chainRoot",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      },
      {
        "name": "assetRoot",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "Sealed",
    "inputs": [],
    "anonymous": false
  },
  {
    "type": "error",
    "name": "AlreadySealed",
    "inputs": []
  },
  {
    "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": "AssetNotRegistered",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "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": "ChainNotRegistered",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "ChainRefMismatch",
    "inputs": [
      {
        "name": "claimed",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "derived",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "EmptyBatch",
    "inputs": []
  },
  {
    "type": "error",
    "name": "IncompleteChainDescription",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidChainRole",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "role",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidDexProtocol",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "protocolId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidPriceSource",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "venueId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "LaneNotAvailable",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "defaultLane",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "lanes",
        "type": "uint16",
        "internalType": "uint16"
      }
    ]
  },
  {
    "type": "error",
    "name": "LeverageCapOutOfRange",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "maxLeveragePct",
        "type": "uint16",
        "internalType": "uint16"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAdmin",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAnEvmAddress",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "field",
        "type": "string",
        "internalType": "string"
      },
      {
        "name": "word",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotConfigured",
    "inputs": []
  },
  {
    "type": "error",
    "name": "NotFresh",
    "inputs": []
  },
  {
    "type": "error",
    "name": "PrecompileUnavailable",
    "inputs": [
      {
        "name": "precompile",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SettlementRequired",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "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": "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": "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": "asked",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "UnknownAsset",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "UnknownChain",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "UseBitOutOfScope",
    "inputs": [
      {
        "name": "uses",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "VmKindMismatch",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "vmKind",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "WrongAlgorithm",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "got",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "required",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  }
]

read contract

bytecode · 31,138 bytes

0x610120806040526004361015610013575f80fd5b5f610100525f3560e01c9081630292709e146156ad57508063036f9a431461568f5780630489d038146155d1578063087a5cf41461555e57806308f0ce6214613a145780630900767d14615545578063150b7a02146154ef5780631591c4a014614ecd578063178bcc9314614e865780631945098e146106495780631a633473146105295780631e000aa514614d84578063200d43ad146140de57806320d4f69614610529578063210de79d1461408f5780632b89089c146140705780633ec4cc34146140255780633fb27b8514613fb657806342cde4e814613f965780634760fd4314613f2a5780634bcb06a914613f115780634ec824ff14613a145780635483a0b514613ec957806357d7064814613eab5780635b88f50e14613a145780635eb320b814613e8b5780635f61b15c14613a1457806360a1800814613e5757806360c29fac14610649578063622c3f2114613e3857806365cea49c14613e1a57806368cf465e146106495780636c2c44e214613e015780636db8175214613de1578063711f3db414613cff578063767c39d2146105295780637b10399914613cb85780637bb737bc14613c9f5780637f49d69f14613c75578063831f33a114613a555780638513e2d514613a37578063852a52e814613a1957806388f06eaa14613a1457806392a2e0b3146139f65780639377c220146139cd5780639493ba1d1461395a57806394bc4e961461364d57806395102c421461362e5780639648eca21461336e57806396f51f3a146130b957806397b9857b1461304d578063a741113c14613013578063aa9239f514612fb7578063ac65bc7014612e8e578063affed0e014612e65578063b19f480514612e28578063b8da302a14612e08578063b9a7f07614612d1f578063b9ed468614610c83578063bbbcda4414610c65578063bc197c8114610bcd578063bda1019e14610bad578063be380c8514610a2c578063c5dbda6614610a0d578063dc544a09146109ec578063de54d429146109cc578063df46c71314610866578063e02e1bfd14610846578063e597e6591461064e578063e70475a714610649578063e94a52af1461062b578063eafe7a741461060b578063eb7773821461052e578063ee076a5014610529578063ee57e33c14610494578063f23a6e611461043e578063f3178f43146103db578063f4e885db146103bb5763f851a44014610387575f80fd5b346103b457610100513660031901126103b45761010051546040516001600160a01b039091168152602090f35b6101005180fd5b346103b457610100513660031901126103b4576020600154604051908152f35b346103b45760403660031901126103b4576103f4615b6f565b6004356101005152600a60205261041a6001600160401b03604061010051209216616914565b6101005152602052602061ffff600260406101005120015460501c16604051908152f35b346103b45760a03660031901126103b457610457615cab565b50610460615cc1565b506084356001600160401b0381116103b457610480903690600401615cd7565b505060405163f23a6e6160e01b8152602090f35b346103b457610100513660031901126103b457604051806020601154918281520190601161010051527f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6890610100515b8181106105135761050f856104fb81870382615eef565b604051918291602083526020830190615c5d565b0390f35b82548452602090930192600192830192016104e4565b615d50565b346103b457610100513660031901126103b45760045461054d816160d4565b610100519091815b8181106105a9575050610567816160d4565b91610100515b82811061058a576040516020808252819061050f90820187615c5d565b80610597600192846161a2565b516105a282876161a2565b520161056d565b806105b5600192616106565b90549060031b1c6101005152600560205260ff600c60406101005120015460081c166105e2575b01610555565b6105eb81616106565b90549060031b1c6106056105fe866162d7565b95876161a2565b526105dc565b346103b457610100513660031901126103b4576020600754604051908152f35b346103b457610100513660031901126103b457602060405160058152f35b615d35565b346103b45760403660031901126103b45760243560043560ff821682036103b45760075461067b816169fb565b6101005190939092835b838110610722578585610697816169fb565b91610100515b8281106106f85783604051809160208201602083528151809152602060408401920190610100515b8181106106d3575050500390f35b919350916020610120826106ea6001948851615bf5565b0194019101918493926106c5565b80610705600192846161a2565b5161071082876161a2565b5261071b81866161a2565b500161069d565b61072b81616132565b90549060031b1c806101005152600860205260ff600560406101005120015460481c161561083d576101005152600a60205260406101005120826101005152602052604061010051206040519061078182615e7f565b805482526001810154602083015284600282015460ff81166040850152600560ff8260081c161515938460608701526001600160401b038360101c16608087015261ffff8360501c1660a0870152600381015460c0870152600481015460e087015201546101008501528261082e575b5050610803575b506001905b01610685565b949061082782610815600194916162d7565b97610820828b6161a2565b52886161a2565b50906107f8565b60ff92501616151584896107f1565b506001906107fd565b346103b457610100513660031901126103b4576020600454604051908152f35b346103b45760203660031901126103b45761087f615d85565b6007549061088c826169ac565b6101005190929091825b82811061094a575050506108a9816169ac565b91610100515b82811061092057836040518091602082016020835281518091526040830190602060408260051b86010193019161010051905b8282106108f157505050500390f35b919360019193955060206109108192603f198a82030186528851615dc7565b96019201920185949391926108e2565b8061092d600192846161a2565b5161093882876161a2565b5261094381866161a2565b50016108af565b61095381616132565b90549060031b1c610100515260086020526109736040610100512061672f565b6101008101511515806109ba575b61098f575b50600101610896565b93906109b3826109a1600194916162d7565b966109ac828a6161a2565b52876161a2565b5090610986565b5060ff8360a083015116161515610981565b346103b457610100513660031901126103b4576020600d54604051908152f35b346103b4576020610a056109ff36615bdf565b90616966565b604051908152f35b346103b457610100513660031901126103b45760206040516101f48152f35b346103b4576102c03660031901126103b457604051610a4a81615e9b565b600435815260243560208201526044356040820152606435606082015260843560ff811681036103b457608082015260a43560a082015260c43560c082015260e43560ff811681036103b45760e0820152610104356001600160401b03811681036103b4576101008201526101243563ffffffff811681036103b4576101208201526101443561ffff811681036103b4576101408201526101643561ffff811681036103b457610160820152610184356101808201526101a43560ff811681036103b4576101a08201526101c4356101c08201526101e4356101e08201526102043560ff811681036103b4576102008201526102243580151581036103b457610220820152610244356001600160401b03811681036103b457610240820152610264356001600160401b03811681036103b4576102608201526102843560ff811681036103b45781610a059161028060209401526102a4356102a08201526168a4565b346103b457610100513660031901126103b4576020601554604051908152f35b346103b45760a03660031901126103b457610be6615cab565b50610bef615cc1565b506044356001600160401b0381116103b457610c0f903690600401615baf565b50506064356001600160401b0381116103b457610c30903690600401615baf565b50506084356001600160401b0381116103b457610c51903690600401615cd7565b505060405163bc197c8160e01b8152602090f35b346103b45760203660031901126103b4576020610a05600435616871565b346103b45760a03660031901126103b4576001600160401b03600435116103b4573660236004350112156103b4576001600160401b0360043560040135116103b4573660246102c060043560040135026004350101116103b4576024356001600160401b0381116103b457610cfc903690600401615baf565b60c0526044356001600160401b0381116103b457610d1e903690600401615d04565b60643592906001600160401b03841684036103b4576084356001600160401b0381116103b457610d52903690600401615baf565b946002548015612d0a57600435600401351580612d00575b80612cf8575b612ce357600354926040519760a089016001600160401b03861660208b0152608060408b015260043560040135905260c08901602460043501610100515b600435600401358110612b50575050601f198a82030160608b015260c051815260208101602060c05160051b830101908992610100515b60c0518110612a4a5750505060209150601f198b82030160808c0152878152019888610100515b8881106129b3575050610e2f81610ed69798999a9b03601f198101835282615eef565b60208151910120610100515060405160208101915f5160206179425f395f51905f5283524660408301523060608301527fe10634eb0bf7bd6ad00dc59a6c67fe9b09037b979405fa59137e7e530d60141460808301526001600160401b03871660a083015260c082015260c08152610ea860e082615eef565b51902090600154927f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d540616a76565b506001600160401b03610eea81831661608b565b16906001600160401b03191617600355601754610f0f6001600160401b03821661608b565b60a0526001600160401b0360a05116906001600160401b03191617601755610f3f60c051600435600401356162af565b6001600160ff1b038216820361299957610f60610f71918360011b906162af565b610f69816160d4565b60e0526160d4565b6101005192909190835b600435600401358510156118a957610fa46004356102c087020160648101359060440135616966565b60246102c087026004350101350361186757610fcb6102446102c0870260043501016162bc565b806117da575b6117b557600160ff610fee6102a46102c0890260043501016162c9565b1611611776576110096102446102c0870260043501016162bc565b80611757575b80611743575b61171e576004356102c0860201604401355f5160206179225f395f51905f521480806116fc575b6116bd57611676575b6101e46102c0860260043501013515158061164c575b6116265760246102c086026004350101356101005152600660205260ff60406101005120541615611593575b61010080516102c08702600435016024810135918290526005602052915160409020908155604482013560018201556064820135600282019081556084830135600383015590916110da9060a4016162c9565b6004838101805460ff191660ff939093169290921790915560c490356102c089020190810135600584015560e4810135600684015561111c90610104016162c9565b60ff1660ff196007840154161760078301556102c0870260043501610124016111449061685d565b6007830190611171919068ffffffffffffffff0082549160081b169068ffffffffffffffff001916179055565b6111866101446004356102c08a0201016162f4565b600783015461ffff60681b6111a66101646004356102c08d0201016162e5565b60681b169061ffff60781b6111c66101846004356102c08e0201016162e5565b60781b169260481b6cffffffff000000000000000000169067ffffffffffffffff60481b191617171760078301556102c08702600435016101a4013560088301556102c08702600435016101c40161121d906162c9565b60098301805460ff191660ff929092169190911790556101e46004356102c089020190810135600a840155610204810135600b84015561126090610224016162c9565b60ff1660ff19600c8401541617600c8301556102c087026004350161024401611288906162bc565b600c8301805460a05169ffffffffffffffff000060109190911b1692151560081b61ff001669ffffffffffffffffff0019909116179190911790556112d86102846004356102c08a02010161685d565b600c8301805467ffffffffffffffff60501b191660509290921b67ffffffffffffffff60501b1691909117905561131a6102a46004356102c08a0201016162c9565b600c8301805460ff60901b609084901b1660ff60901b198216179091556102c08902600435016102c4810135600d86015590929061135b90602401356161e9565b8560e05190611369916161a2565b52835493600181015491546003820154600483015460ff166005840154600685015490600786015492600887015494600988015460ff1696600a89015498600b015499604051809e602082015f5160206179625f395f51905f52905261010051604083015260608201526080015260a08d015260c08c015260e08b01526101008a015261012089015260ff81166101408901528060081c6001600160401b03166101608901528060481c63ffffffff166101808901528060681c61ffff166101a089015260781c61ffff166101c08801526101e087015261020086015261022085015261024084015260ff821661026084015260ff60901b8160901b1660ff60901b1983161760081c60ff16151561028084015260ff60901b8160901b1660ff60901b1983161760101c6001600160401b03166102a084015260ff60901b8160901b1660ff60901b1983161760501c6001600160401b03166102c084015260ff60901b9060901b169060ff60901b19161760901c60ff166102e08201526102c08602600435016102c40135610300820152610300815261150b61032082615eef565b8051906020012061151c82866161a2565b52611526906162d7565b9361153c6102446004356102c0840201016162bc565b6040805160a05192151581526001600160401b0390921660208301526102c083026004350160240135917f523b72ac5fbe8982a57ea63c8ca4daf1602c22eda42d6cc14a884c0310698b809190a260010193610f7b565b60246102c086026004350101356101005152600660205260406101005120600160ff19825416179055600454600160401b81101561160c576115e08160016116059301600455600461614a565b60246102c089949394026004350101359083549060031b91821b915f19901b19161790565b9055611087565b634e487b7160e01b61010051526041600452602461010051fd5b6101e46102c086633f4b99b160e21b610100515202600435010135600452602461010051fd5b506101e46102c086026004350101356101005152600660205260ff6040610100512054161561105b565b604080516116b8916116889082615eef565b60098152683830bcb6b0b9ba32b960b91b60208201526004356102c08802016102c4810135919060240135617108565b611045565b60ff8660246102c06116d861022482850260043501016162c9565b92631e9a7d7560e01b61010051520260043501013560045216602452604461010051fd5b50600160ff6117166102246102c08a0260043501016162c9565b16141561103c565b60246102c086632eb72ead60e11b610100515202600435010135600452602461010051fd5b5060846102c0860260043501013515611015565b5060ff61176f6102a46102c0880260043501016162c9565b161561100f565b60ff8560246102c06117916102a482850260043501016162c9565b92631a7ce1dd60e01b61010051520260043501013560045216602452604461010051fd5b60246102c086633dc2fc1160e01b610100515202600435010135600452602461010051fd5b5063ffffffff6117f56101446102c0880260043501016162f4565b16158015611848575b8015611829575b80610fd1575060ff6118226102246102c0880260043501016162c9565b1615610fd1565b5060ff6118416101c46102c0880260043501016162c9565b1615611805565b5060ff6118606101046102c0880260043501016162c9565b16156117fe565b8460246102c06118866004358285020160648101359060440135616966565b92631a35ac9960e01b610100515202600435010135600452602452604461010051fd5b929085610100515b60c051811015611e8e57600581901b8301353684900361013e19018112156103b4576118e09036908501615f9d565b608052608051516101005152600660205260ff60406101005120541615611e725760a0608051015160ec8116611e56575063ffffffff60c060805101511615611e24575b63ffffffff60e060805101511615611df1575b6001600160401b0360a05116610120608051015261195f608051516020608051015190616514565b806101005152600960205260ff60406101005120541615611d92575b80610100515260086020526040610100512060805151815560206080510151600182015560ff604060805101511660ff60028301911660ff1982541617905560038101606060805101518051906001600160401b03821161160c5781906119e28454616657565b601f8111611d2a575b506020906001601f841114611cc2576101005192611cb7575b50508160011b915f199060031b1c19161790555b6080805101518051906001600160401b03821161160c57611a3c6004840154616657565b601f8111611c57575b506020906001601f841114611bb9576001959493837fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f94611b71946005946101005192611bae575b50505f19600383901b1c191690881b1760048201555b60805160a081015192909101805460c083015160e0840151610100850180516101209096015167ffffffffffffffff60501b60509190911b1669ff00000000000000000096151560481b9690961671ffffffffffffffffffffffffffffffffffff1990941660ff9097169690961760089290921b64ffffffff00169190911768ffffffff000000000060289290921b91909116171791909117905599611b4884616871565b611b548260e0516161a2565b52611b6160805185617150565b611b6b828b6161a2565b526162d7565b60805160a0908101519a5160408051925160ff909d16835290151560208301526001600160401b03909b169a81019a909a5298606090a2016118b1565b015190508e80611a8d565b9060048401610100515280610100512091610100515b601f1985168110611c3f575093600184611b7194600594839a99987fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f98601f19811610611c27575b505050811b016004820155611aa3565b01515f1960f88460031b161c191690558e8080611c17565b91926020600181928685015181550194019201611bcf565b82811115611a455760048401610100515260206101005120601f840160051c60208510611cad575b610100515b81601f850160051c038110611c9b57505050611a45565b61010051838301820155600101611c84565b5061010051611c7f565b015190508b80611a04565b6101008051869052518281209350601f198516905b818110611d125750908460019594939210611cfa575b505050811b019055611a18565b01515f1960f88460031b161c191690558b8080611ced565b92936020600181928786015181550195019301611cd7565b828111156119eb5790915083610100515260206101005120601f840160051c60208510611d88575b9084939291610100515b81601f850160051c038110611d73575050506119eb565b61010051838301820155869550600101611d5c565b5061010051611d52565b806101005152600960205260406101005120600160ff19825416179055600754600160401b81101561160c57611dea611dd4826001859401600755600761614a565b819391549060031b91821b915f19901b19161790565b905561197b565b60805160a0015160021615611e165763ffffffff610bb85b1660e06080510152611937565b63ffffffff6201d4c0611e09565b60805160a0015160021615611e495763ffffffff6103e85b1660c06080510152611924565b63ffffffff612710611e3c565b60ff906376cbaf6d60e01b610100515216600452602461010051fd5b60805151633f4b99b160e21b6101005152600452602461010051fd5b83868387610100515b8181106123db57505060e0518290525081527f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b031690813b156103b4576040519063abf1570d60e01b82528180611eff610100519360e051600484016161b6565b038161010051865af180156121e2576123c0575b50600454611f20816160d4565b6101005191825b8181106122515784611f3985856176fb565b80601555600754611f49816160d4565b6101005191825b8181106121f057505090611f63916176fb565b918260165560609260405190611f798583615eef565b60028252601f198501938436602085013760405194611f988787615eef565b60028652366020870137604080517fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f602082019081526101005182840152918152611fe38882615eef565b519020611fef84616185565b52604051602081019161010051507f205e4a5b42cbb22c56a771766b971165a627b616bd23460c8e002faad102d0918352610100516040830152878201526001600160401b0360a0511660808201526080815261204d60a082615eef565b51902061205985616185565b52604080517fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f602082019081526001828401529181526120998782615eef565b5190206120a583616192565b52604051602081019161010051507f205e4a5b42cbb22c56a771766b971165a627b616bd23460c8e002faad102d091835260016040830152868201526001600160401b0360a0511660808201526080815261210160a082615eef565b51902061210d84616192565b52813b156103b45760405163abf1570d60e01b815260056004820152600160248201526080604482015292839161215f919061214d906084850190615c5d565b83810360031901606485015290615c5d565b9181806101005194039161010051905af180156121e2576121c7575b7ff95ac4707234b4ef5620ff790985a37679e4c7e0b0c603f110ae9bcc65b0cf5c8260155460165460405191825260208201526001600160401b0360a051166040820152a16101005180f35b610100516121d491615eef565b610100516103b4578161217b565b6040513d61010051823e3d90fd5b806121fc600192616132565b90549060031b1c6101005152600860205261221c6040610100512061672f565b6101008101511561224b57612230906167c9565b61224361223c876162d7565b96866161a2565b525b01611f50565b50612245565b8061225d600192616106565b90549060031b1c61010051526005602052604061010051206040519061228282615e9b565b80548252838101546020830152600281015460408301526003810154606083015260ff6004820154166080830152600581015460a0830152600681015460c083015261ffff600782015460ff811660e08501526001600160401b038160081c1661010085015263ffffffff8160481c16610120850152818160681c1661014085015260781c16610160830152600881015461018083015260ff6009820154166101a0830152600a8101546101c0830152600b8101546101e0830152600d600c8201549160ff831661020085015260ff808460081c16159384156102208701526001600160401b038160101c166102408701526001600160401b038160501c1661026087015260901c1661028085015201546102a08301526123ba576123a6906168a4565b6123b261223c876162d7565b525b01611f27565b506123b4565b610100516123cd91615eef565b610100516103b45781611f13565b6123e681838561629e565b93610120853603126103b457604051946123ff86615e7f565b8035865261010060208201359182602089015261241e60408201615d95565b604089015261242f60608201615f90565b606089015261244060808201615b9b565b608089015261245160a0820161607c565b60a089015260c081013560c089015260e081013560e089015201356101008701526101005152600660205260ff6040610100512054161561297c5784516101005152600960205260ff6040610100512054161561296257604085015160f38116611e56575061ffff60a0860151168015158061294d575b61292657506126af906020860151610100515260056020525f5160206179225f395f51905f52600160406101005120015414612885575b6001600160401b0360a05116608087015285516101005152600a60205260406101005120602087015161010051526020526125f26040610100512087518082556020890151918260018201556002810160ff8060408d0151161660ff1982541617815561258560608c01511515829061ff00825491151560081b169061ff001916179055565b60808b0151815469ffffffffffffffff0000191660109190911b69ffffffffffffffff00001617815560a08b0151815461ffff60501b191660509190911b61ffff60501b1617905560c08a0151600382015560e08a015160048201556101008a0151600590910155616534565b6125fe8260e0516161a2565b528551602087015160ff60408901511688606081015115156001600160401b0360808301511661ffff60a0840151169060c08401519261010060e0860151950151956040519760208901995f5160206179625f395f51905f528b52600260408b015260608a0152608089015260a088015260c087015260e086015261010085015261012084015261014083015261016082015261016081526126a261018082615eef565b519020611b6b82896161a2565b9380516020820151907ffdf8afb51a6b8e68fa9d9946922368e4a2e54a69963cf7bcfcdff38235916e7960ff6040850151166060850151151561271c60405192839260a05191849160409194936001600160401b039160ff60608601971685521515602085015216910152565b0390a36060810151151580612875575b61273a575b50600101611e97565b80516101005152600860205260406101005120600581019081546103e863ffffffff8260081c161190811561285f575b50612777575b5050612731565b815460a05171ffffffffffffffff00ffffffffffffffff001990911660509190911b67ffffffffffffffff60501b1617660bb8000003e8001782559195600193927fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f9161285491612819919061280f906127f18c51616871565b6127fd8460e0516161a2565b526128098c519161672f565b90617150565b611b6b828d6161a2565b975192546040805160a05160ff808516835260489490941c909316151560208201526001600160401b03909216908201529081906060820190565b0390a2908680612770565b610bb8915063ffffffff9060281c16118961276a565b506004604082015116151561272c565b61292160208701516128bc60c08901516040928351906128a58583615eef565b60058252643a37b5b2b760d91b6020830152617108565b602088015160e089015182516128ed926128d68583615eef565b60058252640dadee4e0d60db1b6020830152617108565b6020880151610100890151825190929091906129099083615eef565b60068252651d905cdcd95d60d21b6020830152617108565b6124ff565b8560208151910151906368adc18560e01b6101005152600452602452604452606461010051fd5b5060648110806124c857506101f481116124c8565b84516326ef68cf60e01b6101005152600452602461010051fd5b6020850151633f4b99b160e21b6101005152600452602461010051fd5b634e487b7160e01b61010051526011600452602461010051fd5b909a610120806001926101008f803583526020810135602084015260ff6129dc60408301615d95565b1660408401526129ee60608201615f90565b151560608401526001600160401b03612a0960808301615b9b565b16608084015261ffff612a1e60a0830161607c565b1660a084015260c081013560c084015260e081013560e08401520135610100820152019c019101610e0c565b909192601f1983820301845261013e198c360301853512156103b4576020806001928e8835019081358152828201358382015260ff612a8b60408401615d95565b1660408201526101206001600160401b03612b3f82612ae1612ac6612ab3606089018961656e565b61014060608a015261014089019161659f565b612ad3608089018961656e565b9088830360808a015261659f565b9560ff612af060a08301615d95565b1660a087015263ffffffff612b0760c08301615f7f565b1660c087015263ffffffff612b1e60e08301615f7f565b1660e0870152612b316101008201615f90565b151561010087015201615b9b565b169101529601959401929101610de5565b90916102c0806001928535815260208601356020820152604086013560408201526060860135606082015260ff612b8960808801615d95565b16608082015260a086013560a082015260c086013560c082015260ff612bb160e08801615d95565b1660e08201526001600160401b03612bcc6101008801615b9b565b1661010082015263ffffffff612be56101208801615f7f565b1661012082015261ffff612bfc610140880161607c565b1661014082015261ffff612c13610160880161607c565b1661016082015261018086013561018082015260ff612c356101a08801615d95565b166101a08201526101c08601356101c08201526101e08601356101e082015260ff612c636102008801615d95565b16610200820152612c776102208701615f90565b15156102208201526001600160401b03612c946102408801615b9b565b166102408201526001600160401b03612cb06102608801615b9b565b1661026082015260ff612cc66102808801615d95565b166102808201526102a08601356102a08201520193019101610dae565b63c2e5347d60e01b6101005152600461010051fd5b508315610d70565b5060c05115610d6a565b63d311bc3960e01b6101005152600461010051fd5b346103b45760203660031901126103b457600435612d3b616464565b50600d54811015612dee57600d610100515260206101005120016101005150546101005160031b1c6101005152600b60205261050f604061010051206001600160401b03600260405192612d8e84615e64565b8054845260018101546020850152015460ff81161515604084015260081c1660608201526040519182918291909160606001600160401b038160808401958051855260208101516020860152604081015115156040860152015116910152565b634e487b7160e01b61010051526032600452602461010051fd5b346103b457610100513660031901126103b45760206040516201d4c08152f35b346103b457610100513660031901126103b45760206040517f3d07e5a0f372bb3f92469df7a6dcb4a49539b8707a06a699d3727c07dbbb3a4b8152f35b346103b457610100513660031901126103b45760206001600160401b0360035416604051908152f35b346103b45760203660031901126103b457612ea7616221565b506004356101005152600e6020526101005160409020604051612ec981615e7f565b81549182825260010154906020810160ff83168152604082018360081c61ffff168152606083018460181c63ffffffff16815260808401908560381c61ffff16825260a08501928660481c6001600160401b0316845260c08601948760881c61ffff16865260e08701968860981c60ff1615158852610100019760a01c6001600160401b031688526040519889525160ff1660208901525161ffff1660408801525163ffffffff1660608701525161ffff166080860152516001600160401b031660a08501525161ffff1660c084015251151560e0830152516001600160401b031661010082015261012090f35b346103b45760203660031901126103b457612fd061660c565b50612fdc600435616132565b90549060031b1c6101005152600860205261050f612fff6040610100512061672f565b604051918291602083526020830190615dc7565b346103b45760203660031901126103b4576004356001600160401b0381116103b457610a056130486020923690600401615f9d565b6167c9565b346103b45760203660031901126103b45760043561306961660c565b50806101005152600960205260ff604061010051205416156130a1576101005152600860205261050f612fff6040610100512061672f565b63082cdf1f60e11b6101005152600452602461010051fd5b346103b45760a03660031901126103b45760043560048110156103b4576130de615cc1565b90606435906084356001600160a01b03811691604435918381036103b45761310461703c565b61010051548415906001600160a01b03168115613353575b5061333a5761312c8387846164c8565b945f1981036133355750845b80958115613320578082116132f55750613151836165bf565b826131c85750506101005180808087875af161316b6165dd565b50156131ac575f5160206179825f395f51905f5260406020965b61318e846165bf565b81519485528785018790526001600160a01b031693a4604051908152f35b6365f4a9ef60e11b610100515261010051600452602461010051fd5b60209691906131d6846165bf565b6001840361323d57506040805163a9059cbb60e01b898201526001600160a01b039092166024830152604482018790525f5160206179825f395f51905f529290916132389061323281606481015b03601f198101835282615eef565b82617068565b613185565b919591905061324b836165bf565b6002830361329a5750505f5160206179825f395f51905f52604060019561323882516323b872dd60e01b8a82015230602482015287604482015286606482015260648152613232608482615eef565b5f5160206179825f395f51905f529195613238604092835190637921219560e11b8b830152306024830152886044830152876064830152608482015260a060a48201526101005160c482015260c4815261323260e482615eef565b6101008051632190968160e01b90526001600160a01b03891660045260249290925260445251606490fd5b637c2e506f60e11b6101005152600461010051fd5b613138565b836315150d4d60e31b6101005152600452602461010051fd5b905084141580613364575b8761311c565b503384141561335e565b346103b45760203660031901126103b45760043561338a616305565b50806101005152600660205260ff60406101005120541615613616576101005152600560205261050f60406101005120600d604051916133c983615e9b565b8054835260018101546020840152600281015460408401526003810154606084015260ff6004820154166080840152600581015460a0840152600681015460c084015261ffff600782015460ff811660e08601526001600160401b038160081c1661010086015263ffffffff8160481c16610120860152818160681c1661014086015260781c16610160840152600881015461018084015260ff6009820154166101a0840152600a8101546101c0840152600b8101546101e084015260ff600c820154818116610200860152818160081c1615156102208601526001600160401b038160101c166102408601526001600160401b038160501c1661026086015260901c1661028084015201546102a0820152604051918291829190916102a0806102c08301948051845260208101516020850152604081015160408501526060810151606085015260ff608082015116608085015260a081015160a085015260c081015160c085015260ff60e08201511660e08501526001600160401b036101008201511661010085015263ffffffff6101208201511661012085015261ffff6101408201511661014085015261ffff6101608201511661016085015261018081015161018085015260ff6101a0820151166101a08501526101c08101516101c08501526101e08101516101e085015260ff6102008201511661020085015261022081015115156102208501526001600160401b03610240820151166102408501526001600160401b036102608201511661026085015260ff610280820151166102808501520151910152565b632a80afbd60e21b6101005152600452602461010051fd5b346103b457610100513660031901126103b4576020604051610bb88152f35b346103b45760803660031901126103b45760243560043561366c615b85565b6064356001600160401b0381116103b45761368b903690600401615baf565b61010051549091906001600160a01b031633036137a3575b505050816136eb575b807f40bd951b186ab08230e53414aeaafc99b557bb646ef875822965f0e5a8c32ca5926040926001558060025582519182526020820152a16101005180f35b60405163342f616360e01b8152600481018290526020816024817f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b03165afa9081156121e257610100519161376d575b5082811061375057506136ac565b9050633770da3360e11b6101005152600452602452604461010051fd5b90506020813d60201161379b575b8161378860209383615eef565b81010312613797575183613742565b5f80fd5b3d915061377b565b60408051602081018681528183018890529181527f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b03169291906137ef606082615eef565b519020823b156103b45790836001600160401b039593926040519687956322f3f44760e11b875260848701927f3d07e5a0f372bb3f92469df7a6dcb4a49539b8707a06a699d3727c07dbbb3a4b60048901526024880152166044860152608060648601525260a4830160a48560051b85010194826101005190607e19813603015b8383106138ba5750505050505081806101005194039161010051905af180156121e25761389f575b80806136a3565b610100516138ac91615eef565b610100516103b45782613898565b919395965091939660a3198982030186528735828112156103b457830180356001600160a01b03811692908390036103b4576139486020928260019585945260ff613906858401615d95565b168482015261393a61392f61391e604085018561656e565b60806040860152608085019161659f565b92606081019061656e565b91606081850391015261659f565b99019601930190918896959492613870565b346103b45760203660031901126103b457600435610100515260146020526040610100512060405190816020825491828152019161010051526020610100512090610100515b8181106139b75761050f856104fb81870382615eef565b82548452602090930192600192830192016139a0565b346103b457610100513660031901126103b45760206001600160401b0360175416604051908152f35b346103b457610100513660031901126103b457602060405160048152f35b615c90565b346103b457610100513660031901126103b457602060405160648152f35b346103b45760203660031901126103b4576020610a05600435616914565b346103b45760203660031901126103b457613a6e6163a3565b506004356101005152600f60205261050f604061010051206001600160401b03600c60405192613a9d84615eb7565b80548452600181015460208501526002810154604085015260ff600382015416606085015260048101546080850152600581015460a0850152600681015460c0850152600781015460e085015261ffff6008820154166101008501526009810154610120850152600a81015461014085015260ff600b820154818116151561016087015263ffffffff8160081c1661018087015263ffffffff8160281c166101a08701526001600160801b038160481c166101c087015261ffff8160c81c166101e087015260d81c161515610200850152015416610220820152604051918291829190916102206001600160401b038161024084019580518552602081015160208601526040810151604086015260ff60608201511660608601526080810151608086015260a081015160a086015260c081015160c086015260e081015160e086015261ffff61010082015116610100860152610120810151610120860152610140810151610140860152610160810151151561016086015263ffffffff6101808201511661018086015263ffffffff6101a0820151166101a08601526001600160801b036101c0820151166101c086015261ffff6101e0820151166101e08601526102008101511515610200860152015116910152565b346103b457610100513660031901126103b45760206040515f5160206179225f395f51905f528152f35b346103b4576020610a05613cb236615bdf565b90616534565b346103b457610100513660031901126103b4576040517f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b03168152602090f35b346103b45760203660031901126103b4576004356001600160401b038116908190036103b45761010051546001600160a01b03163303613dc8576017546001600160401b03811615801590613dbd575b8015613db2575b613d9d5767ffffffffffffffff191681176017556040519081527f1b0319a7c1b77359b02ef03a86a6e1677b3d55bc74afbe201986efaef1e8b4b590602090a16101005180f35b63dc63d81f60e01b6101005152600461010051fd5b506007541515613d56565b506004541515613d4f565b630bd4212160e11b610100515233600452602461010051fd5b346103b457610100513660031901126103b4576020604051610100518152f35b346103b4576020610a05613e1436615bdf565b90616514565b346103b45760203660031901126103b4576020610a056004356164e1565b346103b457610100513660031901126103b45760206040516127108152f35b346103b45760603660031901126103b45760043560048110156103b457610a05602091613e82615cc1565b604435916164c8565b346103b457610100513660031901126103b4576020601654604051908152f35b346103b45760203660031901126103b4576020610a05600435616488565b346103b45760203660031901126103b457613ee2616464565b506004356101005152600b60205261050f604061010051206001600160401b03600260405192612d8e84615e64565b346103b4576020610a05613f2436615d6b565b91616425565b346103b45760203660031901126103b4576020613f45615d85565b610100515060405160ff838201927fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f845216604082015260408152613f8b606082615eef565b519020604051908152f35b346103b457610100513660031901126103b4576020600254604051908152f35b346103b457610100513660031901126103b45761010051546001600160a01b0381163303613dc8576bffffffffffffffffffffffff60a01b1661010051557f1b2d71eb44f882534bf4e86f940c56ccc869ffb927e2bab86561de93950c22166101005161010051a16101005180f35b346103b45761404561403636615d6b565b9161403f6163a3565b50616425565b6101005152600f60205261050f604061010051206001600160401b03600c60405192613a9d84615eb7565b346103b457610100513660031901126103b45760206040516103e88152f35b346103b45760203660031901126103b4576140a8616305565b506140b4600435616106565b90549060031b1c6101005152600560205261050f60406101005120600d604051916133c983615e9b565b346103b45760803660031901126103b4576004356001600160401b0381116103b45761410e903690600401615d04565b6001600160401b03602435116103b4573660236024350112156103b45760243560040135906001600160401b0382116103b457366024610240840281350101116103b45761415a615b85565b6064356001600160401b0381116103b457614179903690600401615baf565b90916002548015612d0a57841580614d7c575b612ce357600354936001600160401b038516936040518760808201876020840152606060408401525260a081018a90610100515b8a8110614cee575060209150601f198382030160608401528a8152016024803501610100515b8b8110614baa5750509261428e9592826142166001600160401b039997946142889703601f198101835282615eef565b60208151910120610100515060405160208101915f5160206179425f395f51905f5283524660408301523060608301527f5596fa6720176cbfa2a11ee0d5be50e2016480c70c477d3273492663856b2d0b60808301528a871660a083015260c082015260c08152610ea860e082615eef565b5061608b565b16906001600160401b03191617600355601754906142b46001600160401b03831661608b565b67ffffffffffffffff199092166001600160401b038316176017556142d983826162af565b6142eb6142e5826160d4565b916160d4565b6101005190959092835b8181106148ce5750505061010051915b8483101561484157610100515060246102408402813501019283356101005152600960205260ff604061010051205416156148275761434384616cf7565b614360602435610240830201606481013590604401358635616425565b91826101005152601060205260ff604061010051205416156147c2575b6101008051849052600f602052516040902085358155602435610240840201604481013560018301556064810135600283015590919060ff906143c2906084016162c9565b60038401805460ff19169290911691909117905560243561024084020160a4810135600484015560c4810135600584015560e48101356006840155610104810135600784015561ffff9061441990610124016162e5565b60088401805461ffff1916929091169190911790556024356102408402016101448101356009840155610164810135600a8401556144739061445e90610184016162bc565b600b84019060ff801983541691151516179055565b6144ab61448b6101a4610240860260243501016162f4565b600b84019064ffffffff0082549160081b169064ffffffff001916179055565b6144eb6144c36101c4610240860260243501016162f4565b600b84019068ffffffff000000000082549160281b169068ffffffff00000000001916179055565b6024356102408402016101e401356001600160801b03811690036103b45760019382600b61475194015461ffff60c81b614530610204610240890260243501016162e5565b60c81b1660ff60d81b61454e6024356102408a0201610224016162bc565b7fffffffff00000000000000000000000000000000000000ffffffffffffffffff9093166024356102408a02016101e4013560481b78ffffffffffffffffffffffffffffffff00000000000000000016179190911791151560d81b1617600b820155600c8101805467ffffffffffffffff19166001600160401b038b16178155916145d984896161a2565b526147445f5160206179625f395f51905f52602083549489850154600286015460ff600388015416600488015460058901549060068a01549260078b01549461ffff60088d0154169660806040519e8f908c82019d8e52600460408301526060820152015260a08d015260c08c015260e08b01526101008a0152610120890152610140880152610160870152610160865261467661018087615eef565b60098501549060ff6001600160401b03600b600a890154980154925416916040519785890194855260408901528181161515606089015263ffffffff8160081c16608089015263ffffffff8160281c1660a08901526001600160801b038160481c1660c089015261ffff8160c81c1660e089015260d81c161515610100870152610120860152610120855261470d61014086615eef565b60405194859383850197518091895e84019083820190610100518252519283915e010161010051815203601f198101835282615eef565b519020611b6b828b6161a2565b93614767610224610240840260243501016162bc565b6040805191151582526001600160401b0388166020830152602435610240850201606481013593604490910135929035917f9738e55ce3503cada06dd3d3338d42623309a94afb5495119306f4a19a92350b91a40191614305565b826101005152601060205260406101005120600160ff19825416179055601154600160401b81101561160c57614804611dd4826001879401601155601161614a565b9055843561010051526012602052614822836040610100512061615f565b61437d565b836326ef68cf60e01b610100515235600452602461010051fd5b7f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03168287823b156103b45761489892604051808095819463abf1570d60e01b83526101005196600484016161b6565b039161010051905af180156121e2576148b3575b6101005180f35b610100516148c091615eef565b610100516103b457806148ac565b6148d981838561629e565b9485356101005152600660205260ff60406101005120541615614b90576020860135158015614b5b575b614b395790614aaf6001928735610100515260136020526040610100512060208901356101005152602052604061010051206001600160401b03600782015460081c1615614b14575b8835815560208901358582015560ff61496760408b016162c9565b1660ff19600283015416176002820155606089013560038201556080890135600482015560a0890135600582015560c089013560068201556149c36149ae60e08b016162bc565b600783019060ff801983541691151516179055565b60078101805468ffffffffffffffff00191660088c901b68ffffffffffffffff00161790556149f760208a01358a35616264565b614a01838a6161a2565b528054906001600160401b03868201549160ff600282015416906003810154600482015460058301549160076006850154940154946040519760208901995f5160206179625f395f51905f528b52600560408b015260608a0152608089015260a088015260c087015260e086015261010085015261012084015260ff8116151561014084015260081c166101608201526101608152614aa261018082615eef565b519020611b6b828d6161a2565b95614abc60e082016162bc565b7f4b9c1fac36a2aeaf69e62fe75b58c9e69ec27b26ca89e6cde77296d160d452da60405180614b0b8c6020870135963595839092916001600160401b0360209160408401951515845216910152565b0390a3016142f5565b883561010051526014602052614b3460208a01356040610100512061615f565b61494c565b602086638940a78560e01b610100515280356004520135602452604461010051fd5b50614b6860e087016162bc565b8015614903575060ff614b7d604088016162c9565b1615806149035750606086013515614903565b85633f4b99b160e21b610100515235600452602461010051fd5b909182358152602083013560208201526040830135604082015260ff614bd260608501615d95565b1660608201526080830135608082015260a083013560a082015260c083013560c082015260e083013560e082015261ffff614c10610100850161607c565b16610100820152610120830135610120820152610140830135610140820152614c3c6101608401615f90565b151561016082015263ffffffff614c566101808501615f7f565b1661018082015263ffffffff614c6f6101a08501615f7f565b166101a08201526101c0830135906001600160801b0382168092036103b457610240816001936101c08394015261ffff614cac6101e0880161607c565b166101e0820152614cc06102008701615f90565b15156102008201526001600160401b03614cdd6102208801615b9b565b1661022082015201930191016141e6565b9061012080600192853581526020860135602082015260ff614d1260408801615d95565b166040820152606086013560608201526080860135608082015260a086013560a082015260c086013560c0820152614d4c60e08701615f90565b151560e08201526001600160401b03614d686101008801615b9b565b1661010082015201930191019190916141c0565b50851561418c565b346103b457614d9236615bdf565b90614d9b616221565b506101005152601360205260406101005120906101005152602052610120604061010051206001600160401b0360405191614dd583615e7f565b8054928381526001820154916020820192835260ff600282015416604083019081526003820154606084019081526004830154906080850191825260ff60058501549360a08701948552600760068701549660c089019788520154978961010060e08a0199858c1615158b52019960081c1689526040519a8b525160208b0152511660408901525160608801525160808701525160a08601525160c085015251151560e08401525116610100820152f35b346103b457610100513660031901126103b4576040517f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03168152602090f35b346103b45760603660031901126103b4576004356001600160401b0381116103b457614efd903690600401615d04565b90614f06615b6f565b6044356001600160401b0381116103b457614f25903690600401615baf565b90916002548015612d0a578515612ce357600354936001600160401b0385169360405160208101908960608201888452604080840152526080810189610100515b8c8110615425575050926001600160401b0397959282614f9761428897946150059a9703601f198101835282615eef565b519020610100515060405160208101915f5160206179425f395f51905f5283524660408301523060608301527f7d6f52ebac641da43fc11d5911cdb8aabf4931733ac493d1f5540628174e88d360808301528a871660a083015260c082015260c08152610ea860e082615eef565b16906001600160401b03191617600355601754906001600160401b0361502c81841661608b565b1680926001600160401b03191617601755615046836160d4565b91615050846160d4565b93610100515b8181106150b4577f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03168587823b156103b45761489892604051808095819463abf1570d60e01b83526101005196600484016161b6565b6150bf81838661629e565b610120813603126103b457604051906150d782615e7f565b6151606101008235928385526150ef60208201615d95565b60208601526151006040820161607c565b604086015261511160608201615f7f565b60608601526151226080820161607c565b608086015261513360a08201615b9b565b60a086015261514460c0820161607c565b60c086015261515560e08201615f90565b60e086015201615b9b565b6101008301526101005152600660205260ff6040610100512054161561540c57604081015160ff60208301511661ffff6001821b831616156153e8575050908184610100600194015280516101005152600e6020526152a660406101005120848351918281550160ff806020860151161660ff19825416178155604084015181549066ffffffff000000606087015160181b169068ffff00000000000000608088015160381b169167ffffffffffffffff60481b60a089015160481b1661ffff60881b60c08a015160881b169260e08a0151151560981b946001600160401b0360a01b6101008c015160a01b16966001600160401b0360a01b199460ff60981b199361ffff60881b199262ffff0067ffffffffffffffff60481b199260081b169068ffffffffffffffff0019161716171617161716179060ff60981b16171790556164e1565b6152b083896161a2565b52805160ff60208301511661ffff60408401511663ffffffff60608501511661ffff6080860151166001600160401b0360a08701511661ffff60c0880151169160e08801511515936001600160401b036101008a015116956040519760208901995f5160206179625f395f51905f528b52600760408b015260608a0152608089015260a088015260c087015260e0860152610100850152610120840152610140830152610160820152610160815261536a61018082615eef565b519020615377838a6161a2565b527ffb325cc55af6e4500b502905b39f0679ffc3ea5f77a1534709444fdb64a99f0a60a082519260ff6020820151169061ffff6040820151169061ffff608063ffffffff6060840151169201511691604051938452602084015260408301526060820152876080820152a201615056565b61ffff9251633ecb481360e21b610100515260045260245216604452606461010051fd5b51633f4b99b160e21b6101005152600452602461010051fd5b9091610120806001928535815260ff61544060208801615d95565b16602082015261ffff6154556040880161607c565b16604082015263ffffffff61546c60608801615f7f565b16606082015261ffff6154816080880161607c565b1660808201526001600160401b0361549b60a08801615b9b565b1660a082015261ffff6154b060c0880161607c565b1660c08201526154c260e08701615f90565b151560e08201526001600160401b036154de6101008801615b9b565b166101008201520193019101614f66565b346103b45760803660031901126103b457615508615cab565b50615511615cc1565b506064356001600160401b0381116103b457615531903690600401615cd7565b5050604051630a85bd0160e11b8152602090f35b346103b4576020610a0561555836615bdf565b90616264565b346103b45760203660031901126103b457600435610100515260126020526040610100512060405190816020825491828152019161010051526020610100512090610100515b8181106155bb5761050f856104fb81870382615eef565b82548452602090930192600192830192016155a4565b346103b4576155df36615bdf565b906155e8616221565b506101005152600a602052604061010051209061010051526020526101206040610100512060056040519161561c83615e7f565b805483526001810154602084015261ffff600282015460ff8116604086015260ff8160081c16151560608601526001600160401b038160101c16608086015260501c1660a0840152600381015460c0840152600481015460e0840152015461010082015261568d6040518092615bf5565bf35b346103b45760203660031901126103b4576020610a056004356161e9565b3461379757606036600319011261379757600435906001600160401b0382116137975736602383011215613797578160040135906001600160401b038211613797573660248360071b8501011161379757615706615b6f565b906044356001600160401b03811161379757615726903690600401615baf565b9290600254908115615b60578515615b5157600354946001600160401b0386169460208101908860608201888452604080840152526080810160248b015f5b8b8110615b04575050926001600160401b039795928261579661428897946157ff9a9703601f198101835282615eef565b51902060405160208101915f5160206179425f395f51905f5283524660408301523060608301527fe3b31cc24d29add9661dc3a1f58e781f69070e12dc384bcd05cb5622a2f1c57b60808301528a871660a083015260c082015260c08152610ea860e082615eef565b16906001600160401b031916176003556017546001600160401b0361582581831661608b565b1680916001600160401b0319161760175561583f826160d4565b90615849836160d4565b935f5b84811015615a79578060071b820190608060231983360301126137975760405161587581615e64565b6024830135928382526020820191604482013583526158a9608461589b60648501615f90565b936040840194855201615b9b565b5060608101948786525f52600c6020528960ff60405f205416156159e9575b91817fed4d5e005c9676e1dcabfeb18ab560394b1c30a4409c368d6b8e5b7609513535936159c987600199606096515f52600b6020528d615967836159618c61595c600260405f208c5193848255516001820155016159368d511515829060ff801983541691151516179055565b8751815468ffffffffffffffff00191660089190911b68ffffffffffffffff0016179055565b616488565b926161a2565b528451908951906001600160401b0388511515915116906040519260208401945f5160206179625f395f51905f528652600360408601528b850152608084015260a083015260c082015260c081526159c060e082615eef565b519020926161a2565b52519351905115156040519182526020820152876040820152a20161584c565b5080515f52600c60205260405f20600160ff19825416179055805191600d5491600160401b831015615a65578b6159c9876001997fed4d5e005c9676e1dcabfeb18ab560394b1c30a4409c368d6b8e5b760951353597615a55611dd4898e60609b01600d55600d61614a565b90559950505091935091506158c8565b634e487b7160e01b5f52604160045260245ffd5b7f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03168487823b1561379757615acf925f928360405180968195829463abf1570d60e01b8452600484016161b6565b03925af18015615af957615ae4576101005180f35b5f615aee91615eef565b5f61010052806148ac565b6040513d5f823e3d90fd5b90916080806001928535815260208601356020820152615b2660408701615f90565b151560408201526001600160401b03615b4160608801615b9b565b1660608201520193019101615765565b63c2e5347d60e01b5f5260045ffd5b63d311bc3960e01b5f5260045ffd5b602435906001600160401b038216820361379757565b604435906001600160401b038216820361379757565b35906001600160401b038216820361379757565b9181601f84011215613797578235916001600160401b038311613797576020808501948460051b01011161379757565b6040906003190112613797576004359060243590565b6101008091805184526020810151602085015260ff60408201511660408501526060810151151560608501526001600160401b03608082015116608085015261ffff60a08201511660a085015260c081015160c085015260e081015160e08501520151910152565b90602080835192838152019201905f5b818110615c7a5750505090565b8251845260209384019390920191600101615c6d565b34613797575f36600319011261379757602060405160018152f35b600435906001600160a01b038216820361379757565b602435906001600160a01b038216820361379757565b9181601f84011215613797578235916001600160401b038311613797576020838186019501011161379757565b9181601f84011215613797578235916001600160401b03831161379757602080850194610120850201011161379757565b34613797575f36600319011261379757602060405160028152f35b34613797575f36600319011261379757602060405160038152f35b606090600319011261379757600435906024359060443590565b6004359060ff8216820361379757565b359060ff8216820361379757565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90815181526020820151602082015260ff60408301511660408201526101206001600160401b0381615e1f615e0d60608701516101406060880152610140870190615da3565b60808701518682036080880152615da3565b9460ff60a08201511660a086015263ffffffff60c08201511660c086015263ffffffff60e08201511660e0860152610100810151151561010086015201511691015290565b608081019081106001600160401b03821117615a6557604052565b61012081019081106001600160401b03821117615a6557604052565b6102c081019081106001600160401b03821117615a6557604052565b61024081019081106001600160401b03821117615a6557604052565b61014081019081106001600160401b03821117615a6557604052565b90601f801991011681019081106001600160401b03821117615a6557604052565b6001600160401b038111615a6557601f01601f191660200190565b929192615f3782615f10565b91615f456040519384615eef565b829481845281830111613797578281602093845f960137010152565b9080601f8301121561379757816020615f7c93359101615f2b565b90565b359063ffffffff8216820361379757565b3590811515820361379757565b919091610140818403126137975760405190615fb882615ed3565b81938135835260208201356020840152615fd460408301615d95565b604084015260608201356001600160401b0381116137975781615ff8918401615f61565b60608401526080820135906001600160401b0382116137975782616026610120949261607794869401615f61565b608086015261603760a08201615d95565b60a086015261604860c08201615f7f565b60c086015261605960e08201615f7f565b60e086015261606b6101008201615f90565b61010086015201615b9b565b910152565b359061ffff8216820361379757565b6001600160401b036001911601906001600160401b0382116160a957565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b038111615a655760051b60200190565b906160de826160bd565b6160eb6040519182615eef565b82815280926160fc601f19916160bd565b0190602036910137565b60045481101561611e5760045f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b60075481101561611e5760075f5260205f2001905f90565b805482101561611e575f5260205f2001905f90565b805490600160401b821015615a655781611dd49160016161819401815561614a565b9055565b80511561611e5760200190565b80516001101561611e5760400190565b805182101561611e5760209160051b010190565b90916161db615f7c936006845260016020850152608060408501526080840190615c5d565b916060818403910152615c5d565b60405160208101915f5160206179025f395f51905f5283525f604083015260608201526060815261621b608082615eef565b51902090565b6040519061622e82615e7f565b5f610100838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201520152565b906040519060208201925f5160206179025f395f51905f52845260056040840152606083015260808201526080815261621b60a082615eef565b919081101561611e57610120020190565b919082018092116160a957565b3580151581036137975790565b3560ff811681036137975790565b5f1981146160a95760010190565b3561ffff811681036137975790565b3563ffffffff811681036137975790565b6040519061631282615e9b565b5f6102a0838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e082015282610200820152826102208201528261024082015282610260820152826102808201520152565b604051906163b082615eb7565b5f610220838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e0820152826102008201520152565b916040519160208301935f5160206179025f395f51905f528552600460408501526060840152608083015260a082015260a0815261621b60c082615eef565b6040519061647182615e64565b5f6060838281528260208201528260408201520152565b60405160208101915f5160206179025f395f51905f5283526003604083015260608201526060815261621b608082615eef565b919082039182116160a957565b906164d39291616ee6565b80156164dc5790565b505f90565b60405160208101915f5160206179025f395f51905f5283526007604083015260608201526060815261621b608082615eef565b90604051906020820192835260408201526040815261621b606082615eef565b906040519060208201925f5160206179025f395f51905f52845260026040840152606083015260808201526080815261621b60a082615eef565b9035601e19823603018112156137975701602081359101916001600160401b03821161379757813603831361379757565b908060209392818452848401375f828201840152601f01601f1916010190565b600411156165c957565b634e487b7160e01b5f52602160045260245ffd5b3d15616607573d906165ee82615f10565b916165fc6040519384615eef565b82523d5f602084013e565b606090565b6040519061661982615ed3565b5f61012083828152826020820152826040820152606080820152606060808201528260a08201528260c08201528260e0820152826101008201520152565b90600182811c92168015616685575b602083101461667157565b634e487b7160e01b5f52602260045260245ffd5b91607f1691616666565b9060405191825f8254926166a284616657565b808452936001811690811561670d57506001146166c9575b506166c792500383615eef565b565b90505f9291925260205f20905f915b8183106166f15750509060206166c7928201015f6166ba565b60209193508060019154838589010152019101909184926166d8565b9050602092506166c794915060ff191682840152151560051b8201015f6166ba565b9060405161673c81615ed3565b6101206001600160401b0360058395805485526001810154602086015260ff60028201541660408601526167726003820161668f565b60608601526167836004820161668f565b6080860152015460ff811660a085015263ffffffff8160081c1660c085015263ffffffff8160281c1660e085015260ff8160481c16151561010085015260501c16910152565b80519060208101519060ff604082015116906060810151602081519101206001600160401b0361012060808401516020815191012093015116926040519460208601967f8db2525bc769e56adb6fd2402771f48c08c4503c9dc0bc507a776eaca65d700f885260408701526060860152608085015260a084015260c083015260e082015260e0815261621b61010082615eef565b356001600160401b03811681036137975790565b60405160208101915f5160206179025f395f51905f5283526001604083015260608201526060815261621b608082615eef565b8051906060810151906001600160401b0361024060ff60808401511692015116906040519260208401947f2d9686edff91aeaad85c9917be9d5ee284127481a229d9857b748073fba5b1c0865260408501526060840152608083015260a082015260a0815261621b60c082615eef565b60405160208101917faf7a3b9335082a3b4f22ddf585b52ecc40819755789412bb4253e79b45db1ab383525f5160206179225f395f51905f52604083015260608201526060815261621b608082615eef565b906040519060208201927faf7a3b9335082a3b4f22ddf585b52ecc40819755789412bb4253e79b45db1ab38452604083015260608201526060815261621b608082615eef565b906169b6826160bd565b6169c36040519182615eef565b82815280926169d4601f19916160bd565b01905f5b8281106169e457505050565b6020906169ef61660c565b828285010152016169d8565b90616a05826160bd565b616a126040519182615eef565b8281528092616a23601f19916160bd565b01905f5b828110616a3357505050565b602090616a3e616221565b82828501015201616a27565b356001600160a01b03811681036137975790565b90816020910312613797575180151581036137975790565b93919594965f958815616ce8576001600160401b0316438111616cd257610258616aa082436164bb565b11616cbc575060405193602085015260208452616abe604085615eef565b5f975f965b88881015616c92578760051b840135607e198536030181121561379757840199616aec8b616a4a565b6001600160a01b039182169116811015616c665750616b0a8a616a4a565b99616b4b602087616b1a84616a4a565b604051632e4bfa5160e11b81526001600160a01b039091166004820152602481019190915291829081906044820190565b03816001600160a01b038d165afa908115615af9575f91616c38575b5015616c11576020810190600460ff616b7f846162c9565b1603616bde57616b9088828b6172b5565b15616baa575050616ba26001916162d7565b970196616ac3565b90616bbf616bb960ff93616a4a565b916162c9565b9063bbf82ba360e01b5f5260018060a01b03166004521660245260445ffd5b90616bed616bb960ff93616a4a565b9063587548c360e11b5f5260018060a01b031660045216602452600460445260645ffd5b616c1b8691616a4a565b63ae8bb03960e01b5f5260018060a01b031660045260245260445ffd5b616c59915060203d8111616c5f575b616c518183615eef565b810190616a5e565b5f616b67565b503d616c47565b616c6f8b616a4a565b6311641feb60e21b5f9081526004929092526001600160a01b0316602452604490fd5b98509550955050505050808310616ca65750565b826305bc216760e51b5f5260045260245260445ffd5b630ed38fd160e41b5f526004524360245260445ffd5b637b51505560e01b5f526004524360245260445ffd5b631fc460bf60e11b5f5260045ffd5b6040810135908115616e7857616d1061020082016162bc565b15616ee25761ffff616d2561010083016162e5565b16158015616ed6575b616e785760608101600260ff616d43836162c9565b1614616eaf57600160ff616d56836162c9565b1614159081616e97575b50616e7857602081013590815f52600660205260ff60405f20541615616e6557815f52601360205260405f2060a082013590815f5260205260405f209015908115616e55575b50616deb57610120810135158015616e48575b616deb57610180810163ffffffff616dd0826162f4565b1615908115616e2c575b8115616e04575b50616deb57505050565b638307aca560e01b5f523560045260245260445260645ffd5b905063ffffffff80616e22616e1c6101a086016162f4565b936162f4565b169116115f616de1565b905063ffffffff616e406101a084016162f4565b161590616dda565b5061014081013515616db9565b60ff91506007015416155f616da6565b50633f4b99b160e21b5f5260045260245ffd5b602090638307aca560e01b5f528035600452013560245260445260645ffd5b60039150616ea660ff916162c9565b1614155f616d60565b506020810135908115801590616ec9575b616deb57505050565b5060a08101351515616ec0565b50608081013515616d2e565b5050565b90616ef0826165bf565b8115617035575f928392616f03816165bf565b6001811461700c57600290616f17816165bf565b14616f8457604051627eeac760e11b602082019081523060248301526044820192909252616f488160648101613224565b51915afa616f546165dd565b9080616f78575b156164dc5760208151918180820193849201010312613797575190565b50602081511015616f5b565b60405160208101916331a9108f60e11b8352602482015260248152616faa604482615eef565b51915afa616fb66165dd565b81616ffe575b81616fd1575b5015616fcd57600190565b5f90565b905060208180518101031261379757602001516001600160a01b038116908190036137975730145f616fc2565b905060208151101590616fbc565b505060405160208101906370a0823160e01b825230602482015260248152616f48604482615eef565b5050504790565b5f546001600160a01b0316801515908161705e575b506166c7576166c76173f7565b905033145f617051565b90813b156170e7575f816020829351910182855af16170856165dd565b90159081156170b7575b506170975750565b6365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b80518015159250826170cc575b50505f61708f565b6170df9250602080918301019101616a5e565b155f806170c4565b506365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b90916001600160a01b03811161711d57505050565b617146604051938493639a49f29360e01b85526004850152606060248501526064840190615da3565b9060448301520390fd5b9080519060208101519060ff60408201511660608201516020815191012060808301516020815191012060ff60a0850151169063ffffffff60c0860151169263ffffffff60e087015116946001600160401b03610120610100890151151598015116976040519960208b019b5f5160206179625f395f51905f528d52600160408d015260608c015260808b015260a08a015260c089015260e08801526101008701526101208601526101408501526101608401526101808301526101a08201526101a0815261621b6101c082615eef565b602081830312613797578051906001600160401b038211613797570181601f820112156137975780519061725482615f10565b926172626040519485615eef565b8284526020838301011161379757815f9260208093018386015e8301015290565b903590601e198136030182121561379757018035906001600160401b0382116137975760200191813603831361379757565b9160208201600460ff6172c7836162c9565b16146173765760ff6172da6005926162c9565b16146172e7575050505f90565b5f6172f183616a4a565b604051639e5adaeb60e01b81526001600160a01b0391821660048201529485916024918391165afa918215615af957615f7c935f9361734a575b5061733d816040617344930190617283565b3691615f2b565b9161767f565b61734491935061736e61733d913d805f833e6173668183615eef565b810190617221565b93915061732b565b505f61738183616a4a565b60405163b7af85d760e01b81526001600160a01b0391821660048201529485916024918391165afa918215615af957615f7c935f936173d3575b5061733d8160406173cd930190617283565b916175b6565b6173cd9193506173ef61733d913d805f833e6173668183615eef565b9391506173bb565b6040516328305db160e21b81527f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b031690602081600481855afa908115615af9575f91617597575b50158061751f575b61751c5760405163e14c465b60e01b8152602081600481855afa908115615af9575f916174e8575b50604051632e4bfa5160e11b815233600482015260248101919091529060209082908180604481015b03915afa908115615af9575f916174c9575b506166c75763321cbc0960e21b5f523360045260245ffd5b6174e2915060203d602011616c5f57616c518183615eef565b5f6174b1565b90506020813d602011617514575b8161750360209383615eef565b81010312613797575161749f617476565b3d91506174f6565b50565b5060405163f5778b0360e01b8152602081600481855afa908115615af9575f91617555575b506001600160a01b0316331461744e565b90506020813d60201161758f575b8161757060209383615eef565b8101031261379757516001600160a01b0381168103613797575f617544565b3d9150617563565b6175b0915060203d602011616c5f57616c518183615eef565b5f617446565b610a20815114801590617672575b61766b5760206176175f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282615eef565b51906102045afa6176266165dd565b8161765f575b81617635575090565b905060208151910151906020811061764e575b50151590565b5f199060200360031b1b165f617648565b8051602014915061762c565b5050505f90565b50611213835114156175c4565b60408151148015906176ee575b61766b5760206176df5f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282615eef565b51906102055afa6176266165dd565b506174608351141561768c565b9080156178fb5760015b81811061786957505f5b81811061782857505b6001811161772e575061772a90616185565b5190565b5f905f5b818110617740575050617718565b600181018082116160a95782811461780a5761775c82866161a2565b51905f9161776a82886161a2565b5111156177ee5761777b83876161a2565b51916160a95761778b90866161a2565b515b604051906020820192600160f81b845260218301526041820152604181526177b6606182615eef565b5190206177cc6177c5856162d7565b94866161a2565b525b600281018091111561773257634e487b7160e01b5f52601160045260245ffd5b6177f99150856161a2565b5161780482866161a2565b5161778d565b5061781581856161a2565b516178226177c5856162d7565b526177ce565b80617835600192856161a2565b5160405160208101915f8352602182015260218152617855604182615eef565b51902061786282866161a2565b520161770f565b92617876848493946161a2565b5192845b80158015806178dd575b156178c4575f198201918083116160a9576178aa6178a284886161a2565b5191876161a2565b521561787a57634e487b7160e01b5f52601160045260245ffd5b50936178d660019396929495866161a2565b5201617705565b505f1982018281116160a9576178f48791876161a2565b5111617884565b50505f9056febe98de78ab1a89101b9b77b1b3013a49647e63c349ee09cc55e0a33f97e351a984e75c8576483b53128ed23c26158522388ddb0a5df8516c83887d522196f91fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267553bfca378ab6d3ea8c58df6b962c50de5515a3d2878713fa6dd8d84f220809ff7643c83e539cea2f6bf506545392e52cfd5f917e327efbcd0ba28f29c28d042e
No CBOR metadata tail — this bytecode was built with cbor_metadata off, the setting our own contracts pin for CREATE2 address invariance.

disassembly (first 4,000 ops)

pcopoperand
0000PUSH20x0120
0003DUP1
0004PUSH10x40
0006MSTORE
0007PUSH10x04
0009CALLDATASIZE
000aLT
000bISZERO
000cPUSH20x0013
000fJUMPI
0010PUSH0
0011DUP1
0012REVERT
0013JUMPDEST
0014PUSH0
0015PUSH20x0100
0018MSTORE
0019PUSH0
001aCALLDATALOAD
001bPUSH10xe0
001dSHR
001eSWAP1
001fDUP2
0020PUSH40x0292709e
0025EQ
0026PUSH20x56ad
0029JUMPI
002aPOP
002bDUP1
002cPUSH40x036f9a43
0031EQ
0032PUSH20x568f
0035JUMPI
0036DUP1
0037PUSH40x0489d038
003cEQ
003dPUSH20x55d1
0040JUMPI
0041DUP1
0042PUSH40x087a5cf4
0047EQ
0048PUSH20x555e
004bJUMPI
004cDUP1
004dPUSH40x08f0ce62
0052EQ
0053PUSH20x3a14
0056JUMPI
0057DUP1
0058PUSH40x0900767d
005dEQ
005ePUSH20x5545
0061JUMPI
0062DUP1
0063PUSH40x150b7a02
0068EQ
0069PUSH20x54ef
006cJUMPI
006dDUP1
006ePUSH40x1591c4a0
0073EQ
0074PUSH20x4ecd
0077JUMPI
0078DUP1
0079PUSH40x178bcc93
007eEQ
007fPUSH20x4e86
0082JUMPI
0083DUP1
0084PUSH40x1945098e
0089EQ
008aPUSH20x0649
008dJUMPI
008eDUP1
008fPUSH40x1a633473
0094EQ
0095PUSH20x0529
0098JUMPI
0099DUP1
009aPUSH40x1e000aa5
009fEQ
00a0PUSH20x4d84
00a3JUMPI
00a4DUP1
00a5PUSH40x200d43ad
00aaEQ
00abPUSH20x40de
00aeJUMPI
00afDUP1
00b0PUSH40x20d4f696
00b5EQ
00b6PUSH20x0529
00b9JUMPI
00baDUP1
00bbPUSH40x210de79d
00c0EQ
00c1PUSH20x408f
00c4JUMPI
00c5DUP1
00c6PUSH40x2b89089c
00cbEQ
00ccPUSH20x4070
00cfJUMPI
00d0DUP1
00d1PUSH40x3ec4cc34
00d6EQ
00d7PUSH20x4025
00daJUMPI
00dbDUP1
00dcPUSH40x3fb27b85
00e1EQ
00e2PUSH20x3fb6
00e5JUMPI
00e6DUP1
00e7PUSH40x42cde4e8
00ecEQ
00edPUSH20x3f96
00f0JUMPI
00f1DUP1
00f2PUSH40x4760fd43
00f7EQ
00f8PUSH20x3f2a
00fbJUMPI
00fcDUP1
00fdPUSH40x4bcb06a9
0102EQ
0103PUSH20x3f11
0106JUMPI
0107DUP1
0108PUSH40x4ec824ff
010dEQ
010ePUSH20x3a14
0111JUMPI
0112DUP1
0113PUSH40x5483a0b5
0118EQ
0119PUSH20x3ec9
011cJUMPI
011dDUP1
011ePUSH40x57d70648
0123EQ
0124PUSH20x3eab
0127JUMPI
0128DUP1
0129PUSH40x5b88f50e
012eEQ
012fPUSH20x3a14
0132JUMPI
0133DUP1
0134PUSH40x5eb320b8
0139EQ
013aPUSH20x3e8b
013dJUMPI
013eDUP1
013fPUSH40x5f61b15c
0144EQ
0145PUSH20x3a14
0148JUMPI
0149DUP1
014aPUSH40x60a18008
014fEQ
0150PUSH20x3e57
0153JUMPI
0154DUP1
0155PUSH40x60c29fac
015aEQ
015bPUSH20x0649
015eJUMPI
015fDUP1
0160PUSH40x622c3f21
0165EQ
0166PUSH20x3e38
0169JUMPI
016aDUP1
016bPUSH40x65cea49c
0170EQ
0171PUSH20x3e1a
0174JUMPI
0175DUP1
0176PUSH40x68cf465e
017bEQ
017cPUSH20x0649
017fJUMPI
0180DUP1
0181PUSH40x6c2c44e2
0186EQ
0187PUSH20x3e01
018aJUMPI
018bDUP1
018cPUSH40x6db81752
0191EQ
0192PUSH20x3de1
0195JUMPI
0196DUP1
0197PUSH40x711f3db4
019cEQ
019dPUSH20x3cff
01a0JUMPI
01a1DUP1
01a2PUSH40x767c39d2
01a7EQ
01a8PUSH20x0529
01abJUMPI
01acDUP1
01adPUSH40x7b103999
01b2EQ
01b3PUSH20x3cb8
01b6JUMPI
01b7DUP1
01b8PUSH40x7bb737bc
01bdEQ
01bePUSH20x3c9f
01c1JUMPI
01c2DUP1
01c3PUSH40x7f49d69f
01c8EQ
01c9PUSH20x3c75
01ccJUMPI
01cdDUP1
01cePUSH40x831f33a1
01d3EQ
01d4PUSH20x3a55
01d7JUMPI
01d8DUP1
01d9PUSH40x8513e2d5
01deEQ
01dfPUSH20x3a37
01e2JUMPI
01e3DUP1
01e4PUSH40x852a52e8
01e9EQ
01eaPUSH20x3a19
01edJUMPI
01eeDUP1
01efPUSH40x88f06eaa
01f4EQ
01f5PUSH20x3a14
01f8JUMPI
01f9DUP1
01faPUSH40x92a2e0b3
01ffEQ
0200PUSH20x39f6
0203JUMPI
0204DUP1
0205PUSH40x9377c220
020aEQ
020bPUSH20x39cd
020eJUMPI
020fDUP1
0210PUSH40x9493ba1d
0215EQ
0216PUSH20x395a
0219JUMPI
021aDUP1
021bPUSH40x94bc4e96
0220EQ
0221PUSH20x364d
0224JUMPI
0225DUP1
0226PUSH40x95102c42
022bEQ
022cPUSH20x362e
022fJUMPI
0230DUP1
0231PUSH40x9648eca2
0236EQ
0237PUSH20x336e
023aJUMPI
023bDUP1
023cPUSH40x96f51f3a
0241EQ
0242PUSH20x30b9
0245JUMPI
0246DUP1
0247PUSH40x97b9857b
024cEQ
024dPUSH20x304d
0250JUMPI
0251DUP1
0252PUSH40xa741113c
0257EQ
0258PUSH20x3013
025bJUMPI
025cDUP1
025dPUSH40xaa9239f5
0262EQ
0263PUSH20x2fb7
0266JUMPI
0267DUP1
0268PUSH40xac65bc70
026dEQ
026ePUSH20x2e8e
0271JUMPI
0272DUP1
0273PUSH40xaffed0e0
0278EQ
0279PUSH20x2e65
027cJUMPI
027dDUP1
027ePUSH40xb19f4805
0283EQ
0284PUSH20x2e28
0287JUMPI
0288DUP1
0289PUSH40xb8da302a
028eEQ
028fPUSH20x2e08
0292JUMPI
0293DUP1
0294PUSH40xb9a7f076
0299EQ
029aPUSH20x2d1f
029dJUMPI
029eDUP1
029fPUSH40xb9ed4686
02a4EQ
02a5PUSH20x0c83
02a8JUMPI
02a9DUP1
02aaPUSH40xbbbcda44
02afEQ
02b0PUSH20x0c65
02b3JUMPI
02b4DUP1
02b5PUSH40xbc197c81
02baEQ
02bbPUSH20x0bcd
02beJUMPI
02bfDUP1
02c0PUSH40xbda1019e
02c5EQ
02c6PUSH20x0bad
02c9JUMPI
02caDUP1
02cbPUSH40xbe380c85
02d0EQ
02d1PUSH20x0a2c
02d4JUMPI
02d5DUP1
02d6PUSH40xc5dbda66
02dbEQ
02dcPUSH20x0a0d
02dfJUMPI
02e0DUP1
02e1PUSH40xdc544a09
02e6EQ
02e7PUSH20x09ec
02eaJUMPI
02ebDUP1
02ecPUSH40xde54d429
02f1EQ
02f2PUSH20x09cc
02f5JUMPI
02f6DUP1
02f7PUSH40xdf46c713
02fcEQ
02fdPUSH20x0866
0300JUMPI
0301DUP1
0302PUSH40xe02e1bfd
0307EQ
0308PUSH20x0846
030bJUMPI
030cDUP1
030dPUSH40xe597e659
0312EQ
0313PUSH20x064e
0316JUMPI
0317DUP1
0318PUSH40xe70475a7
031dEQ
031ePUSH20x0649
0321JUMPI
0322DUP1
0323PUSH40xe94a52af
0328EQ
0329PUSH20x062b
032cJUMPI
032dDUP1
032ePUSH40xeafe7a74
0333EQ
0334PUSH20x060b
0337JUMPI
0338DUP1
0339PUSH40xeb777382
033eEQ
033fPUSH20x052e
0342JUMPI
0343DUP1
0344PUSH40xee076a50
0349EQ
034aPUSH20x0529
034dJUMPI
034eDUP1
034fPUSH40xee57e33c
0354EQ
0355PUSH20x0494
0358JUMPI
0359DUP1
035aPUSH40xf23a6e61
035fEQ
0360PUSH20x043e
0363JUMPI
0364DUP1
0365PUSH40xf3178f43
036aEQ
036bPUSH20x03db
036eJUMPI
036fDUP1
0370PUSH40xf4e885db
0375EQ
0376PUSH20x03bb
0379JUMPI
037aPUSH40xf851a440
037fEQ
0380PUSH20x0387
0383JUMPI
0384PUSH0
0385DUP1
0386REVERT
0387JUMPDEST
0388CALLVALUE
0389PUSH20x03b4
038cJUMPI
038dPUSH20x0100
0390MLOAD
0391CALLDATASIZE
0392PUSH10x03
0394NOT
0395ADD
0396SLT
0397PUSH20x03b4
039aJUMPI
039bPUSH20x0100
039eMLOAD
039fSLOAD
03a0PUSH10x40
03a2MLOAD
03a3PUSH10x01
03a5PUSH10x01
03a7PUSH10xa0
03a9SHL
03aaSUB
03abSWAP1
03acSWAP2
03adAND
03aeDUP2
03afMSTORE
03b0PUSH10x20
03b2SWAP1
03b3RETURN
03b4JUMPDEST
03b5PUSH20x0100
03b8MLOAD
03b9DUP1
03baREVERT
03bbJUMPDEST
03bcCALLVALUE
03bdPUSH20x03b4
03c0JUMPI
03c1PUSH20x0100
03c4MLOAD
03c5CALLDATASIZE
03c6PUSH10x03
03c8NOT
03c9ADD
03caSLT
03cbPUSH20x03b4
03ceJUMPI
03cfPUSH10x20
03d1PUSH10x01
03d3SLOAD
03d4PUSH10x40
03d6MLOAD
03d7SWAP1
03d8DUP2
03d9MSTORE
03daRETURN
03dbJUMPDEST
03dcCALLVALUE
03ddPUSH20x03b4
03e0JUMPI
03e1PUSH10x40
03e3CALLDATASIZE
03e4PUSH10x03
03e6NOT
03e7ADD
03e8SLT
03e9PUSH20x03b4
03ecJUMPI
03edPUSH20x03f4
03f0PUSH20x5b6f
03f3JUMP
03f4JUMPDEST
03f5PUSH10x04
03f7CALLDATALOAD
03f8PUSH20x0100
03fbMLOAD
03fcMSTORE
03fdPUSH10x0a
03ffPUSH10x20
0401MSTORE
0402PUSH20x041a
0405PUSH10x01
0407PUSH10x01
0409PUSH10x40
040bSHL
040cSUB
040dPUSH10x40
040fPUSH20x0100
0412MLOAD
0413KECCAK256
0414SWAP3
0415AND
0416PUSH20x6914
0419JUMP
041aJUMPDEST
041bPUSH20x0100
041eMLOAD
041fMSTORE
0420PUSH10x20
0422MSTORE
0423PUSH10x20
0425PUSH20xffff
0428PUSH10x02
042aPUSH10x40
042cPUSH20x0100
042fMLOAD
0430KECCAK256
0431ADD
0432SLOAD
0433PUSH10x50
0435SHR
0436AND
0437PUSH10x40
0439MLOAD
043aSWAP1
043bDUP2
043cMSTORE
043dRETURN
043eJUMPDEST
043fCALLVALUE
0440PUSH20x03b4
0443JUMPI
0444PUSH10xa0
0446CALLDATASIZE
0447PUSH10x03
0449NOT
044aADD
044bSLT
044cPUSH20x03b4
044fJUMPI
0450PUSH20x0457
0453PUSH20x5cab
0456JUMP
0457JUMPDEST
0458POP
0459PUSH20x0460
045cPUSH20x5cc1
045fJUMP
0460JUMPDEST
0461POP
0462PUSH10x84
0464CALLDATALOAD
0465PUSH10x01
0467PUSH10x01
0469PUSH10x40
046bSHL
046cSUB
046dDUP2
046eGT
046fPUSH20x03b4
0472JUMPI
0473PUSH20x0480
0476SWAP1
0477CALLDATASIZE
0478SWAP1
0479PUSH10x04
047bADD
047cPUSH20x5cd7
047fJUMP
0480JUMPDEST
0481POP
0482POP
0483PUSH10x40
0485MLOAD
0486PUSH40xf23a6e61
048bPUSH10xe0
048dSHL
048eDUP2
048fMSTORE
0490PUSH10x20
0492SWAP1
0493RETURN
0494JUMPDEST
0495CALLVALUE
0496PUSH20x03b4
0499JUMPI
049aPUSH20x0100
049dMLOAD
049eCALLDATASIZE
049fPUSH10x03
04a1NOT
04a2ADD
04a3SLT
04a4PUSH20x03b4
04a7JUMPI
04a8PUSH10x40
04aaMLOAD
04abDUP1
04acPUSH10x20
04aePUSH10x11
04b0SLOAD
04b1SWAP2
04b2DUP3
04b3DUP2
04b4MSTORE
04b5ADD
04b6SWAP1
04b7PUSH10x11
04b9PUSH20x0100
04bcMLOAD
04bdMSTORE
04bePUSH320x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68
04dfSWAP1
04e0PUSH20x0100
04e3MLOAD
04e4JUMPDEST
04e5DUP2
04e6DUP2
04e7LT
04e8PUSH20x0513
04ebJUMPI
04ecPUSH20x050f
04efDUP6
04f0PUSH20x04fb
04f3DUP2
04f4DUP8
04f5SUB
04f6DUP3
04f7PUSH20x5eef
04faJUMP
04fbJUMPDEST
04fcPUSH10x40
04feMLOAD
04ffSWAP2
0500DUP3
0501SWAP2
0502PUSH10x20
0504DUP4
0505MSTORE
0506PUSH10x20
0508DUP4
0509ADD
050aSWAP1
050bPUSH20x5c5d
050eJUMP
050fJUMPDEST
0510SUB
0511SWAP1
0512RETURN
0513JUMPDEST
0514DUP3
0515SLOAD
0516DUP5
0517MSTORE
0518PUSH10x20
051aSWAP1
051bSWAP4
051cADD
051dSWAP3
051ePUSH10x01
0520SWAP3
0521DUP4
0522ADD
0523SWAP3
0524ADD
0525PUSH20x04e4
0528JUMP
0529JUMPDEST
052aPUSH20x5d50
052dJUMP
052eJUMPDEST
052fCALLVALUE
0530PUSH20x03b4
0533JUMPI
0534PUSH20x0100
0537MLOAD
0538CALLDATASIZE
0539PUSH10x03
053bNOT
053cADD
053dSLT
053ePUSH20x03b4
0541JUMPI
0542PUSH10x04
0544SLOAD
0545PUSH20x054d
0548DUP2
0549PUSH20x60d4
054cJUMP
054dJUMPDEST
054ePUSH20x0100
0551MLOAD
0552SWAP1
0553SWAP2
0554DUP2
0555JUMPDEST
0556DUP2
0557DUP2
0558LT
0559PUSH20x05a9
055cJUMPI
055dPOP
055ePOP
055fPUSH20x0567
0562DUP2
0563PUSH20x60d4
0566JUMP
0567JUMPDEST
0568SWAP2
0569PUSH20x0100
056cMLOAD
056dJUMPDEST
056eDUP3
056fDUP2
0570LT
0571PUSH20x058a
0574JUMPI
0575PUSH10x40
0577MLOAD
0578PUSH10x20
057aDUP1
057bDUP3
057cMSTORE
057dDUP2
057eSWAP1
057fPUSH20x050f
0582SWAP1
0583DUP3
0584ADD
0585DUP8
0586PUSH20x5c5d
0589JUMP
058aJUMPDEST
058bDUP1
058cPUSH20x0597
058fPUSH10x01
0591SWAP3
0592DUP5
0593PUSH20x61a2
0596JUMP
0597JUMPDEST
0598MLOAD
0599PUSH20x05a2
059cDUP3
059dDUP8
059ePUSH20x61a2
05a1JUMP
05a2JUMPDEST
05a3MSTORE
05a4ADD
05a5PUSH20x056d
05a8JUMP
05a9JUMPDEST
05aaDUP1
05abPUSH20x05b5
05aePUSH10x01
05b0SWAP3
05b1PUSH20x6106
05b4JUMP
05b5JUMPDEST
05b6SWAP1
05b7SLOAD
05b8SWAP1
05b9PUSH10x03
05bbSHL
05bcSHR
05bdPUSH20x0100
05c0MLOAD
05c1MSTORE
05c2PUSH10x05
05c4PUSH10x20
05c6MSTORE
05c7PUSH10xff
05c9PUSH10x0c
05cbPUSH10x40
05cdPUSH20x0100
05d0MLOAD
05d1KECCAK256
05d2ADD
05d3SLOAD
05d4PUSH10x08
05d6SHR
05d7AND
05d8PUSH20x05e2
05dbJUMPI
05dcJUMPDEST
05ddADD
05dePUSH20x0555
05e1JUMP
05e2JUMPDEST
05e3PUSH20x05eb
05e6DUP2
05e7PUSH20x6106
05eaJUMP
05ebJUMPDEST
05ecSWAP1
05edSLOAD
05eeSWAP1
05efPUSH10x03
05f1SHL
05f2SHR
05f3PUSH20x0605
05f6PUSH20x05fe
05f9DUP7
05faPUSH20x62d7
05fdJUMP
05feJUMPDEST
05ffSWAP6
0600DUP8
0601PUSH20x61a2
0604JUMP
0605JUMPDEST
0606MSTORE
0607PUSH20x05dc
060aJUMP
060bJUMPDEST
060cCALLVALUE
060dPUSH20x03b4
0610JUMPI
0611PUSH20x0100
0614MLOAD
0615CALLDATASIZE
0616PUSH10x03
0618NOT
0619ADD
061aSLT
061bPUSH20x03b4
061eJUMPI
061fPUSH10x20
0621PUSH10x07
0623SLOAD
0624PUSH10x40
0626MLOAD
0627SWAP1
0628DUP2
0629MSTORE
062aRETURN
062bJUMPDEST
062cCALLVALUE
062dPUSH20x03b4
0630JUMPI
0631PUSH20x0100
0634MLOAD
0635CALLDATASIZE
0636PUSH10x03
0638NOT
0639ADD
063aSLT
063bPUSH20x03b4
063eJUMPI
063fPUSH10x20
0641PUSH10x40
0643MLOAD
0644PUSH10x05
0646DUP2
0647MSTORE
0648RETURN
0649JUMPDEST
064aPUSH20x5d35
064dJUMP
064eJUMPDEST
064fCALLVALUE
0650PUSH20x03b4
0653JUMPI
0654PUSH10x40
0656CALLDATASIZE
0657PUSH10x03
0659NOT
065aADD
065bSLT
065cPUSH20x03b4
065fJUMPI
0660PUSH10x24
0662CALLDATALOAD
0663PUSH10x04
0665CALLDATALOAD
0666PUSH10xff
0668DUP3
0669AND
066aDUP3
066bSUB
066cPUSH20x03b4
066fJUMPI
0670PUSH10x07
0672SLOAD
0673PUSH20x067b
0676DUP2
0677PUSH20x69fb
067aJUMP
067bJUMPDEST
067cPUSH20x0100
067fMLOAD
0680SWAP1
0681SWAP4
0682SWAP1
0683SWAP3
0684DUP4
0685JUMPDEST
0686DUP4
0687DUP2
0688LT
0689PUSH20x0722
068cJUMPI
068dDUP6
068eDUP6
068fPUSH20x0697
0692DUP2
0693PUSH20x69fb
0696JUMP
0697JUMPDEST
0698SWAP2
0699PUSH20x0100
069cMLOAD
069dJUMPDEST
069eDUP3
069fDUP2
06a0LT
06a1PUSH20x06f8
06a4JUMPI
06a5DUP4
06a6PUSH10x40
06a8MLOAD
06a9DUP1
06aaSWAP2
06abPUSH10x20
06adDUP3
06aeADD
06afPUSH10x20
06b1DUP4
06b2MSTORE
06b3DUP2
06b4MLOAD
06b5DUP1
06b6SWAP2
06b7MSTORE
06b8PUSH10x20
06baPUSH10x40
06bcDUP5
06bdADD
06beSWAP3
06bfADD
06c0SWAP1
06c1PUSH20x0100
06c4MLOAD
06c5JUMPDEST
06c6DUP2
06c7DUP2
06c8LT
06c9PUSH20x06d3
06ccJUMPI
06cdPOP
06cePOP
06cfPOP
06d0SUB
06d1SWAP1
06d2RETURN
06d3JUMPDEST
06d4SWAP2
06d5SWAP4
06d6POP
06d7SWAP2
06d8PUSH10x20
06daPUSH20x0120
06ddDUP3
06dePUSH20x06ea
06e1PUSH10x01
06e3SWAP5
06e4DUP9
06e5MLOAD
06e6PUSH20x5bf5
06e9JUMP
06eaJUMPDEST
06ebADD
06ecSWAP5
06edADD
06eeSWAP2
06efADD
06f0SWAP2
06f1DUP5
06f2SWAP4
06f3SWAP3
06f4PUSH20x06c5
06f7JUMP
06f8JUMPDEST
06f9DUP1
06faPUSH20x0705
06fdPUSH10x01
06ffSWAP3
0700DUP5
0701PUSH20x61a2
0704JUMP
0705JUMPDEST
0706MLOAD
0707PUSH20x0710
070aDUP3
070bDUP8
070cPUSH20x61a2
070fJUMP
0710JUMPDEST
0711MSTORE
0712PUSH20x071b
0715DUP2
0716DUP7
0717PUSH20x61a2
071aJUMP
071bJUMPDEST
071cPOP
071dADD
071ePUSH20x069d
0721JUMP
0722JUMPDEST
0723PUSH20x072b
0726DUP2
0727PUSH20x6132
072aJUMP
072bJUMPDEST
072cSWAP1
072dSLOAD
072eSWAP1
072fPUSH10x03
0731SHL
0732SHR
0733DUP1
0734PUSH20x0100
0737MLOAD
0738MSTORE
0739PUSH10x08
073bPUSH10x20
073dMSTORE
073ePUSH10xff
0740PUSH10x05
0742PUSH10x40
0744PUSH20x0100
0747MLOAD
0748KECCAK256
0749ADD
074aSLOAD
074bPUSH10x48
074dSHR
074eAND
074fISZERO
0750PUSH20x083d
0753JUMPI
0754PUSH20x0100
0757MLOAD
0758MSTORE
0759PUSH10x0a
075bPUSH10x20
075dMSTORE
075ePUSH10x40
0760PUSH20x0100
0763MLOAD
0764KECCAK256
0765DUP3
0766PUSH20x0100
0769MLOAD
076aMSTORE
076bPUSH10x20
076dMSTORE
076ePUSH10x40
0770PUSH20x0100
0773MLOAD
0774KECCAK256
0775PUSH10x40
0777MLOAD
0778SWAP1
0779PUSH20x0781
077cDUP3
077dPUSH20x5e7f
0780JUMP
0781JUMPDEST
0782DUP1
0783SLOAD
0784DUP3
0785MSTORE
0786PUSH10x01
0788DUP2
0789ADD
078aSLOAD
078bPUSH10x20
078dDUP4
078eADD
078fMSTORE
0790DUP5
0791PUSH10x02
0793DUP3
0794ADD
0795SLOAD
0796PUSH10xff
0798DUP2
0799AND
079aPUSH10x40
079cDUP6
079dADD
079eMSTORE
079fPUSH10x05
07a1PUSH10xff
07a3DUP3
07a4PUSH10x08
07a6SHR
07a7AND
07a8ISZERO
07a9ISZERO
07aaSWAP4
07abDUP5
07acPUSH10x60
07aeDUP8
07afADD
07b0MSTORE
07b1PUSH10x01
07b3PUSH10x01
07b5PUSH10x40
07b7SHL
07b8SUB
07b9DUP4
07baPUSH10x10
07bcSHR
07bdAND
07bePUSH10x80
07c0DUP8
07c1ADD
07c2MSTORE
07c3PUSH20xffff
07c6DUP4
07c7PUSH10x50
07c9SHR
07caAND
07cbPUSH10xa0
07cdDUP8
07ceADD
07cfMSTORE
07d0PUSH10x03
07d2DUP2
07d3ADD
07d4SLOAD
07d5PUSH10xc0
07d7DUP8
07d8ADD
07d9MSTORE
07daPUSH10x04
07dcDUP2
07ddADD
07deSLOAD
07dfPUSH10xe0
07e1DUP8
07e2ADD
07e3MSTORE
07e4ADD
07e5SLOAD
07e6PUSH20x0100
07e9DUP6
07eaADD
07ebMSTORE
07ecDUP3
07edPUSH20x082e
07f0JUMPI
07f1JUMPDEST
07f2POP
07f3POP
07f4PUSH20x0803
07f7JUMPI
07f8JUMPDEST
07f9POP
07faPUSH10x01
07fcSWAP1
07fdJUMPDEST
07feADD
07ffPUSH20x0685
0802JUMP
0803JUMPDEST
0804SWAP5
0805SWAP1
0806PUSH20x0827
0809DUP3
080aPUSH20x0815
080dPUSH10x01
080fSWAP5
0810SWAP2
0811PUSH20x62d7
0814JUMP
0815JUMPDEST
0816SWAP8
0817PUSH20x0820
081aDUP3
081bDUP12
081cPUSH20x61a2
081fJUMP
0820JUMPDEST
0821MSTORE
0822DUP9
0823PUSH20x61a2
0826JUMP
0827JUMPDEST
0828POP
0829SWAP1
082aPUSH20x07f8
082dJUMP
082eJUMPDEST
082fPUSH10xff
0831SWAP3
0832POP
0833AND
0834AND
0835ISZERO
0836ISZERO
0837DUP5
0838DUP10
0839PUSH20x07f1
083cJUMP
083dJUMPDEST
083ePOP
083fPUSH10x01
0841SWAP1
0842PUSH20x07fd
0845JUMP
0846JUMPDEST
0847CALLVALUE
0848PUSH20x03b4
084bJUMPI
084cPUSH20x0100
084fMLOAD
0850CALLDATASIZE
0851PUSH10x03
0853NOT
0854ADD
0855SLT
0856PUSH20x03b4
0859JUMPI
085aPUSH10x20
085cPUSH10x04
085eSLOAD
085fPUSH10x40
0861MLOAD
0862SWAP1
0863DUP2
0864MSTORE
0865RETURN
0866JUMPDEST
0867CALLVALUE
0868PUSH20x03b4
086bJUMPI
086cPUSH10x20
086eCALLDATASIZE
086fPUSH10x03
0871NOT
0872ADD
0873SLT
0874PUSH20x03b4
0877JUMPI
0878PUSH20x087f
087bPUSH20x5d85
087eJUMP
087fJUMPDEST
0880PUSH10x07
0882SLOAD
0883SWAP1
0884PUSH20x088c
0887DUP3
0888PUSH20x69ac
088bJUMP
088cJUMPDEST
088dPUSH20x0100
0890MLOAD
0891SWAP1
0892SWAP3
0893SWAP1
0894SWAP2
0895DUP3
0896JUMPDEST
0897DUP3
0898DUP2
0899LT
089aPUSH20x094a
089dJUMPI
089ePOP
089fPOP
08a0POP
08a1PUSH20x08a9
08a4DUP2
08a5PUSH20x69ac
08a8JUMP
08a9JUMPDEST
08aaSWAP2
08abPUSH20x0100
08aeMLOAD
08afJUMPDEST
08b0DUP3
08b1DUP2
08b2LT
08b3PUSH20x0920
08b6JUMPI
08b7DUP4
08b8PUSH10x40
08baMLOAD
08bbDUP1
08bcSWAP2
08bdPUSH10x20
08bfDUP3
08c0ADD
08c1PUSH10x20
08c3DUP4
08c4MSTORE
08c5DUP2
08c6MLOAD
08c7DUP1
08c8SWAP2
08c9MSTORE
08caPUSH10x40
08ccDUP4
08cdADD
08ceSWAP1
08cfPUSH10x20
08d1PUSH10x40
08d3DUP3
08d4PUSH10x05
08d6SHL
08d7DUP7
08d8ADD
08d9ADD
08daSWAP4
08dbADD
08dcSWAP2
08ddPUSH20x0100
08e0MLOAD
08e1SWAP1
08e2JUMPDEST
08e3DUP3
08e4DUP3
08e5LT
08e6PUSH20x08f1
08e9JUMPI
08eaPOP
08ebPOP
08ecPOP
08edPOP
08eeSUB
08efSWAP1
08f0RETURN
08f1JUMPDEST
08f2SWAP2
08f3SWAP4
08f4PUSH10x01
08f6SWAP2
08f7SWAP4
08f8SWAP6
08f9POP
08faPUSH10x20
08fcPUSH20x0910
08ffDUP2
0900SWAP3
0901PUSH10x3f
0903NOT
0904DUP11
0905DUP3
0906SUB
0907ADD
0908DUP7
0909MSTORE
090aDUP9
090bMLOAD
090cPUSH20x5dc7
090fJUMP
0910JUMPDEST
0911SWAP7
0912ADD
0913SWAP3
0914ADD
0915SWAP3
0916ADD
0917DUP6
0918SWAP5
0919SWAP4
091aSWAP2
091bSWAP3
091cPUSH20x08e2
091fJUMP
0920JUMPDEST
0921DUP1
0922PUSH20x092d
0925PUSH10x01
0927SWAP3
0928DUP5
0929PUSH20x61a2
092cJUMP
092dJUMPDEST
092eMLOAD
092fPUSH20x0938
0932DUP3
0933DUP8
0934PUSH20x61a2
0937JUMP
0938JUMPDEST
0939MSTORE
093aPUSH20x0943
093dDUP2
093eDUP7
093fPUSH20x61a2
0942JUMP
0943JUMPDEST
0944POP
0945ADD
0946PUSH20x08af
0949JUMP
094aJUMPDEST
094bPUSH20x0953
094eDUP2
094fPUSH20x6132
0952JUMP
0953JUMPDEST
0954SWAP1
0955SLOAD
0956SWAP1
0957PUSH10x03
0959SHL
095aSHR
095bPUSH20x0100
095eMLOAD
095fMSTORE
0960PUSH10x08
0962PUSH10x20
0964MSTORE
0965PUSH20x0973
0968PUSH10x40
096aPUSH20x0100
096dMLOAD
096eKECCAK256
096fPUSH20x672f
0972JUMP
0973JUMPDEST
0974PUSH20x0100
0977DUP2
0978ADD
0979MLOAD
097aISZERO
097bISZERO
097cDUP1
097dPUSH20x09ba
0980JUMPI
0981JUMPDEST
0982PUSH20x098f
0985JUMPI
0986JUMPDEST
0987POP
0988PUSH10x01
098aADD
098bPUSH20x0896
098eJUMP
098fJUMPDEST
0990SWAP4
0991SWAP1
0992PUSH20x09b3
0995DUP3
0996PUSH20x09a1
0999PUSH10x01
099bSWAP5
099cSWAP2
099dPUSH20x62d7
09a0JUMP
09a1JUMPDEST
09a2SWAP7
09a3PUSH20x09ac
09a6DUP3
09a7DUP11
09a8PUSH20x61a2
09abJUMP
09acJUMPDEST
09adMSTORE
09aeDUP8
09afPUSH20x61a2
09b2JUMP
09b3JUMPDEST
09b4POP
09b5SWAP1
09b6PUSH20x0986
09b9JUMP
09baJUMPDEST
09bbPOP
09bcPUSH10xff
09beDUP4
09bfPUSH10xa0
09c1DUP4
09c2ADD
09c3MLOAD
09c4AND
09c5AND
09c6ISZERO
09c7ISZERO
09c8PUSH20x0981
09cbJUMP
09ccJUMPDEST
09cdCALLVALUE
09cePUSH20x03b4
09d1JUMPI
09d2PUSH20x0100
09d5MLOAD
09d6CALLDATASIZE
09d7PUSH10x03
09d9NOT
09daADD
09dbSLT
09dcPUSH20x03b4
09dfJUMPI
09e0PUSH10x20
09e2PUSH10x0d
09e4SLOAD
09e5PUSH10x40
09e7MLOAD
09e8SWAP1
09e9DUP2
09eaMSTORE
09ebRETURN
09ecJUMPDEST
09edCALLVALUE
09eePUSH20x03b4
09f1JUMPI
09f2PUSH10x20
09f4PUSH20x0a05
09f7PUSH20x09ff
09faCALLDATASIZE
09fbPUSH20x5bdf
09feJUMP
09ffJUMPDEST
0a00SWAP1
0a01PUSH20x6966
0a04JUMP
0a05JUMPDEST
0a06PUSH10x40
0a08MLOAD
0a09SWAP1
0a0aDUP2
0a0bMSTORE
0a0cRETURN
0a0dJUMPDEST
0a0eCALLVALUE
0a0fPUSH20x03b4
0a12JUMPI
0a13PUSH20x0100
0a16MLOAD
0a17CALLDATASIZE
0a18PUSH10x03
0a1aNOT
0a1bADD
0a1cSLT
0a1dPUSH20x03b4
0a20JUMPI
0a21PUSH10x20
0a23PUSH10x40
0a25MLOAD
0a26PUSH20x01f4
0a29DUP2
0a2aMSTORE
0a2bRETURN
0a2cJUMPDEST
0a2dCALLVALUE
0a2ePUSH20x03b4
0a31JUMPI
0a32PUSH20x02c0
0a35CALLDATASIZE
0a36PUSH10x03
0a38NOT
0a39ADD
0a3aSLT
0a3bPUSH20x03b4
0a3eJUMPI
0a3fPUSH10x40
0a41MLOAD
0a42PUSH20x0a4a
0a45DUP2
0a46PUSH20x5e9b
0a49JUMP
0a4aJUMPDEST
0a4bPUSH10x04
0a4dCALLDATALOAD
0a4eDUP2
0a4fMSTORE
0a50PUSH10x24
0a52CALLDATALOAD
0a53PUSH10x20
0a55DUP3
0a56ADD
0a57MSTORE
0a58PUSH10x44
0a5aCALLDATALOAD
0a5bPUSH10x40
0a5dDUP3
0a5eADD
0a5fMSTORE
0a60PUSH10x64
0a62CALLDATALOAD
0a63PUSH10x60
0a65DUP3
0a66ADD
0a67MSTORE
0a68PUSH10x84
0a6aCALLDATALOAD
0a6bPUSH10xff
0a6dDUP2
0a6eAND
0a6fDUP2
0a70SUB
0a71PUSH20x03b4
0a74JUMPI
0a75PUSH10x80
0a77DUP3
0a78ADD
0a79MSTORE
0a7aPUSH10xa4
0a7cCALLDATALOAD
0a7dPUSH10xa0
0a7fDUP3
0a80ADD
0a81MSTORE
0a82PUSH10xc4
0a84CALLDATALOAD
0a85PUSH10xc0
0a87DUP3
0a88ADD
0a89MSTORE
0a8aPUSH10xe4
0a8cCALLDATALOAD
0a8dPUSH10xff
0a8fDUP2
0a90AND
0a91DUP2
0a92SUB
0a93PUSH20x03b4
0a96JUMPI
0a97PUSH10xe0
0a99DUP3
0a9aADD
0a9bMSTORE
0a9cPUSH20x0104
0a9fCALLDATALOAD
0aa0PUSH10x01
0aa2PUSH10x01
0aa4PUSH10x40
0aa6SHL
0aa7SUB
0aa8DUP2
0aa9AND
0aaaDUP2
0aabSUB
0aacPUSH20x03b4
0aafJUMPI
0ab0PUSH20x0100
0ab3DUP3
0ab4ADD
0ab5MSTORE
0ab6PUSH20x0124
0ab9CALLDATALOAD
0abaPUSH40xffffffff
0abfDUP2
0ac0AND
0ac1DUP2
0ac2SUB
0ac3PUSH20x03b4
0ac6JUMPI
0ac7PUSH20x0120
0acaDUP3
0acbADD
0accMSTORE
0acdPUSH20x0144
0ad0CALLDATALOAD
0ad1PUSH20xffff
0ad4DUP2
0ad5AND
0ad6DUP2
0ad7SUB
0ad8PUSH20x03b4
0adbJUMPI
0adcPUSH20x0140
0adfDUP3
0ae0ADD
0ae1MSTORE
0ae2PUSH20x0164
0ae5CALLDATALOAD
0ae6PUSH20xffff
0ae9DUP2
0aeaAND
0aebDUP2
0aecSUB
0aedPUSH20x03b4
0af0JUMPI
0af1PUSH20x0160
0af4DUP3
0af5ADD
0af6MSTORE
0af7PUSH20x0184
0afaCALLDATALOAD
0afbPUSH20x0180
0afeDUP3
0affADD
0b00MSTORE
0b01PUSH20x01a4
0b04CALLDATALOAD
0b05PUSH10xff
0b07DUP2
0b08AND
0b09DUP2
0b0aSUB
0b0bPUSH20x03b4
0b0eJUMPI
0b0fPUSH20x01a0
0b12DUP3
0b13ADD
0b14MSTORE
0b15PUSH20x01c4
0b18CALLDATALOAD
0b19PUSH20x01c0
0b1cDUP3
0b1dADD
0b1eMSTORE
0b1fPUSH20x01e4
0b22CALLDATALOAD
0b23PUSH20x01e0
0b26DUP3
0b27ADD
0b28MSTORE
0b29PUSH20x0204
0b2cCALLDATALOAD
0b2dPUSH10xff
0b2fDUP2
0b30AND
0b31DUP2
0b32SUB
0b33PUSH20x03b4
0b36JUMPI
0b37PUSH20x0200
0b3aDUP3
0b3bADD
0b3cMSTORE
0b3dPUSH20x0224
0b40CALLDATALOAD
0b41DUP1
0b42ISZERO
0b43ISZERO
0b44DUP2
0b45SUB
0b46PUSH20x03b4
0b49JUMPI
0b4aPUSH20x0220
0b4dDUP3
0b4eADD
0b4fMSTORE
0b50PUSH20x0244
0b53CALLDATALOAD
0b54PUSH10x01
0b56PUSH10x01
0b58PUSH10x40
0b5aSHL
0b5bSUB
0b5cDUP2
0b5dAND
0b5eDUP2
0b5fSUB
0b60PUSH20x03b4
0b63JUMPI
0b64PUSH20x0240
0b67DUP3
0b68ADD
0b69MSTORE
0b6aPUSH20x0264
0b6dCALLDATALOAD
0b6ePUSH10x01
0b70PUSH10x01
0b72PUSH10x40
0b74SHL
0b75SUB
0b76DUP2
0b77AND
0b78DUP2
0b79SUB
0b7aPUSH20x03b4
0b7dJUMPI
0b7ePUSH20x0260
0b81DUP3
0b82ADD
0b83MSTORE
0b84PUSH20x0284
0b87CALLDATALOAD
0b88PUSH10xff
0b8aDUP2
0b8bAND
0b8cDUP2
0b8dSUB
0b8ePUSH20x03b4
0b91JUMPI
0b92DUP2
0b93PUSH20x0a05
0b96SWAP2
0b97PUSH20x0280
0b9aPUSH10x20
0b9cSWAP5
0b9dADD
0b9eMSTORE
0b9fPUSH20x02a4
0ba2CALLDATALOAD
0ba3PUSH20x02a0
0ba6DUP3
0ba7ADD
0ba8MSTORE
0ba9PUSH20x68a4
0bacJUMP
0badJUMPDEST
0baeCALLVALUE
0bafPUSH20x03b4
0bb2JUMPI
0bb3PUSH20x0100
0bb6MLOAD
0bb7CALLDATASIZE
0bb8PUSH10x03
0bbaNOT
0bbbADD
0bbcSLT
0bbdPUSH20x03b4
0bc0JUMPI
0bc1PUSH10x20
0bc3PUSH10x15
0bc5SLOAD
0bc6PUSH10x40
0bc8MLOAD
0bc9SWAP1
0bcaDUP2
0bcbMSTORE
0bccRETURN
0bcdJUMPDEST
0bceCALLVALUE
0bcfPUSH20x03b4
0bd2JUMPI
0bd3PUSH10xa0
0bd5CALLDATASIZE
0bd6PUSH10x03
0bd8NOT
0bd9ADD
0bdaSLT
0bdbPUSH20x03b4
0bdeJUMPI
0bdfPUSH20x0be6
0be2PUSH20x5cab
0be5JUMP
0be6JUMPDEST
0be7POP
0be8PUSH20x0bef
0bebPUSH20x5cc1
0beeJUMP
0befJUMPDEST
0bf0POP
0bf1PUSH10x44
0bf3CALLDATALOAD
0bf4PUSH10x01
0bf6PUSH10x01
0bf8PUSH10x40
0bfaSHL
0bfbSUB
0bfcDUP2
0bfdGT
0bfePUSH20x03b4
0c01JUMPI
0c02PUSH20x0c0f
0c05SWAP1
0c06CALLDATASIZE
0c07SWAP1
0c08PUSH10x04
0c0aADD
0c0bPUSH20x5baf
0c0eJUMP
0c0fJUMPDEST
0c10POP
0c11POP
0c12PUSH10x64
0c14CALLDATALOAD
0c15PUSH10x01
0c17PUSH10x01
0c19PUSH10x40
0c1bSHL
0c1cSUB
0c1dDUP2
0c1eGT
0c1fPUSH20x03b4
0c22JUMPI
0c23PUSH20x0c30
0c26SWAP1
0c27CALLDATASIZE
0c28SWAP1
0c29PUSH10x04
0c2bADD
0c2cPUSH20x5baf
0c2fJUMP
0c30JUMPDEST
0c31POP
0c32POP
0c33PUSH10x84
0c35CALLDATALOAD
0c36PUSH10x01
0c38PUSH10x01
0c3aPUSH10x40
0c3cSHL
0c3dSUB
0c3eDUP2
0c3fGT
0c40PUSH20x03b4
0c43JUMPI
0c44PUSH20x0c51
0c47SWAP1
0c48CALLDATASIZE
0c49SWAP1
0c4aPUSH10x04
0c4cADD
0c4dPUSH20x5cd7
0c50JUMP
0c51JUMPDEST
0c52POP
0c53POP
0c54PUSH10x40
0c56MLOAD
0c57PUSH40xbc197c81
0c5cPUSH10xe0
0c5eSHL
0c5fDUP2
0c60MSTORE
0c61PUSH10x20
0c63SWAP1
0c64RETURN
0c65JUMPDEST
0c66CALLVALUE
0c67PUSH20x03b4
0c6aJUMPI
0c6bPUSH10x20
0c6dCALLDATASIZE
0c6ePUSH10x03
0c70NOT
0c71ADD
0c72SLT
0c73PUSH20x03b4
0c76JUMPI
0c77PUSH10x20
0c79PUSH20x0a05
0c7cPUSH10x04
0c7eCALLDATALOAD
0c7fPUSH20x6871
0c82JUMP
0c83JUMPDEST
0c84CALLVALUE
0c85PUSH20x03b4
0c88JUMPI
0c89PUSH10xa0
0c8bCALLDATASIZE
0c8cPUSH10x03
0c8eNOT
0c8fADD
0c90SLT
0c91PUSH20x03b4
0c94JUMPI
0c95PUSH10x01
0c97PUSH10x01
0c99PUSH10x40
0c9bSHL
0c9cSUB
0c9dPUSH10x04
0c9fCALLDATALOAD
0ca0GT
0ca1PUSH20x03b4
0ca4JUMPI
0ca5CALLDATASIZE
0ca6PUSH10x23
0ca8PUSH10x04
0caaCALLDATALOAD
0cabADD
0cacSLT
0cadISZERO
0caePUSH20x03b4
0cb1JUMPI
0cb2PUSH10x01
0cb4PUSH10x01
0cb6PUSH10x40
0cb8SHL
0cb9SUB
0cbaPUSH10x04
0cbcCALLDATALOAD
0cbdPUSH10x04
0cbfADD
0cc0CALLDATALOAD
0cc1GT
0cc2PUSH20x03b4
0cc5JUMPI
0cc6CALLDATASIZE
0cc7PUSH10x24
0cc9PUSH20x02c0
0cccPUSH10x04
0cceCALLDATALOAD
0ccfPUSH10x04
0cd1ADD
0cd2CALLDATALOAD
0cd3MUL
0cd4PUSH10x04
0cd6CALLDATALOAD
0cd7ADD
0cd8ADD
0cd9GT
0cdaPUSH20x03b4
0cddJUMPI
0cdePUSH10x24
0ce0CALLDATALOAD
0ce1PUSH10x01
0ce3PUSH10x01
0ce5PUSH10x40
0ce7SHL
0ce8SUB
0ce9DUP2
0ceaGT
0cebPUSH20x03b4
0ceeJUMPI
0cefPUSH20x0cfc
0cf2SWAP1
0cf3CALLDATASIZE
0cf4SWAP1
0cf5PUSH10x04
0cf7ADD
0cf8PUSH20x5baf
0cfbJUMP
0cfcJUMPDEST
0cfdPUSH10xc0
0cffMSTORE
0d00PUSH10x44
0d02CALLDATALOAD
0d03PUSH10x01
0d05PUSH10x01
0d07PUSH10x40
0d09SHL
0d0aSUB
0d0bDUP2
0d0cGT
0d0dPUSH20x03b4
0d10JUMPI
0d11PUSH20x0d1e
0d14SWAP1
0d15CALLDATASIZE
0d16SWAP1
0d17PUSH10x04
0d19ADD
0d1aPUSH20x5d04
0d1dJUMP
0d1eJUMPDEST
0d1fPUSH10x64
0d21CALLDATALOAD
0d22SWAP3
0d23SWAP1
0d24PUSH10x01
0d26PUSH10x01
0d28PUSH10x40
0d2aSHL
0d2bSUB
0d2cDUP5
0d2dAND
0d2eDUP5
0d2fSUB
0d30PUSH20x03b4
0d33JUMPI
0d34PUSH10x84
0d36CALLDATALOAD
0d37PUSH10x01
0d39PUSH10x01
0d3bPUSH10x40
0d3dSHL
0d3eSUB
0d3fDUP2
0d40GT
0d41PUSH20x03b4
0d44JUMPI
0d45PUSH20x0d52
0d48SWAP1
0d49CALLDATASIZE
0d4aSWAP1
0d4bPUSH10x04
0d4dADD
0d4ePUSH20x5baf
0d51JUMP
0d52JUMPDEST
0d53SWAP5
0d54PUSH10x02
0d56SLOAD
0d57DUP1
0d58ISZERO
0d59PUSH20x2d0a
0d5cJUMPI
0d5dPUSH10x04
0d5fCALLDATALOAD
0d60PUSH10x04
0d62ADD
0d63CALLDATALOAD
0d64ISZERO
0d65DUP1
0d66PUSH20x2d00
0d69JUMPI
0d6aJUMPDEST
0d6bDUP1
0d6cPUSH20x2cf8
0d6fJUMPI
0d70JUMPDEST
0d71PUSH20x2ce3
0d74JUMPI
0d75PUSH10x03
0d77SLOAD
0d78SWAP3
0d79PUSH10x40
0d7bMLOAD
0d7cSWAP8
0d7dPUSH10xa0
0d7fDUP10
0d80ADD
0d81PUSH10x01
0d83PUSH10x01
0d85PUSH10x40
0d87SHL
0d88SUB
0d89DUP7
0d8aAND
0d8bPUSH10x20
0d8dDUP12
0d8eADD
0d8fMSTORE
0d90PUSH10x80
0d92PUSH10x40
0d94DUP12
0d95ADD
0d96MSTORE
0d97PUSH10x04
0d99CALLDATALOAD
0d9aPUSH10x04
0d9cADD
0d9dCALLDATALOAD
0d9eSWAP1
0d9fMSTORE
0da0PUSH10xc0
0da2DUP10
0da3ADD
0da4PUSH10x24
0da6PUSH10x04
0da8CALLDATALOAD
0da9ADD
0daaPUSH20x0100
0dadMLOAD
0daeJUMPDEST
0dafPUSH10x04
0db1CALLDATALOAD
0db2PUSH10x04
0db4ADD
0db5CALLDATALOAD
0db6DUP2
0db7LT
0db8PUSH20x2b50
0dbbJUMPI
0dbcPOP
0dbdPOP
0dbePUSH10x1f
0dc0NOT
0dc1DUP11
0dc2DUP3
0dc3SUB
0dc4ADD
0dc5PUSH10x60
0dc7DUP12
0dc8ADD
0dc9MSTORE
0dcaPUSH10xc0
0dccMLOAD
0dcdDUP2
0dceMSTORE
0dcfPUSH10x20
0dd1DUP2
0dd2ADD
0dd3PUSH10x20
0dd5PUSH10xc0
0dd7MLOAD
0dd8PUSH10x05
0ddaSHL
0ddbDUP4
0ddcADD
0dddADD
0ddeSWAP1
0ddfDUP10
0de0SWAP3
0de1PUSH20x0100
0de4MLOAD
0de5JUMPDEST
0de6PUSH10xc0
0de8MLOAD
0de9DUP2
0deaLT
0debPUSH20x2a4a
0deeJUMPI
0defPOP
0df0POP
0df1POP
0df2PUSH10x20
0df4SWAP2
0df5POP
0df6PUSH10x1f
0df8NOT
0df9DUP12
0dfaDUP3
0dfbSUB
0dfcADD
0dfdPUSH10x80
0dffDUP13
0e00ADD
0e01MSTORE
0e02DUP8
0e03DUP2
0e04MSTORE
0e05ADD
0e06SWAP9
0e07DUP9
0e08PUSH20x0100
0e0bMLOAD
0e0cJUMPDEST
0e0dDUP9
0e0eDUP2
0e0fLT
0e10PUSH20x29b3
0e13JUMPI
0e14POP
0e15POP
0e16PUSH20x0e2f
0e19DUP2
0e1aPUSH20x0ed6
0e1dSWAP8
0e1eSWAP9
0e1fSWAP10
0e20SWAP11
0e21SWAP12
0e22SUB
0e23PUSH10x1f
0e25NOT
0e26DUP2
0e27ADD
0e28DUP4
0e29MSTORE
0e2aDUP3
0e2bPUSH20x5eef
0e2eJUMP
0e2fJUMPDEST
0e30PUSH10x20
0e32DUP2
0e33MLOAD
0e34SWAP2
0e35ADD
0e36KECCAK256
0e37PUSH20x0100
0e3aMLOAD
0e3bPOP
0e3cPUSH10x40
0e3eMLOAD
0e3fPUSH10x20
0e41DUP2
0e42ADD
0e43SWAP2
0e44PUSH0
0e45MLOAD
0e46PUSH10x20
0e48PUSH20x7942
0e4bPUSH0
0e4cCODECOPY
0e4dPUSH0
0e4eMLOAD
0e4fSWAP1
0e50PUSH0
0e51MSTORE
0e52DUP4
0e53MSTORE
0e54CHAINID
0e55PUSH10x40
0e57DUP4
0e58ADD
0e59MSTORE
0e5aADDRESS
0e5bPUSH10x60
0e5dDUP4
0e5eADD
0e5fMSTORE
0e60PUSH320xe10634eb0bf7bd6ad00dc59a6c67fe9b09037b979405fa59137e7e530d601414
0e81PUSH10x80
0e83DUP4
0e84ADD
0e85MSTORE
0e86PUSH10x01
0e88PUSH10x01
0e8aPUSH10x40
0e8cSHL
0e8dSUB
0e8eDUP8
0e8fAND
0e90PUSH10xa0
0e92DUP4
0e93ADD
0e94MSTORE
0e95PUSH10xc0
0e97DUP3
0e98ADD
0e99MSTORE
0e9aPUSH10xc0
0e9cDUP2
0e9dMSTORE
0e9ePUSH20x0ea8
0ea1PUSH10xe0
0ea3DUP3
0ea4PUSH20x5eef
0ea7JUMP
0ea8JUMPDEST
0ea9MLOAD
0eaaSWAP1
0eabKECCAK256
0eacSWAP1
0eadPUSH10x01
0eafSLOAD
0eb0SWAP3
0eb1PUSH320x0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d540
0ed2PUSH20x6a76
0ed5JUMP
0ed6JUMPDEST
0ed7POP
0ed8PUSH10x01
0edaPUSH10x01
0edcPUSH10x40
0edeSHL
0edfSUB
0ee0PUSH20x0eea
0ee3DUP2
0ee4DUP4
0ee5AND
0ee6PUSH20x608b
0ee9JUMP
0eeaJUMPDEST
0eebAND
0eecSWAP1
0eedPUSH10x01
0eefPUSH10x01
0ef1PUSH10x40
0ef3SHL
0ef4SUB
0ef5NOT
0ef6AND
0ef7OR
0ef8PUSH10x03
0efaSSTORE
0efbPUSH10x17
0efdSLOAD
0efePUSH20x0f0f
0f01PUSH10x01
0f03PUSH10x01
0f05PUSH10x40
0f07SHL
0f08SUB
0f09DUP3
0f0aAND
0f0bPUSH20x608b
0f0eJUMP
0f0fJUMPDEST
0f10PUSH10xa0
0f12MSTORE
0f13PUSH10x01
0f15PUSH10x01
0f17PUSH10x40
0f19SHL
0f1aSUB
0f1bPUSH10xa0
0f1dMLOAD
0f1eAND
0f1fSWAP1
0f20PUSH10x01
0f22PUSH10x01
0f24PUSH10x40
0f26SHL
0f27SUB
0f28NOT
0f29AND
0f2aOR
0f2bPUSH10x17
0f2dSSTORE
0f2ePUSH20x0f3f
0f31PUSH10xc0
0f33MLOAD
0f34PUSH10x04
0f36CALLDATALOAD
0f37PUSH10x04
0f39ADD
0f3aCALLDATALOAD
0f3bPUSH20x62af
0f3eJUMP
0f3fJUMPDEST
0f40PUSH10x01
0f42PUSH10x01
0f44PUSH10xff
0f46SHL
0f47SUB
0f48DUP3
0f49AND
0f4aDUP3
0f4bSUB
0f4cPUSH20x2999
0f4fJUMPI
0f50PUSH20x0f60
0f53PUSH20x0f71
0f56SWAP2
0f57DUP4
0f58PUSH10x01
0f5aSHL
0f5bSWAP1
0f5cPUSH20x62af
0f5fJUMP
0f60JUMPDEST
0f61PUSH20x0f69
0f64DUP2
0f65PUSH20x60d4
0f68JUMP
0f69JUMPDEST
0f6aPUSH10xe0
0f6cMSTORE
0f6dPUSH20x60d4
0f70JUMP
0f71JUMPDEST
0f72PUSH20x0100
0f75MLOAD
0f76SWAP3
0f77SWAP1
0f78SWAP2
0f79SWAP1
0f7aDUP4
0f7bJUMPDEST
0f7cPUSH10x04
0f7eCALLDATALOAD
0f7fPUSH10x04
0f81ADD
0f82CALLDATALOAD
0f83DUP6
0f84LT
0f85ISZERO
0f86PUSH20x18a9
0f89JUMPI
0f8aPUSH20x0fa4
0f8dPUSH10x04
0f8fCALLDATALOAD
0f90PUSH20x02c0
0f93DUP8
0f94MUL
0f95ADD
0f96PUSH10x64
0f98DUP2
0f99ADD
0f9aCALLDATALOAD
0f9bSWAP1
0f9cPUSH10x44
0f9eADD
0f9fCALLDATALOAD
0fa0PUSH20x6966
0fa3JUMP
0fa4JUMPDEST
0fa5PUSH10x24
0fa7PUSH20x02c0
0faaDUP8
0fabMUL
0facPUSH10x04
0faeCALLDATALOAD
0fafADD
0fb0ADD
0fb1CALLDATALOAD
0fb2SUB
0fb3PUSH20x1867
0fb6JUMPI
0fb7PUSH20x0fcb
0fbaPUSH20x0244
0fbdPUSH20x02c0
0fc0DUP8
0fc1MUL
0fc2PUSH10x04
0fc4CALLDATALOAD
0fc5ADD
0fc6ADD
0fc7PUSH20x62bc
0fcaJUMP
0fcbJUMPDEST
0fccDUP1
0fcdPUSH20x17da
0fd0JUMPI
0fd1JUMPDEST
0fd2PUSH20x17b5
0fd5JUMPI
0fd6PUSH10x01
0fd8PUSH10xff
0fdaPUSH20x0fee
0fddPUSH20x02a4
0fe0PUSH20x02c0
0fe3DUP10
0fe4MUL
0fe5PUSH10x04
0fe7CALLDATALOAD
0fe8ADD
0fe9ADD
0feaPUSH20x62c9
0fedJUMP
0feeJUMPDEST
0fefAND
0ff0GT
0ff1PUSH20x1776
0ff4JUMPI
0ff5PUSH20x1009
0ff8PUSH20x0244
0ffbPUSH20x02c0
0ffeDUP8
0fffMUL
1000PUSH10x04
1002CALLDATALOAD
1003ADD
1004ADD
1005PUSH20x62bc
1008JUMP
1009JUMPDEST
100aDUP1
100bPUSH20x1757
100eJUMPI
100fJUMPDEST
1010DUP1
1011PUSH20x1743
1014JUMPI
1015JUMPDEST
1016PUSH20x171e
1019JUMPI
101aPUSH10x04
101cCALLDATALOAD
101dPUSH20x02c0
1020DUP7
1021MUL
1022ADD
1023PUSH10x44
1025ADD
1026CALLDATALOAD
1027PUSH0
1028MLOAD
1029PUSH10x20
102bPUSH20x7922
102ePUSH0
102fCODECOPY
1030PUSH0
1031MLOAD
1032SWAP1
1033PUSH0
1034MSTORE
1035EQ
1036DUP1
1037DUP1
1038PUSH20x16fc
103bJUMPI
103cJUMPDEST
103dPUSH20x16bd
1040JUMPI
1041PUSH20x1676
1044JUMPI
1045JUMPDEST
1046PUSH20x01e4
1049PUSH20x02c0
104cDUP7
104dMUL
104ePUSH10x04
1050CALLDATALOAD
1051ADD
1052ADD
1053CALLDATALOAD
1054ISZERO
1055ISZERO
1056DUP1
1057PUSH20x164c
105aJUMPI
105bJUMPDEST
105cPUSH20x1626
105fJUMPI
1060PUSH10x24
1062PUSH20x02c0
1065DUP7
1066MUL
1067PUSH10x04
1069CALLDATALOAD
106aADD
106bADD
106cCALLDATALOAD
106dPUSH20x0100
1070MLOAD
1071MSTORE
1072PUSH10x06
1074PUSH10x20
1076MSTORE
1077PUSH10xff
1079PUSH10x40
107bPUSH20x0100
107eMLOAD
107fKECCAK256
1080SLOAD
1081AND
1082ISZERO
1083PUSH20x1593
1086JUMPI
1087JUMPDEST
1088PUSH20x0100
108bDUP1
108cMLOAD
108dPUSH20x02c0
1090DUP8
1091MUL
1092PUSH10x04
1094CALLDATALOAD
1095ADD
1096PUSH10x24
1098DUP2
1099ADD
109aCALLDATALOAD
109bSWAP2
109cDUP3
109dSWAP1
109eMSTORE
109fPUSH10x05
10a1PUSH10x20
10a3MSTORE
10a4SWAP2
10a5MLOAD
10a6PUSH10x40
10a8SWAP1
10a9KECCAK256
10aaSWAP1
10abDUP2
10acSSTORE
10adPUSH10x44
10afDUP3
10b0ADD
10b1CALLDATALOAD
10b2PUSH10x01
10b4DUP3
10b5ADD
10b6SSTORE
10b7PUSH10x64
10b9DUP3
10baADD
10bbCALLDATALOAD
10bcPUSH10x02
10beDUP3
10bfADD
10c0SWAP1
10c1DUP2
10c2SSTORE
10c3PUSH10x84
10c5DUP4
10c6ADD
10c7CALLDATALOAD
10c8PUSH10x03
10caDUP4
10cbADD
10ccSSTORE
10cdSWAP1
10ceSWAP2
10cfPUSH20x10da
10d2SWAP1
10d3PUSH10xa4
10d5ADD
10d6PUSH20x62c9
10d9JUMP
10daJUMPDEST
10dbPUSH10x04
10ddDUP4
10deDUP2
10dfADD
10e0DUP1
10e1SLOAD
10e2PUSH10xff
10e4NOT
10e5AND
10e6PUSH10xff
10e8SWAP4
10e9SWAP1
10eaSWAP4
10ebAND
10ecSWAP3
10edSWAP1
10eeSWAP3
10efOR
10f0SWAP1
10f1SWAP2
10f2SSTORE
10f3PUSH10xc4
10f5SWAP1
10f6CALLDATALOAD
10f7PUSH20x02c0
10faDUP10
10fbMUL
10fcADD
10fdSWAP1
10feDUP2
10ffADD
1100CALLDATALOAD
1101PUSH10x05
1103DUP5
1104ADD
1105SSTORE
1106PUSH10xe4
1108DUP2
1109ADD
110aCALLDATALOAD
110bPUSH10x06
110dDUP5
110eADD
110fSSTORE
1110PUSH20x111c
1113SWAP1
1114PUSH20x0104
1117ADD
1118PUSH20x62c9
111bJUMP
111cJUMPDEST
111dPUSH10xff
111fAND
1120PUSH10xff
1122NOT
1123PUSH10x07
1125DUP5
1126ADD
1127SLOAD
1128AND
1129OR
112aPUSH10x07
112cDUP4
112dADD
112eSSTORE
112fPUSH20x02c0
1132DUP8
1133MUL
1134PUSH10x04
1136CALLDATALOAD
1137ADD
1138PUSH20x0124
113bADD
113cPUSH20x1144
113fSWAP1
1140PUSH20x685d
1143JUMP
1144JUMPDEST
1145PUSH10x07
1147DUP4
1148ADD
1149SWAP1
114aPUSH20x1171
114dSWAP2
114eSWAP1
114fPUSH90xffffffffffffffff00
1159DUP3
115aSLOAD
115bSWAP2
115cPUSH10x08
115eSHL
115fAND
1160SWAP1
1161PUSH90xffffffffffffffff00
116bNOT
116cAND
116dOR
116eSWAP1
116fSSTORE
1170JUMP
1171JUMPDEST
1172PUSH20x1186
1175PUSH20x0144
1178PUSH10x04
117aCALLDATALOAD
117bPUSH20x02c0
117eDUP11
117fMUL
1180ADD
1181ADD
1182PUSH20x62f4
1185JUMP
1186JUMPDEST
1187PUSH10x07
1189DUP4
118aADD
118bSLOAD
118cPUSH20xffff
118fPUSH10x68
1191SHL
1192PUSH20x11a6
1195PUSH20x0164
1198PUSH10x04
119aCALLDATALOAD
119bPUSH20x02c0
119eDUP14
119fMUL
11a0ADD
11a1ADD
11a2PUSH20x62e5
11a5JUMP
11a6JUMPDEST
11a7PUSH10x68
11a9SHL
11aaAND
11abSWAP1
11acPUSH20xffff
11afPUSH10x78
11b1SHL
11b2PUSH20x11c6
11b5PUSH20x0184
11b8PUSH10x04
11baCALLDATALOAD
11bbPUSH20x02c0
11beDUP15
11bfMUL
11c0ADD
11c1ADD
11c2PUSH20x62e5
11c5JUMP
11c6JUMPDEST
11c7PUSH10x78
11c9SHL
11caAND
11cbSWAP3
11ccPUSH10x48
11ceSHL
11cfPUSH130xffffffff000000000000000000
11ddAND
11deSWAP1
11dfPUSH80xffffffffffffffff
11e8PUSH10x48
11eaSHL
11ebNOT
11ecAND
11edOR
11eeOR
11efOR
11f0PUSH10x07
11f2DUP4
11f3ADD
11f4SSTORE
11f5PUSH20x02c0
11f8DUP8
11f9MUL
11faPUSH10x04
11fcCALLDATALOAD
11fdADD
11fePUSH20x01a4
1201ADD
1202CALLDATALOAD
1203PUSH10x08
1205DUP4
1206ADD
1207SSTORE
1208PUSH20x02c0
120bDUP8
120cMUL
120dPUSH10x04
120fCALLDATALOAD
1210ADD
1211PUSH20x01c4
1214ADD
1215PUSH20x121d
1218SWAP1
1219PUSH20x62c9
121cJUMP
121dJUMPDEST
121ePUSH10x09
1220DUP4
1221ADD
1222DUP1
1223SLOAD
1224PUSH10xff
1226NOT
1227AND
1228PUSH10xff
122aSWAP3
122bSWAP1
122cSWAP3
122dAND
122eSWAP2
122fSWAP1
1230SWAP2
1231OR
1232SWAP1
1233SSTORE
1234PUSH20x01e4
1237PUSH10x04
1239CALLDATALOAD
123aPUSH20x02c0
123dDUP10
123eMUL
123fADD
1240SWAP1
1241DUP2
1242ADD
1243CALLDATALOAD
1244PUSH10x0a
1246DUP5
1247ADD
1248SSTORE
1249PUSH20x0204
124cDUP2
124dADD
124eCALLDATALOAD
124fPUSH10x0b
1251DUP5
1252ADD
1253SSTORE
1254PUSH20x1260
1257SWAP1
1258PUSH20x0224
125bADD
125cPUSH20x62c9
125fJUMP
1260JUMPDEST
1261PUSH10xff
1263AND
1264PUSH10xff
1266NOT
1267PUSH10x0c
1269DUP5
126aADD
126bSLOAD
126cAND
126dOR
126ePUSH10x0c
1270DUP4
1271ADD
1272SSTORE
1273PUSH20x02c0
1276DUP8
1277MUL
1278PUSH10x04
127aCALLDATALOAD
127bADD
127cPUSH20x0244
127fADD
1280PUSH20x1288
1283SWAP1
1284PUSH20x62bc
1287JUMP
1288JUMPDEST
1289PUSH10x0c
128bDUP4
128cADD
128dDUP1
128eSLOAD
128fPUSH10xa0
1291MLOAD
1292PUSH100xffffffffffffffff0000
129dPUSH10x10
129fSWAP2
12a0SWAP1
12a1SWAP2
12a2SHL
12a3AND
12a4SWAP3
12a5ISZERO
12a6ISZERO
12a7PUSH10x08
12a9SHL
12aaPUSH20xff00
12adAND
12aePUSH100xffffffffffffffffff00
12b9NOT
12baSWAP1
12bbSWAP2
12bcAND
12bdOR
12beSWAP2
12bfSWAP1
12c0SWAP2
12c1OR
12c2SWAP1
12c3SSTORE
12c4PUSH20x12d8
12c7PUSH20x0284
12caPUSH10x04
12ccCALLDATALOAD
12cdPUSH20x02c0
12d0DUP11
12d1MUL
12d2ADD
12d3ADD
12d4PUSH20x685d
12d7JUMP
12d8JUMPDEST
12d9PUSH10x0c
12dbDUP4
12dcADD
12ddDUP1
12deSLOAD
12dfPUSH80xffffffffffffffff
12e8PUSH10x50
12eaSHL
12ebNOT
12ecAND
12edPUSH10x50
12efSWAP3
12f0SWAP1
12f1SWAP3
12f2SHL
12f3PUSH80xffffffffffffffff
12fcPUSH10x50
12feSHL
12ffAND
1300SWAP2
1301SWAP1
1302SWAP2
1303OR
1304SWAP1
1305SSTORE
1306PUSH20x131a
1309PUSH20x02a4
130cPUSH10x04
130eCALLDATALOAD
130fPUSH20x02c0
1312DUP11
1313MUL
1314ADD
1315ADD
1316PUSH20x62c9
1319JUMP
131aJUMPDEST
131bPUSH10x0c
131dDUP4
131eADD
131fDUP1
1320SLOAD
1321PUSH10xff
1323PUSH10x90
1325SHL
1326PUSH10x90
1328DUP5
1329SWAP1
132aSHL
132bAND
132cPUSH10xff
132ePUSH10x90
1330SHL
1331NOT
1332DUP3
1333AND
1334OR
1335SWAP1
1336SWAP2
1337SSTORE
1338PUSH20x02c0
133bDUP10
133cMUL
133dPUSH10x04
133fCALLDATALOAD
1340ADD
1341PUSH20x02c4
1344DUP2
1345ADD
1346CALLDATALOAD
1347PUSH10x0d
1349DUP7
134aADD
134bSSTORE
134cSWAP1
134dSWAP3
134eSWAP1
134fPUSH20x135b
1352SWAP1
1353PUSH10x24
1355ADD
1356CALLDATALOAD
1357PUSH20x61e9
135aJUMP
135bJUMPDEST
135cDUP6
135dPUSH10xe0
135fMLOAD
1360SWAP1
1361PUSH20x1369
1364SWAP2
1365PUSH20x61a2
1368JUMP
1369JUMPDEST
136aMSTORE
136bDUP4
136cSLOAD
136dSWAP4
136ePUSH10x01
1370DUP2
1371ADD
1372SLOAD
1373SWAP2
1374SLOAD
1375PUSH10x03
1377DUP3
1378ADD
1379SLOAD
137aPUSH10x04
137cDUP4
137dADD
137eSLOAD
137fPUSH10xff
1381AND
1382PUSH10x05
1384DUP5
1385ADD
1386SLOAD
1387PUSH10x06
1389DUP6
138aADD
138bSLOAD
138cSWAP1
138dPUSH10x07
138fDUP7
1390ADD
1391SLOAD
1392SWAP3
1393PUSH10x08
1395DUP8
1396ADD
1397SLOAD
1398SWAP5
1399PUSH10x09
139bDUP9
139cADD
139dSLOAD
139ePUSH10xff
13a0AND
13a1SWAP7
13a2PUSH10x0a
13a4DUP10
13a5ADD
13a6SLOAD
13a7SWAP9
13a8PUSH10x0b
13aaADD
13abSLOAD
13acSWAP10
13adPUSH10x40
13afMLOAD
13b0DUP1
13b1SWAP15
13b2PUSH10x20
13b4DUP3
13b5ADD
13b6PUSH0
13b7MLOAD
13b8PUSH10x20
13baPUSH20x7962
13bdPUSH0
13beCODECOPY
13bfPUSH0
13c0MLOAD
13c1SWAP1
13c2PUSH0
13c3MSTORE
13c4SWAP1
13c5MSTORE
13c6PUSH20x0100
13c9MLOAD
13caPUSH10x40
13ccDUP4
13cdADD
13ceMSTORE
13cfPUSH10x60
13d1DUP3
13d2ADD
13d3MSTORE
13d4PUSH10x80
13d6ADD
13d7MSTORE
13d8PUSH10xa0
13daDUP14
13dbADD
13dcMSTORE
13ddPUSH10xc0
13dfDUP13
13e0ADD
13e1MSTORE
13e2PUSH10xe0
13e4DUP12
13e5ADD
13e6MSTORE
13e7PUSH20x0100
13eaDUP11
13ebADD
13ecMSTORE
13edPUSH20x0120
13f0DUP10
13f1ADD
13f2MSTORE
13f3PUSH10xff
13f5DUP2
13f6AND
13f7PUSH20x0140
13faDUP10
13fbADD
13fcMSTORE
13fdDUP1
13fePUSH10x08
1400SHR
1401PUSH10x01
1403PUSH10x01
1405PUSH10x40
1407SHL
1408SUB
1409AND
140aPUSH20x0160
140dDUP10
140eADD
140fMSTORE
1410DUP1
1411PUSH10x48
1413SHR
1414PUSH40xffffffff
1419AND
141aPUSH20x0180
141dDUP10
141eADD
141fMSTORE
1420DUP1
1421PUSH10x68
1423SHR
1424PUSH20xffff
1427AND
1428PUSH20x01a0
142bDUP10
142cADD
142dMSTORE
142ePUSH10x78
1430SHR
1431PUSH20xffff
1434AND
1435PUSH20x01c0
1438DUP9
1439ADD
143aMSTORE
143bPUSH20x01e0
143eDUP8
143fADD
1440MSTORE
1441PUSH20x0200
1444DUP7
1445ADD
1446MSTORE
1447PUSH20x0220
144aDUP6
144bADD
144cMSTORE
144dPUSH20x0240
1450DUP5
1451ADD
1452MSTORE
1453PUSH10xff
1455DUP3
1456AND
1457PUSH20x0260
145aDUP5
145bADD
145cMSTORE
145dPUSH10xff
145fPUSH10x90
1461SHL
1462DUP2
1463PUSH10x90
1465SHL
1466AND
1467PUSH10xff
1469PUSH10x90
146bSHL
146cNOT
146dDUP4
146eAND
146fOR
1470PUSH10x08
1472SHR
1473PUSH10xff
1475AND
1476ISZERO
1477ISZERO
1478PUSH20x0280
147bDUP5
147cADD
147dMSTORE
147ePUSH10xff
1480PUSH10x90
1482SHL
1483DUP2
1484PUSH10x90
1486SHL
1487AND
1488PUSH10xff
148aPUSH10x90
148cSHL
148dNOT
148eDUP4
148fAND
1490OR
1491PUSH10x10
1493SHR
1494PUSH10x01
1496PUSH10x01
1498PUSH10x40
149aSHL
149bSUB
149cAND
149dPUSH20x02a0
14a0DUP5
14a1ADD
14a2MSTORE
14a3PUSH10xff
14a5PUSH10x90
14a7SHL
14a8DUP2
14a9PUSH10x90
14abSHL
14acAND
14adPUSH10xff
14afPUSH10x90
14b1SHL
14b2NOT
14b3DUP4
14b4AND
14b5OR
14b6PUSH10x50
14b8SHR
14b9PUSH10x01
14bbPUSH10x01
14bdPUSH10x40
14bfSHL
14c0SUB
14c1AND
14c2PUSH20x02c0
14c5DUP5
14c6ADD
14c7MSTORE
14c8PUSH10xff
14caPUSH10x90
14ccSHL
14cdSWAP1
14cePUSH10x90
14d0SHL
14d1AND
14d2SWAP1
14d3PUSH10xff
14d5PUSH10x90
14d7SHL
14d8NOT
14d9AND
14daOR
14dbPUSH10x90
14ddSHR
14dePUSH10xff
14e0AND
14e1PUSH20x02e0
14e4DUP3
14e5ADD
14e6MSTORE
14e7PUSH20x02c0
14eaDUP7
14ebMUL
14ecPUSH10x04
14eeCALLDATALOAD
14efADD
14f0PUSH20x02c4
14f3ADD
14f4CALLDATALOAD
14f5PUSH20x0300
14f8DUP3
14f9ADD
14faMSTORE
14fbPUSH20x0300
14feDUP2
14ffMSTORE
1500PUSH20x150b
1503PUSH20x0320
1506DUP3
1507PUSH20x5eef
150aJUMP
150bJUMPDEST
150cDUP1
150dMLOAD
150eSWAP1
150fPUSH10x20
1511ADD
1512KECCAK256
1513PUSH20x151c
1516DUP3
1517DUP7
1518PUSH20x61a2
151bJUMP
151cJUMPDEST
151dMSTORE
151ePUSH20x1526
1521SWAP1
1522PUSH20x62d7
1525JUMP
1526JUMPDEST
1527SWAP4
1528PUSH20x153c
152bPUSH20x0244
152ePUSH10x04
1530CALLDATALOAD
1531PUSH20x02c0
1534DUP5
1535MUL
1536ADD
1537ADD
1538PUSH20x62bc
153bJUMP
153cJUMPDEST
153dPUSH10x40
153fDUP1
1540MLOAD
1541PUSH10xa0
1543MLOAD
1544SWAP3
1545ISZERO
1546ISZERO
1547DUP2
1548MSTORE
1549PUSH10x01
154bPUSH10x01
154dPUSH10x40
154fSHL
1550SUB
1551SWAP1
1552SWAP3
1553AND
1554PUSH10x20
1556DUP4
1557ADD
1558MSTORE
1559PUSH20x02c0
155cDUP4
155dMUL
155ePUSH10x04
1560CALLDATALOAD
1561ADD
1562PUSH10x24
1564ADD
1565CALLDATALOAD
1566SWAP2
1567PUSH320x523b72ac5fbe8982a57ea63c8ca4daf1602c22eda42d6cc14a884c0310698b80
1588SWAP2
1589SWAP1
158aLOG2
158bPUSH10x01
158dADD
158eSWAP4
158fPUSH20x0f7b
1592JUMP
1593JUMPDEST
1594PUSH10x24
1596PUSH20x02c0
1599DUP7
159aMUL
159bPUSH10x04
159dCALLDATALOAD
159eADD
159fADD
15a0CALLDATALOAD
15a1PUSH20x0100
15a4MLOAD
15a5MSTORE
15a6PUSH10x06
15a8PUSH10x20
15aaMSTORE
15abPUSH10x40
15adPUSH20x0100
15b0MLOAD
15b1KECCAK256
15b2PUSH10x01
15b4PUSH10xff
15b6NOT
15b7DUP3
15b8SLOAD
15b9AND
15baOR
15bbSWAP1
15bcSSTORE
15bdPUSH10x04
15bfSLOAD
15c0PUSH10x01
15c2PUSH10x40
15c4SHL
15c5DUP2
15c6LT
15c7ISZERO
15c8PUSH20x160c
15cbJUMPI
15ccPUSH20x15e0
15cfDUP2
15d0PUSH10x01
15d2PUSH20x1605
15d5SWAP4
15d6ADD
15d7PUSH10x04
15d9SSTORE
15daPUSH10x04
15dcPUSH20x614a
15dfJUMP
15e0JUMPDEST
15e1PUSH10x24
15e3PUSH20x02c0
15e6DUP10
15e7SWAP5
15e8SWAP4
15e9SWAP5
15eaMUL
15ebPUSH10x04
15edCALLDATALOAD
15eeADD
15efADD
15f0CALLDATALOAD
15f1SWAP1
15f2DUP4
15f3SLOAD
15f4SWAP1
15f5PUSH10x03
15f7SHL
15f8SWAP2
15f9DUP3
15faSHL
15fbSWAP2
15fcPUSH0
15fdNOT
15feSWAP1
15ffSHL
1600NOT
1601AND
1602OR
1603SWAP1
1604JUMP
1605JUMPDEST
1606SWAP1
1607SSTORE
1608PUSH20x1087
160bJUMP
160cJUMPDEST
160dPUSH40x4e487b71
1612PUSH10xe0
1614SHL
1615PUSH20x0100
1618MLOAD
1619MSTORE
161aPUSH10x41
161cPUSH10x04
161eMSTORE
161fPUSH10x24
1621PUSH20x0100
1624MLOAD
1625REVERT
1626JUMPDEST
1627PUSH20x01e4
162aPUSH20x02c0
162dDUP7
162ePUSH40x3f4b99b1
1633PUSH10xe2
1635SHL
1636PUSH20x0100
1639MLOAD
163aMSTORE
163bMUL
163cPUSH10x04
163eCALLDATALOAD
163fADD
1640ADD
1641CALLDATALOAD
1642PUSH10x04
1644MSTORE
1645PUSH10x24
1647PUSH20x0100
164aMLOAD
164bREVERT
164cJUMPDEST
164dPOP
164ePUSH20x01e4
1651PUSH20x02c0
1654DUP7
1655MUL
1656PUSH10x04
1658CALLDATALOAD
1659ADD
165aADD
165bCALLDATALOAD
165cPUSH20x0100
165fMLOAD
1660MSTORE
1661PUSH10x06
1663PUSH10x20
1665MSTORE
1666PUSH10xff
1668PUSH10x40
166aPUSH20x0100
166dMLOAD
166eKECCAK256
166fSLOAD
1670AND
1671ISZERO
1672PUSH20x105b
1675JUMP
1676JUMPDEST
1677PUSH10x40
1679DUP1
167aMLOAD
167bPUSH20x16b8
167eSWAP2
167fPUSH20x1688
1682SWAP1
1683DUP3
1684PUSH20x5eef
1687JUMP
1688JUMPDEST
1689PUSH10x09
168bDUP2
168cMSTORE
168dPUSH90x3830bcb6b0b9ba32b9
1697PUSH10xb9
1699SHL
169aPUSH10x20
169cDUP3
169dADD
169eMSTORE
169fPUSH10x04
16a1CALLDATALOAD
16a2PUSH20x02c0
16a5DUP9
16a6MUL
16a7ADD
16a8PUSH20x02c4
16abDUP2
16acADD
16adCALLDATALOAD
16aeSWAP2
16afSWAP1
16b0PUSH10x24
16b2ADD
16b3CALLDATALOAD
16b4PUSH20x7108
16b7JUMP
16b8JUMPDEST
16b9PUSH20x1045
16bcJUMP
16bdJUMPDEST
16bePUSH10xff
16c0DUP7
16c1PUSH10x24
16c3PUSH20x02c0
16c6PUSH20x16d8
16c9PUSH20x0224
16ccDUP3
16cdDUP6
16ceMUL
16cfPUSH10x04
16d1CALLDATALOAD
16d2ADD
16d3ADD
16d4PUSH20x62c9
16d7JUMP
16d8JUMPDEST
16d9SWAP3
16daPUSH40x1e9a7d75
16dfPUSH10xe0
16e1SHL
16e2PUSH20x0100
16e5MLOAD
16e6MSTORE
16e7MUL
16e8PUSH10x04
16eaCALLDATALOAD
16ebADD
16ecADD
16edCALLDATALOAD
16eePUSH10x04
16f0MSTORE
16f1AND
16f2PUSH10x24
16f4MSTORE
16f5PUSH10x44
16f7PUSH20x0100
16faMLOAD
16fbREVERT
16fcJUMPDEST
16fdPOP
16fePUSH10x01
1700PUSH10xff
1702PUSH20x1716
1705PUSH20x0224
1708PUSH20x02c0
170bDUP11
170cMUL
170dPUSH10x04
170fCALLDATALOAD
1710ADD
1711ADD
1712PUSH20x62c9
1715JUMP
1716JUMPDEST
1717AND
1718EQ
1719ISZERO
171aPUSH20x103c
171dJUMP
171eJUMPDEST
171fPUSH10x24
1721PUSH20x02c0
1724DUP7
1725PUSH40x2eb72ead
172aPUSH10xe1
172cSHL
172dPUSH20x0100
1730MLOAD
1731MSTORE
1732MUL
1733PUSH10x04
1735CALLDATALOAD
1736ADD
1737ADD
1738CALLDATALOAD
1739PUSH10x04
173bMSTORE
173cPUSH10x24
173ePUSH20x0100
1741MLOAD
1742REVERT
1743JUMPDEST
1744POP
1745PUSH10x84
1747PUSH20x02c0
174aDUP7
174bMUL
174cPUSH10x04
174eCALLDATALOAD
174fADD
1750ADD
1751CALLDATALOAD
1752ISZERO
1753PUSH20x1015
1756JUMP
1757JUMPDEST
1758POP
1759PUSH10xff
175bPUSH20x176f
175ePUSH20x02a4
1761PUSH20x02c0
1764DUP9
1765MUL
1766PUSH10x04
1768CALLDATALOAD
1769ADD
176aADD
176bPUSH20x62c9
176eJUMP
176fJUMPDEST
1770AND
1771ISZERO
1772PUSH20x100f
1775JUMP
1776JUMPDEST
1777PUSH10xff
1779DUP6
177aPUSH10x24
177cPUSH20x02c0
177fPUSH20x1791
1782PUSH20x02a4
1785DUP3
1786DUP6
1787MUL
1788PUSH10x04
178aCALLDATALOAD
178bADD
178cADD
178dPUSH20x62c9
1790JUMP
1791JUMPDEST
1792SWAP3
1793PUSH40x1a7ce1dd
1798PUSH10xe0
179aSHL
179bPUSH20x0100
179eMLOAD
179fMSTORE
17a0MUL
17a1PUSH10x04
17a3CALLDATALOAD
17a4ADD
17a5ADD
17a6CALLDATALOAD
17a7PUSH10x04
17a9MSTORE
17aaAND
17abPUSH10x24
17adMSTORE
17aePUSH10x44
17b0PUSH20x0100
17b3MLOAD
17b4REVERT
17b5JUMPDEST
17b6PUSH10x24
17b8PUSH20x02c0
17bbDUP7
17bcPUSH40x3dc2fc11
17c1PUSH10xe0
17c3SHL
17c4PUSH20x0100
17c7MLOAD
17c8MSTORE
17c9MUL
17caPUSH10x04
17ccCALLDATALOAD
17cdADD
17ceADD
17cfCALLDATALOAD
17d0PUSH10x04
17d2MSTORE
17d3PUSH10x24
17d5PUSH20x0100
17d8MLOAD
17d9REVERT
17daJUMPDEST
17dbPOP
17dcPUSH40xffffffff
17e1PUSH20x17f5
17e4PUSH20x0144
17e7PUSH20x02c0
17eaDUP9
17ebMUL
17ecPUSH10x04
17eeCALLDATALOAD
17efADD
17f0ADD
17f1PUSH20x62f4
17f4JUMP
17f5JUMPDEST
17f6AND
17f7ISZERO
17f8DUP1
17f9ISZERO
17faPUSH20x1848
17fdJUMPI
17feJUMPDEST
17ffDUP1
1800ISZERO
1801PUSH20x1829
1804JUMPI
1805JUMPDEST
1806DUP1
1807PUSH20x0fd1
180aJUMPI
180bPOP
180cPUSH10xff
180ePUSH20x1822
1811PUSH20x0224
1814PUSH20x02c0
1817DUP9
1818MUL
1819PUSH10x04
181bCALLDATALOAD
181cADD
181dADD
181ePUSH20x62c9
1821JUMP
1822JUMPDEST
1823AND
1824ISZERO
1825PUSH20x0fd1
1828JUMP
1829JUMPDEST
182aPOP
182bPUSH10xff
182dPUSH20x1841
1830PUSH20x01c4
1833PUSH20x02c0
1836DUP9
1837MUL
1838PUSH10x04
183aCALLDATALOAD
183bADD
183cADD
183dPUSH20x62c9
1840JUMP
1841JUMPDEST
1842AND
1843ISZERO
1844PUSH20x1805
1847JUMP
1848JUMPDEST
1849POP
184aPUSH10xff
184cPUSH20x1860
184fPUSH20x0104
1852PUSH20x02c0
1855DUP9
1856MUL
1857PUSH10x04
1859CALLDATALOAD
185aADD
185bADD
185cPUSH20x62c9
185fJUMP
1860JUMPDEST
1861AND
1862ISZERO
1863PUSH20x17fe
1866JUMP
1867JUMPDEST
1868DUP5
1869PUSH10x24
186bPUSH20x02c0
186ePUSH20x1886
1871PUSH10x04
1873CALLDATALOAD
1874DUP3
1875DUP6
1876MUL
1877ADD
1878PUSH10x64
187aDUP2
187bADD
187cCALLDATALOAD
187dSWAP1
187ePUSH10x44
1880ADD
1881CALLDATALOAD
1882PUSH20x6966
1885JUMP
1886JUMPDEST
1887SWAP3
1888PUSH40x1a35ac99
188dPUSH10xe0
188fSHL
1890PUSH20x0100
1893MLOAD
1894MSTORE
1895MUL
1896PUSH10x04
1898CALLDATALOAD
1899ADD
189aADD
189bCALLDATALOAD
189cPUSH10x04
189eMSTORE
189fPUSH10x24
18a1MSTORE
18a2PUSH10x44
18a4PUSH20x0100
18a7MLOAD
18a8REVERT
18a9JUMPDEST
18aaSWAP3
18abSWAP1
18acDUP6
18adPUSH20x0100
18b0MLOAD
18b1JUMPDEST
18b2PUSH10xc0
18b4MLOAD
18b5DUP2
18b6LT
18b7ISZERO
18b8PUSH20x1e8e
18bbJUMPI
18bcPUSH10x05
18beDUP2
18bfSWAP1
18c0SHL
18c1DUP4
18c2ADD
18c3CALLDATALOAD
18c4CALLDATASIZE
18c5DUP5
18c6SWAP1
18c7SUB
18c8PUSH20x013e
18cbNOT
18ccADD
18cdDUP2
18ceSLT
18cfISZERO
18d0PUSH20x03b4
18d3JUMPI
18d4PUSH20x18e0
18d7SWAP1
18d8CALLDATASIZE
18d9SWAP1
18daDUP6
18dbADD
18dcPUSH20x5f9d
18dfJUMP
18e0JUMPDEST
18e1PUSH10x80
18e3MSTORE
18e4PUSH10x80