Final Testnetexplorer K_J · Final Testnet · 48359
en

Contract

0x5e0e6f3bb713b69c395502a89b4368995b276cd5

Address
0x5e0e6f3bb713b69c395502a89b4368995b276cd5
Kind
verified contract FinalAssetRegistry
Balance
0 vETH
Nonce
1
Code
33,519 bytes codehash 0x60c9aaedf634fed7a5101bb8945650c6994621af71bd3bda483b6bcb4f0a50f6

account tree

Tree
1 · accounts
Present
no leaf
Key
0x02f0e4eb18190210234a21671271fefdc138c9302d9e700d968f3bf24223df02
Live root
0xeae723253d5f6a608807aa960f2b55066f9694cd06953d238148b49b400dce61
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-10T07:10:05.198Z
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
 *
 * Eight key kinds. Kinds 0–2 are the rosters (chains, assets, assets on chains),
 * kind 3 is every numeric policy, kind 7 is a chain's execution terms (its
 * lanes and fee-quote bounds), kind 8 is the builder set a chain's bundles may
 * be offered to (flavour, endpoint hash, capability bits — the tree attests the
 * transport, the endpoint URL and its key stay in the service's own
 * configuration), 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 Action tag for a per-chain builder-row write (kind 8).
    bytes32 internal constant ACTION_SET_BUILDERS = keccak256("FinalAssetRegistry.setBuilders.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;
    }

    /// @notice One BUILDER a chain's bundles may be offered to — kind 8. The
    ///         execution-path schema's phase 2 (2026-09-08): which builders,
    ///         relays and sequencer private lanes a chain submits to is
    ///         protocol configuration and lives here, per chain, like every
    ///         other execution term. Retires the named builder set of the
    ///         kind-3 row `CHAIN_BUILDER_SET_<chainId>` and the env lists.
    /// @dev The endpoint URL is NOT on the tree: it does not fit a word, it is
    ///      public-but-mutable, and a service's builder key sits beside it in
    ///      its own configuration. The tree carries `keccak256(bytes(url))`, so
    ///      the service can prove the endpoint it was given is the one the
    ///      quorum meant and refuse any other — the attested-transport path.
    ///      `flavour` is the API shape (a code fact, numbered by the shared
    ///      table `BUILDER_FLAVOUR_*`); `capabilities` are the builder's own
    ///      properties as bits (`BUILDER_CAP_*`) — sponsorship is a builder's
    ///      property, never a chain-level term (ruled 2026-09-05: nothing on an
    ///      execution chain is sponsored, so the bit is a statement about the
    ///      builder's API, not a lane).
    struct BuilderEntry {
        /// @dev The chain whose blocks this builder builds.
        bytes32 chainRef;
        /// @dev `keccak256(bytes(name))` — the builder's stable name (`flashbots`, `titan`, `op-rollup-boost`).
        bytes32 builderId;
        /// @dev API flavour, `BUILDER_FLAVOUR_*`. Zero is refused on an enabled row.
        uint8 flavour;
        /// @dev `keccak256(bytes(endpointUrl))` of the endpoint the service must be configured with. Zero is refused on an enabled row.
        bytes32 endpointHash;
        /// @dev Capability bits, `BUILDER_CAP_*`.
        uint16 capabilities;
        /// @dev False retires the builder; the row stays as a tombstone.
        bool enabled;
        /// @dev Monotonic per entry.
        uint64 epoch;
    }

    /// @dev Kind-8 flavour numbers — the wire form of `ofa/builderProfiles.js BUILDER_API_FLAVORS` keys.
    uint8 internal constant BUILDER_FLAVOUR_FLASHBOTS = 1;
    uint8 internal constant BUILDER_FLAVOUR_BUILDERNET = 2;
    uint8 internal constant BUILDER_FLAVOUR_TITAN = 3;
    uint8 internal constant BUILDER_FLAVOUR_BEAVER = 4;
    uint8 internal constant BUILDER_FLAVOUR_BLOXROUTE = 5;
    uint8 internal constant BUILDER_FLAVOUR_BLOCKBEELDER = 6;
    uint8 internal constant BUILDER_FLAVOUR_RSYNC = 7;
    uint8 internal constant BUILDER_FLAVOUR_JETBLDR = 8;
    uint8 internal constant BUILDER_FLAVOUR_BTCS = 9;
    uint8 internal constant BUILDER_FLAVOUR_TBUILDER = 10;
    uint8 internal constant BUILDER_FLAVOUR_GENERIC = 11;
    /// @dev A rollup sequencer's private lane (rollup-boost / builder endpoint on OP Mainnet, Base, Unichain).
    uint8 internal constant BUILDER_FLAVOUR_ROLLUP_BOOST = 12;

    /// @dev Kind-8 capability bits.
    uint16 internal constant BUILDER_CAP_PRIVATE_TX = 1;
    uint16 internal constant BUILDER_CAP_REFUND = 2;
    uint16 internal constant BUILDER_CAP_CANCEL = 4;
    uint16 internal constant BUILDER_CAP_SPONSORS = 8;
    uint16 internal constant BUILDER_CAP_ATTESTED = 16;
    uint16 internal constant BUILDER_CAP_AGGREGATOR = 32;

    /// @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 8 — builders per chain, `chainRef -> builderId -> entry`.
    mapping(bytes32 chainRef => mapping(bytes32 builderId => BuilderEntry)) internal _builders;
    /// @dev Per-chain list of the builder ids written on it, insertion order (tombstones included).
    mapping(bytes32 chainRef => bytes32[]) internal _builderIdsOn;

    /// @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 builder row was written or retired.
    /// @param chainRef The chain.
    /// @param builderId `keccak256(bytes(name))`.
    /// @param flavour The API flavour.
    /// @param endpointHash `keccak256(bytes(endpointUrl))` the service must match.
    /// @param capabilities The builder's capability bits.
    /// @param enabled Whether the builder is live after this write.
    /// @param epoch The row's new epoch.
    event BuilderSet(
        bytes32 indexed chainRef, bytes32 indexed builderId, uint8 flavour, bytes32 endpointHash, uint16 capabilities, bool enabled, 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);
    /// @dev A kind-8 row is enabled with no id, no flavour or no endpoint hash.
    error InvalidBuilder(bytes32 chainRef, bytes32 builderId);
    /// @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);
    }

    /**
     * @notice Publish per-chain builder rows (kind 8) — which builders, relays
     *         and sequencer private lanes a chain's bundles may be offered to,
     *         each with its API flavour, the hash of the endpoint the service
     *         must be configured with, and its capability bits. The same quorum
     *         as every other row; a newer epoch supersedes; `enabled == false`
     *         retires a builder and leaves its tombstone.
     */
    function setBuilders(
        BuilderEntry[] 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_BUILDERS, 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++) {
            BuilderEntry memory b = updates[i];
            if (!_chainKnown[b.chainRef]) revert ChainNotRegistered(b.chainRef);
            if (b.builderId == bytes32(0) || (b.enabled && (b.flavour == 0 || b.endpointHash == bytes32(0)))) {
                revert InvalidBuilder(b.chainRef, b.builderId);
            }
            b.epoch = e;
            if (_builders[b.chainRef][b.builderId].epoch == 0) _builderIdsOn[b.chainRef].push(b.builderId);
            _builders[b.chainRef][b.builderId] = b;
            keys[i] = allowlistKeyForBuilder(b.chainRef, b.builderId);
            leaves[i] = _allowlistLeafForBuilder(b);
            emit BuilderSet(b.chainRef, b.builderId, b.flavour, b.endpointHash, b.capabilities, b.enabled, 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 Kind 8: one builder row of a chain.
    function allowlistKeyForBuilder(bytes32 chainRef, bytes32 builderId) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(8), chainRef, builderId));
    }

    /// @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 Folds a builder row into its allowlist leaf, under the same word-for-word rule.
    /// @param b The builder row.
    /// @return The leaf.
    function _allowlistLeafForBuilder(BuilderEntry memory b) private pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ALLOWLIST_LEAF, uint8(8), b.chainRef, b.builderId, b.flavour, b.endpointHash, b.capabilities,
                b.enabled, b.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 One builder row of a chain, or a zeroed entry when never written.
    function builderOf(bytes32 chainRef, bytes32 builderId) external view returns (BuilderEntry memory) {
        return _builders[chainRef][builderId];
    }

    /// @notice The builder ids ever written on a chain, insertion order, tombstones included.
    function builderIdsOn(bytes32 chainRef) external view returns (bytes32[] memory) {
        return _builderIdsOn[chainRef];
    }

    /// @notice Every builder row of a chain, insertion order, tombstones included — the
    ///         reader filters `enabled`. One call per chain at adoption.
    function buildersOn(bytes32 chainRef) external view returns (BuilderEntry[] memory out) {
        bytes32[] storage ids = _builderIdsOn[chainRef];
        out = new BuilderEntry[](ids.length);
        for (uint256 i = 0; i < ids.length; i++) {
            out[i] = _builders[chainRef][ids[i]];
        }
    }

    /// @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 place a service identity on a chain of their choosing,
    ///      which is why the projection reads and never accepts.
    /// @return The enabled chain references, in the registry's own order.
    function enabledChainRefs() external view returns (bytes32[] memory);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

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

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

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

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

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

        _bump(TREE_ACCOUNTS, leaves.length);
    }

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

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

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

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        _leaf[treeId][idx] = leaf;

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

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

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

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

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

contracts/utils/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": "allowlistKeyForBuilder",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "builderId",
        "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": "builderIdsOn",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "builderOf",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "builderId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.BuilderEntry",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "builderId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "flavour",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "endpointHash",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "capabilities",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "buildersOn",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "out",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.BuilderEntry[]",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "builderId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "flavour",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "endpointHash",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "capabilities",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "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": "setBuilders",
    "inputs": [
      {
        "name": "updates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.BuilderEntry[]",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "builderId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "flavour",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "endpointHash",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "capabilities",
            "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": "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": "BuilderSet",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "builderId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "flavour",
        "type": "uint8",
        "indexed": false,
        "internalType": "uint8"
      },
      {
        "name": "endpointHash",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      },
      {
        "name": "capabilities",
        "type": "uint16",
        "indexed": false,
        "internalType": "uint16"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "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": "InvalidBuilder",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "builderId",
        "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 · 33,519 bytes

0x610120806040526004361015610013575f80fd5b5f610100525f3560e01c9081630292709e14615f3857508063036f9a4314615f1a5780630489d03814615e5e578063087a5cf414615deb57806308f0ce6214613af45780630900767d14615dd25780630eca186714615db9578063150b7a0214615d635780631591c4a014615741578063178bcc93146156fa5780631945098e146106805780631a633473146105605780631e000aa5146155f8578063200d43ad1461498e57806320d4f69614610560578063210de79d1461493f5780632b89089c146149205780633ec4cc34146148d55780633fb27b851461486657806342cde4e8146148465780634760fd43146147da5780634bcb06a9146147c15780634e2ed4bb146141e75780634ec824ff14613af45780635483a0b51461419f57806357d70648146141815780635b88f50e14613af45780635eb320b8146141615780635f61b15c14613af4578063608ed2ec146140ee57806360a18008146140ba57806360c29fac14610680578063622c3f211461409b57806365cea49c1461407d57806368cf465e146106805780636c2c44e2146140645780636db8175214614044578063711f3db414613f62578063767c39d2146105605780637b10399914613f1b5780637bb737bc14613f025780637dcdde8e14613d7f5780637f49d69f14613d55578063831f33a114613b355780638513e2d514613b17578063852a52e814613af957806388f06eaa14613af457806392a2e0b314613ad65780639377c22014613aad5780639493ba1d14613a3a57806394bc4e961461372d57806395102c421461370e5780639648eca21461344e57806396f51f3a1461319957806397b9857b1461312d578063a741113c146130f3578063aa9239f514613097578063ac65bc7014612f6e578063ad844b9614612ec5578063affed0e014612e9c578063b19f480514612e5f578063b8da302a14612e3f578063b9a7f07614612d56578063b9ed468614610cba578063bbbcda4414610c9c578063bc197c8114610c04578063bda1019e14610be4578063be380c8514610a63578063c5dbda6614610a44578063dc544a0914610a23578063de54d42914610a03578063df46c7131461089d578063e02e1bfd1461087d578063e597e65914610685578063e70475a714610680578063e94a52af14610662578063eafe7a7414610642578063eb77738214610565578063ee076a5014610560578063ee57e33c146104cb578063f23a6e6114610475578063f3178f4314610412578063f4e885db146103f25763f851a440146103be575f80fd5b346103eb57610100513660031901126103eb5761010051546040516001600160a01b039091168152602090f35b6101005180fd5b346103eb57610100513660031901126103eb576020600154604051908152f35b346103eb5760403660031901126103eb5761042b6163eb565b6004356101005152600a6020526104516001600160401b03604061010051209216617261565b6101005152602052602061ffff600260406101005120015460501c16604051908152f35b346103eb5760a03660031901126103eb5761048e616527565b5061049761653d565b506084356001600160401b0381116103eb576104b7903690600401616553565b505060405163f23a6e6160e01b8152602090f35b346103eb57610100513660031901126103eb57604051806020601354918281520190601361010051527f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a09090610100515b81811061054a5761054685610532818703826167d9565b6040519182916020835260208301906164d9565b0390f35b825484526020909301926001928301920161051b565b6165cc565b346103eb57610100513660031901126103eb57600454610584816169be565b610100519091815b8181106105e057505061059e816169be565b91610100515b8281106105c15760405160208082528190610546908201876164d9565b806105ce60019284616a8c565b516105d98287616a8c565b52016105a4565b806105ec6001926169f0565b90549060031b1c6101005152600560205260ff600c60406101005120015460081c16610619575b0161058c565b610622816169f0565b90549060031b1c61063c61063586616bfb565b9587616a8c565b52610613565b346103eb57610100513660031901126103eb576020600754604051908152f35b346103eb57610100513660031901126103eb57602060405160058152f35b6165b1565b346103eb5760403660031901126103eb5760243560043560ff821682036103eb576007546106b281617348565b6101005190939092835b8381106107595785856106ce81617348565b91610100515b82811061072f5783604051809160208201602083528151809152602060408401920190610100515b81811061070a575050500390f35b919350916020610120826107216001948851616471565b0194019101918493926106fc565b8061073c60019284616a8c565b516107478287616a8c565b526107528186616a8c565b50016106d4565b61076281616a1c565b90549060031b1c806101005152600860205260ff600560406101005120015460481c1615610874576101005152600a6020526040610100512082610100515260205260406101005120604051906107b88261674e565b805482526001810154602083015284600282015460ff81166040850152600560ff8260081c161515938460608701526001600160401b038360101c16608087015261ffff8360501c1660a0870152600381015460c0870152600481015460e0870152015461010085015282610865575b505061083a575b506001905b016106bc565b949061085e8261084c60019491616bfb565b97610857828b616a8c565b5288616a8c565b509061082f565b60ff9250161615158489610828565b50600190610834565b346103eb57610100513660031901126103eb576020600454604051908152f35b346103eb5760203660031901126103eb576108b6616601565b600754906108c3826172f9565b6101005190929091825b828110610981575050506108e0816172f9565b91610100515b82811061095757836040518091602082016020835281518091526040830190602060408260051b86010193019161010051905b82821061092857505050500390f35b919360019193955060206109478192603f198a82030186528851616696565b9601920192018594939192610919565b8061096460019284616a8c565b5161096f8287616a8c565b5261097a8186616a8c565b50016108e6565b61098a81616a1c565b90549060031b1c610100515260086020526109aa6040610100512061707c565b6101008101511515806109f1575b6109c6575b506001016108cd565b93906109ea826109d860019491616bfb565b966109e3828a616a8c565b5287616a8c565b50906109bd565b5060ff8360a0830151161615156109b8565b346103eb57610100513660031901126103eb576020600d54604051908152f35b346103eb576020610a3c610a363661645b565b906172b3565b604051908152f35b346103eb57610100513660031901126103eb5760206040516101f48152f35b346103eb576102c03660031901126103eb57604051610a818161676a565b600435815260243560208201526044356040820152606435606082015260843560ff811681036103eb57608082015260a43560a082015260c43560c082015260e43560ff811681036103eb5760e0820152610104356001600160401b03811681036103eb576101008201526101243563ffffffff811681036103eb576101208201526101443561ffff811681036103eb576101408201526101643561ffff811681036103eb57610160820152610184356101808201526101a43560ff811681036103eb576101a08201526101c4356101c08201526101e4356101e08201526102043560ff811681036103eb576102008201526102243580151581036103eb57610220820152610244356001600160401b03811681036103eb57610240820152610264356001600160401b03811681036103eb576102608201526102843560ff811681036103eb5781610a3c9161028060209401526102a4356102a08201526171f1565b346103eb57610100513660031901126103eb576020601754604051908152f35b346103eb5760a03660031901126103eb57610c1d616527565b50610c2661653d565b506044356001600160401b0381116103eb57610c4690369060040161642b565b50506064356001600160401b0381116103eb57610c6790369060040161642b565b50506084356001600160401b0381116103eb57610c88903690600401616553565b505060405163bc197c8160e01b8152602090f35b346103eb5760203660031901126103eb576020610a3c6004356171be565b346103eb5760a03660031901126103eb576001600160401b03600435116103eb573660236004350112156103eb576001600160401b0360043560040135116103eb573660246102c060043560040135026004350101116103eb576024356001600160401b0381116103eb57610d3390369060040161642b565b60c0526044356001600160401b0381116103eb57610d55903690600401616580565b60643592906001600160401b03841684036103eb576084356001600160401b0381116103eb57610d8990369060040161642b565b946002548015612d4157600435600401351580612d37575b80612d2f575b612d1a57600354926040519760a089016001600160401b03861660208b0152608060408b015260043560040135905260c08901602460043501610100515b600435600401358110612b87575050601f198a82030160608b015260c051815260208101602060c05160051b830101908992610100515b60c0518110612a815750505060209150601f198b82030160808c0152878152019888610100515b8881106129ea575050610e6681610f0d9798999a9b03601f1981018352826167d9565b60208151910120610100515060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527fe10634eb0bf7bd6ad00dc59a6c67fe9b09037b979405fa59137e7e530d60141460808301526001600160401b03871660a083015260c082015260c08152610edf60e0826167d9565b51902090600154927f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6173c3565b506001600160401b03610f21818316616975565b16906001600160401b03191617600355601954610f466001600160401b038216616975565b60a0526001600160401b0360a05116906001600160401b03191617601955610f7660c05160043560040135616bd3565b6001600160ff1b03821682036129d057610f97610fa8918360011b90616bd3565b610fa0816169be565b60e0526169be565b6101005192909190835b600435600401358510156118e057610fdb6004356102c0870201606481013590604401356172b3565b60246102c087026004350101350361189e576110026102446102c087026004350101616be0565b80611811575b6117ec57600160ff6110256102a46102c089026004350101616bed565b16116117ad576110406102446102c087026004350101616be0565b8061178e575b8061177a575b611755576004356102c0860201604401355f51602061826f5f395f51905f52148080611733575b6116f4576116ad575b6101e46102c08602600435010135151580611683575b61165d5760246102c086026004350101356101005152600660205260ff604061010051205416156115ca575b61010080516102c08702600435016024810135918290526005602052915160409020908155604482013560018201556064820135600282019081556084830135600383015590916111119060a401616bed565b6004838101805460ff191660ff939093169290921790915560c490356102c089020190810135600584015560e481013560068401556111539061010401616bed565b60ff1660ff196007840154161760078301556102c08702600435016101240161117b906171aa565b60078301906111a8919068ffffffffffffffff0082549160081b169068ffffffffffffffff001916179055565b6111bd6101446004356102c08a020101616c18565b600783015461ffff60681b6111dd6101646004356102c08d020101616c09565b60681b169061ffff60781b6111fd6101846004356102c08e020101616c09565b60781b169260481b6cffffffff000000000000000000169067ffffffffffffffff60481b191617171760078301556102c08702600435016101a4013560088301556102c08702600435016101c40161125490616bed565b60098301805460ff191660ff929092169190911790556101e46004356102c089020190810135600a840155610204810135600b8401556112979061022401616bed565b60ff1660ff19600c8401541617600c8301556102c0870260043501610244016112bf90616be0565b600c8301805460a05169ffffffffffffffff000060109190911b1692151560081b61ff001669ffffffffffffffffff00199091161791909117905561130f6102846004356102c08a0201016171aa565b600c8301805467ffffffffffffffff60501b191660509290921b67ffffffffffffffff60501b169190911790556113516102a46004356102c08a020101616bed565b600c8301805460ff60901b609084901b1660ff60901b198216179091556102c08902600435016102c4810135600d8601559092906113929060240135616ad3565b8560e051906113a091616a8c565b52835493600181015491546003820154600483015460ff166005840154600685015490600786015492600887015494600988015460ff1696600a89015498600b015499604051809e602082015f5160206182af5f395f51905f52905261010051604083015260608201526080015260a08d015260c08c015260e08b01526101008a015261012089015260ff81166101408901528060081c6001600160401b03166101608901528060481c63ffffffff166101808901528060681c61ffff166101a089015260781c61ffff166101c08801526101e087015261020086015261022085015261024084015260ff821661026084015260ff60901b8160901b1660ff60901b1983161760081c60ff16151561028084015260ff60901b8160901b1660ff60901b1983161760101c6001600160401b03166102a084015260ff60901b8160901b1660ff60901b1983161760501c6001600160401b03166102c084015260ff60901b9060901b169060ff60901b19161760901c60ff166102e08201526102c08602600435016102c401356103008201526103008152611542610320826167d9565b805190602001206115538286616a8c565b5261155d90616bfb565b936115736102446004356102c084020101616be0565b6040805160a05192151581526001600160401b0390921660208301526102c083026004350160240135917f523b72ac5fbe8982a57ea63c8ca4daf1602c22eda42d6cc14a884c0310698b809190a260010193610fb2565b60246102c086026004350101356101005152600660205260406101005120600160ff19825416179055600454600160401b8110156116435761161781600161163c93016004556004616a34565b60246102c089949394026004350101359083549060031b91821b915f19901b19161790565b90556110be565b634e487b7160e01b61010051526041600452602461010051fd5b6101e46102c086633f4b99b160e21b610100515202600435010135600452602461010051fd5b506101e46102c086026004350101356101005152600660205260ff60406101005120541615611092565b604080516116ef916116bf90826167d9565b60098152683830bcb6b0b9ba32b960b91b60208201526004356102c08802016102c4810135919060240135617a55565b61107c565b60ff8660246102c061170f6102248285026004350101616bed565b92631e9a7d7560e01b61010051520260043501013560045216602452604461010051fd5b50600160ff61174d6102246102c08a026004350101616bed565b161415611073565b60246102c086632eb72ead60e11b610100515202600435010135600452602461010051fd5b5060846102c086026004350101351561104c565b5060ff6117a66102a46102c088026004350101616bed565b1615611046565b60ff8560246102c06117c86102a48285026004350101616bed565b92631a7ce1dd60e01b61010051520260043501013560045216602452604461010051fd5b60246102c086633dc2fc1160e01b610100515202600435010135600452602461010051fd5b5063ffffffff61182c6101446102c088026004350101616c18565b1615801561187f575b8015611860575b80611008575060ff6118596102246102c088026004350101616bed565b1615611008565b5060ff6118786101c46102c088026004350101616bed565b161561183c565b5060ff6118976101046102c088026004350101616bed565b1615611835565b8460246102c06118bd60043582850201606481013590604401356172b3565b92631a35ac9960e01b610100515202600435010135600452602452604461010051fd5b929085610100515b60c051811015611ec557600581901b8301353684900361013e19018112156103eb576119179036908501616887565b608052608051516101005152600660205260ff60406101005120541615611ea95760a0608051015160ec8116611e8d575063ffffffff60c060805101511615611e5b575b63ffffffff60e060805101511615611e28575b6001600160401b0360a051166101206080510152611996608051516020608051015190616e2b565b806101005152600960205260ff60406101005120541615611dc9575b80610100515260086020526040610100512060805151815560206080510151600182015560ff604060805101511660ff60028301911660ff1982541617905560038101606060805101518051906001600160401b038211611643578190611a198454616fa4565b601f8111611d61575b506020906001601f841114611cf9576101005192611cee575b50508160011b915f199060031b1c19161790555b6080805101518051906001600160401b03821161164357611a736004840154616fa4565b601f8111611c8e575b506020906001601f841114611bf0576001959493837fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f94611ba8946005946101005192611be5575b50505f19600383901b1c191690881b1760048201555b60805160a081015192909101805460c083015160e0840151610100850180516101209096015167ffffffffffffffff60501b60509190911b1669ff00000000000000000096151560481b9690961671ffffffffffffffffffffffffffffffffffff1990941660ff9097169690961760089290921b64ffffffff00169190911768ffffffff000000000060289290921b91909116171791909117905599611b7f846171be565b611b8b8260e051616a8c565b52611b9860805185617a9d565b611ba2828b616a8c565b52616bfb565b60805160a0908101519a5160408051925160ff909d16835290151560208301526001600160401b03909b169a81019a909a5298606090a2016118e8565b015190508e80611ac4565b9060048401610100515280610100512091610100515b601f1985168110611c76575093600184611ba894600594839a99987fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f98601f19811610611c5e575b505050811b016004820155611ada565b01515f1960f88460031b161c191690558e8080611c4e565b91926020600181928685015181550194019201611c06565b82811115611a7c5760048401610100515260206101005120601f840160051c60208510611ce4575b610100515b81601f850160051c038110611cd257505050611a7c565b61010051838301820155600101611cbb565b5061010051611cb6565b015190508b80611a3b565b6101008051869052518281209350601f198516905b818110611d495750908460019594939210611d31575b505050811b019055611a4f565b01515f1960f88460031b161c191690558b8080611d24565b92936020600181928786015181550195019301611d0e565b82811115611a225790915083610100515260206101005120601f840160051c60208510611dbf575b9084939291610100515b81601f850160051c038110611daa57505050611a22565b61010051838301820155869550600101611d93565b5061010051611d89565b806101005152600960205260406101005120600160ff19825416179055600754600160401b81101561164357611e21611e0b8260018594016007556007616a34565b819391549060031b91821b915f19901b19161790565b90556119b2565b60805160a0015160021615611e4d5763ffffffff610bb85b1660e0608051015261196e565b63ffffffff6201d4c0611e40565b60805160a0015160021615611e805763ffffffff6103e85b1660c0608051015261195b565b63ffffffff612710611e73565b60ff906376cbaf6d60e01b610100515216600452602461010051fd5b60805151633f4b99b160e21b6101005152600452602461010051fd5b83868387610100515b81811061241257505060e0518290525081527f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b031690813b156103eb576040519063abf1570d60e01b82528180611f36610100519360e05160048401616aa0565b038161010051865af18015612219576123f7575b50600454611f57816169be565b6101005191825b8181106122885784611f708585618048565b80601755600754611f80816169be565b6101005191825b81811061222757505090611f9a91618048565b918260185560609260405190611fb085836167d9565b60028252601f198501938436602085013760405194611fcf87876167d9565b60028652366020870137604080517fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f60208201908152610100518284015291815261201a88826167d9565b51902061202684616a6f565b52604051602081019161010051507f205e4a5b42cbb22c56a771766b971165a627b616bd23460c8e002faad102d0918352610100516040830152878201526001600160401b0360a0511660808201526080815261208460a0826167d9565b51902061209085616a6f565b52604080517fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f602082019081526001828401529181526120d087826167d9565b5190206120dc83616a7c565b52604051602081019161010051507f205e4a5b42cbb22c56a771766b971165a627b616bd23460c8e002faad102d091835260016040830152868201526001600160401b0360a0511660808201526080815261213860a0826167d9565b51902061214484616a7c565b52813b156103eb5760405163abf1570d60e01b815260056004820152600160248201526080604482015292839161219691906121849060848501906164d9565b838103600319016064850152906164d9565b9181806101005194039161010051905af18015612219576121fe575b7ff95ac4707234b4ef5620ff790985a37679e4c7e0b0c603f110ae9bcc65b0cf5c8260175460185460405191825260208201526001600160401b0360a051166040820152a16101005180f35b6101005161220b916167d9565b610100516103eb57816121b2565b6040513d61010051823e3d90fd5b80612233600192616a1c565b90549060031b1c610100515260086020526122536040610100512061707c565b610100810151156122825761226790617116565b61227a61227387616bfb565b9686616a8c565b525b01611f87565b5061227c565b806122946001926169f0565b90549060031b1c6101005152600560205260406101005120604051906122b98261676a565b80548252838101546020830152600281015460408301526003810154606083015260ff6004820154166080830152600581015460a0830152600681015460c083015261ffff600782015460ff811660e08501526001600160401b038160081c1661010085015263ffffffff8160481c16610120850152818160681c1661014085015260781c16610160830152600881015461018083015260ff6009820154166101a0830152600a8101546101c0830152600b8101546101e0830152600d600c8201549160ff831661020085015260ff808460081c16159384156102208701526001600160401b038160101c166102408701526001600160401b038160501c1661026087015260901c1661028085015201546102a08301526123f1576123dd906171f1565b6123e961227387616bfb565b525b01611f5e565b506123eb565b61010051612404916167d9565b610100516103eb5781611f4a565b61241d818385616bc2565b93610120853603126103eb57604051946124368661674e565b8035865261010060208201359182602089015261245560408201616611565b60408901526124666060820161687a565b606089015261247760808201616417565b608089015261248860a08201616966565b60a089015260c081013560c089015260e081013560e089015201356101008701526101005152600660205260ff604061010051205416156129b35784516101005152600960205260ff6040610100512054161561299957604085015160f38116611e8d575061ffff60a08601511680151580612984575b61295d57506126e6906020860151610100515260056020525f51602061826f5f395f51905f526001604061010051200154146128bc575b6001600160401b0360a05116608087015285516101005152600a60205260406101005120602087015161010051526020526126296040610100512087518082556020890151918260018201556002810160ff8060408d0151161660ff198254161781556125bc60608c01511515829061ff00825491151560081b169061ff001916179055565b60808b0151815469ffffffffffffffff0000191660109190911b69ffffffffffffffff00001617815560a08b0151815461ffff60501b191660509190911b61ffff60501b1617905560c08a0151600382015560e08a015160048201556101008a0151600590910155616e4b565b6126358260e051616a8c565b528551602087015160ff60408901511688606081015115156001600160401b0360808301511661ffff60a0840151169060c08401519261010060e0860151950151956040519760208901995f5160206182af5f395f51905f528b52600260408b015260608a0152608089015260a088015260c087015260e086015261010085015261012084015261014083015261016082015261016081526126d9610180826167d9565b519020611ba28289616a8c565b9380516020820151907ffdf8afb51a6b8e68fa9d9946922368e4a2e54a69963cf7bcfcdff38235916e7960ff6040850151166060850151151561275360405192839260a05191849160409194936001600160401b039160ff60608601971685521515602085015216910152565b0390a360608101511515806128ac575b612771575b50600101611ece565b80516101005152600860205260406101005120600581019081546103e863ffffffff8260081c1611908115612896575b506127ae575b5050612768565b815460a05171ffffffffffffffff00ffffffffffffffff001990911660509190911b67ffffffffffffffff60501b1617660bb8000003e8001782559195600193927fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f9161288b916128509190612846906128288c516171be565b6128348460e051616a8c565b526128408c519161707c565b90617a9d565b611ba2828d616a8c565b975192546040805160a05160ff808516835260489490941c909316151560208201526001600160401b03909216908201529081906060820190565b0390a29086806127a7565b610bb8915063ffffffff9060281c1611896127a1565b5060046040820151161515612763565b61295860208701516128f360c08901516040928351906128dc85836167d9565b60058252643a37b5b2b760d91b6020830152617a55565b602088015160e089015182516129249261290d85836167d9565b60058252640dadee4e0d60db1b6020830152617a55565b60208801516101008901518251909290919061294090836167d9565b60068252651d905cdcd95d60d21b6020830152617a55565b612536565b8560208151910151906368adc18560e01b6101005152600452602452604452606461010051fd5b5060648110806124ff57506101f481116124ff565b84516326ef68cf60e01b6101005152600452602461010051fd5b6020850151633f4b99b160e21b6101005152600452602461010051fd5b634e487b7160e01b61010051526011600452602461010051fd5b909a610120806001926101008f803583526020810135602084015260ff612a1360408301616611565b166040840152612a256060820161687a565b151560608401526001600160401b03612a4060808301616417565b16608084015261ffff612a5560a08301616966565b1660a084015260c081013560c084015260e081013560e08401520135610100820152019c019101610e43565b909192601f1983820301845261013e198c360301853512156103eb576020806001928e8835019081358152828201358382015260ff612ac260408401616611565b1660408201526101206001600160401b03612b7682612b18612afd612aea6060890189616ebb565b61014060608a0152610140890191616eec565b612b0a6080890189616ebb565b9088830360808a0152616eec565b9560ff612b2760a08301616611565b1660a087015263ffffffff612b3e60c08301616869565b1660c087015263ffffffff612b5560e08301616869565b1660e0870152612b68610100820161687a565b151561010087015201616417565b169101529601959401929101610e1c565b90916102c0806001928535815260208601356020820152604086013560408201526060860135606082015260ff612bc060808801616611565b16608082015260a086013560a082015260c086013560c082015260ff612be860e08801616611565b1660e08201526001600160401b03612c036101008801616417565b1661010082015263ffffffff612c1c6101208801616869565b1661012082015261ffff612c336101408801616966565b1661014082015261ffff612c4a6101608801616966565b1661016082015261018086013561018082015260ff612c6c6101a08801616611565b166101a08201526101c08601356101c08201526101e08601356101e082015260ff612c9a6102008801616611565b16610200820152612cae610220870161687a565b15156102208201526001600160401b03612ccb6102408801616417565b166102408201526001600160401b03612ce76102608801616417565b1661026082015260ff612cfd6102808801616611565b166102808201526102a08601356102a08201520193019101610de5565b63c2e5347d60e01b6101005152600461010051fd5b508315610da7565b5060c05115610da1565b63d311bc3960e01b6101005152600461010051fd5b346103eb5760203660031901126103eb57600435612d72616d88565b50600d54811015612e2557600d610100515260206101005120016101005150546101005160031b1c6101005152600b602052610546604061010051206001600160401b03600260405192612dc584616733565b8054845260018101546020850152015460ff81161515604084015260081c1660608201526040519182918291909160606001600160401b038160808401958051855260208101516020860152604081015115156040860152015116910152565b634e487b7160e01b61010051526032600452602461010051fd5b346103eb57610100513660031901126103eb5760206040516201d4c08152f35b346103eb57610100513660031901126103eb5760206040517f3d07e5a0f372bb3f92469df7a6dcb4a49539b8707a06a699d3727c07dbbb3a4b8152f35b346103eb57610100513660031901126103eb5760206001600160401b0360035416604051908152f35b346103eb57612ed33661645b565b90612edc616e85565b506101005152600f6020526040610100512090610100515260205260e0604061010051206001600160401b03600460405192612f17846167a2565b805484526001810154602085015260ff600282015416604085015260038101546060850152015461ffff8116608084015260ff8160101c16151560a084015260181c1660c0820152612f6c604051809261661f565bf35b346103eb5760203660031901126103eb57612f87616b0b565b506004356101005152600e6020526101005160409020604051612fa98161674e565b81549182825260010154906020810160ff83168152604082018360081c61ffff168152606083018460181c63ffffffff16815260808401908560381c61ffff16825260a08501928660481c6001600160401b0316845260c08601948760881c61ffff16865260e08701968860981c60ff1615158852610100019760a01c6001600160401b031688526040519889525160ff1660208901525161ffff1660408801525163ffffffff1660608701525161ffff166080860152516001600160401b031660a08501525161ffff1660c084015251151560e0830152516001600160401b031661010082015261012090f35b346103eb5760203660031901126103eb576130b0616f59565b506130bc600435616a1c565b90549060031b1c610100515260086020526105466130df6040610100512061707c565b604051918291602083526020830190616696565b346103eb5760203660031901126103eb576004356001600160401b0381116103eb57610a3c6131286020923690600401616887565b617116565b346103eb5760203660031901126103eb57600435613149616f59565b50806101005152600960205260ff6040610100512054161561318157610100515260086020526105466130df6040610100512061707c565b63082cdf1f60e11b6101005152600452602461010051fd5b346103eb5760a03660031901126103eb5760043560048110156103eb576131be61653d565b90606435906084356001600160a01b03811691604435918381036103eb576131e4617989565b61010051548415906001600160a01b03168115613433575b5061341a5761320c838784616ddf565b945f1981036134155750845b80958115613400578082116133d5575061323183616f0c565b826132a85750506101005180808087875af161324b616f2a565b501561328c575f5160206182cf5f395f51905f5260406020965b61326e84616f0c565b81519485528785018790526001600160a01b031693a4604051908152f35b6365f4a9ef60e11b610100515261010051600452602461010051fd5b60209691906132b684616f0c565b6001840361331d57506040805163a9059cbb60e01b898201526001600160a01b039092166024830152604482018790525f5160206182cf5f395f51905f529290916133189061331281606481015b03601f1981018352826167d9565b826179b5565b613265565b919591905061332b83616f0c565b6002830361337a5750505f5160206182cf5f395f51905f52604060019561331882516323b872dd60e01b8a820152306024820152876044820152866064820152606481526133126084826167d9565b5f5160206182cf5f395f51905f529195613318604092835190637921219560e11b8b830152306024830152886044830152876064830152608482015260a060a48201526101005160c482015260c4815261331260e4826167d9565b6101008051632190968160e01b90526001600160a01b03891660045260249290925260445251606490fd5b637c2e506f60e11b6101005152600461010051fd5b613218565b836315150d4d60e31b6101005152600452602461010051fd5b905084141580613444575b876131fc565b503384141561343e565b346103eb5760203660031901126103eb5760043561346a616c29565b50806101005152600660205260ff604061010051205416156136f6576101005152600560205261054660406101005120600d604051916134a98361676a565b8054835260018101546020840152600281015460408401526003810154606084015260ff6004820154166080840152600581015460a0840152600681015460c084015261ffff600782015460ff811660e08601526001600160401b038160081c1661010086015263ffffffff8160481c16610120860152818160681c1661014086015260781c16610160840152600881015461018084015260ff6009820154166101a0840152600a8101546101c0840152600b8101546101e084015260ff600c820154818116610200860152818160081c1615156102208601526001600160401b038160101c166102408601526001600160401b038160501c1661026086015260901c1661028084015201546102a0820152604051918291829190916102a0806102c08301948051845260208101516020850152604081015160408501526060810151606085015260ff608082015116608085015260a081015160a085015260c081015160c085015260ff60e08201511660e08501526001600160401b036101008201511661010085015263ffffffff6101208201511661012085015261ffff6101408201511661014085015261ffff6101608201511661016085015261018081015161018085015260ff6101a0820151166101a08501526101c08101516101c08501526101e08101516101e085015260ff6102008201511661020085015261022081015115156102208501526001600160401b03610240820151166102408501526001600160401b036102608201511661026085015260ff610280820151166102808501520151910152565b632a80afbd60e21b6101005152600452602461010051fd5b346103eb57610100513660031901126103eb576020604051610bb88152f35b346103eb5760803660031901126103eb5760243560043561374c616401565b6064356001600160401b0381116103eb5761376b90369060040161642b565b61010051549091906001600160a01b03163303613883575b505050816137cb575b807f40bd951b186ab08230e53414aeaafc99b557bb646ef875822965f0e5a8c32ca5926040926001558060025582519182526020820152a16101005180f35b60405163342f616360e01b8152600481018290526020816024817f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03165afa90811561221957610100519161384d575b50828110613830575061378c565b9050633770da3360e11b6101005152600452602452604461010051fd5b90506020813d60201161387b575b81613868602093836167d9565b81010312613877575183613822565b5f80fd5b3d915061385b565b60408051602081018681528183018890529181527f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03169291906138cf6060826167d9565b519020823b156103eb5790836001600160401b039593926040519687956322f3f44760e11b875260848701927f3d07e5a0f372bb3f92469df7a6dcb4a49539b8707a06a699d3727c07dbbb3a4b60048901526024880152166044860152608060648601525260a4830160a48560051b85010194826101005190607e19813603015b83831061399a5750505050505081806101005194039161010051905af180156122195761397f575b8080613783565b6101005161398c916167d9565b610100516103eb5782613978565b919395965091939660a3198982030186528735828112156103eb57830180356001600160a01b03811692908390036103eb57613a286020928260019585945260ff6139e6858401616611565b1684820152613a1a613a0f6139fe6040850185616ebb565b608060408601526080850191616eec565b926060810190616ebb565b916060818503910152616eec565b99019601930190918896959492613950565b346103eb5760203660031901126103eb57600435610100515260166020526040610100512060405190816020825491828152019161010051526020610100512090610100515b818110613a975761054685610532818703826167d9565b8254845260209093019260019283019201613a80565b346103eb57610100513660031901126103eb5760206001600160401b0360195416604051908152f35b346103eb57610100513660031901126103eb57602060405160048152f35b61650c565b346103eb57610100513660031901126103eb57602060405160648152f35b346103eb5760203660031901126103eb576020610a3c600435617261565b346103eb5760203660031901126103eb57613b4e616cc7565b5060043561010051526011602052610546604061010051206001600160401b03600c60405192613b7d84616786565b80548452600181015460208501526002810154604085015260ff600382015416606085015260048101546080850152600581015460a0850152600681015460c0850152600781015460e085015261ffff6008820154166101008501526009810154610120850152600a81015461014085015260ff600b820154818116151561016087015263ffffffff8160081c1661018087015263ffffffff8160281c166101a08701526001600160801b038160481c166101c087015261ffff8160c81c166101e087015260d81c161515610200850152015416610220820152604051918291829190916102206001600160401b038161024084019580518552602081015160208601526040810151604086015260ff60608201511660608601526080810151608086015260a081015160a086015260c081015160c086015260e081015160e086015261ffff61010082015116610100860152610120810151610120860152610140810151610140860152610160810151151561016086015263ffffffff6101808201511661018086015263ffffffff6101a0820151166101a08601526001600160801b036101c0820151166101c086015261ffff6101e0820151166101e08601526102008101511515610200860152015116910152565b346103eb57610100513660031901126103eb5760206040515f51602061826f5f395f51905f528152f35b346103eb5760203660031901126103eb57600435806101005152601060205260406101005120805490613db1826169a7565b92613dbf60405194856167d9565b828452601f19613dce846169a7565b01610100515b818110613eeb575050610100515b838110613e3c5784604051809160208201602083528151809152602060408401920190610100515b818110613e18575050500390f35b91935091602060e082613e2e600194885161661f565b019401910191849392613e0a565b600190826101005152600f60205260406101005120613e5b8286616a34565b90549060031b1c6101005152602052604061010051206001600160401b03600460405192613e88846167a2565b8054845285810154602085015260ff600282015416604085015260038101546060850152015461ffff8116608084015260ff8160101c16151560a084015260181c1660c0820152613ed98288616a8c565b52613ee48187616a8c565b5001613de2565b602090613ef6616e85565b82828901015201613dd4565b346103eb576020610a3c613f153661645b565b90616e4b565b346103eb57610100513660031901126103eb576040517f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03168152602090f35b346103eb5760203660031901126103eb576004356001600160401b038116908190036103eb5761010051546001600160a01b0316330361402b576019546001600160401b03811615801590614020575b8015614015575b6140005767ffffffffffffffff191681176019556040519081527f1b0319a7c1b77359b02ef03a86a6e1677b3d55bc74afbe201986efaef1e8b4b590602090a16101005180f35b63dc63d81f60e01b6101005152600461010051fd5b506007541515613fb9565b506004541515613fb2565b630bd4212160e11b610100515233600452602461010051fd5b346103eb57610100513660031901126103eb576020604051610100518152f35b346103eb576020610a3c6140773661645b565b90616e2b565b346103eb5760203660031901126103eb576020610a3c600435616df8565b346103eb57610100513660031901126103eb5760206040516127108152f35b346103eb5760603660031901126103eb5760043560048110156103eb57610a3c6020916140e561653d565b60443591616ddf565b346103eb5760203660031901126103eb57600435610100515260106020526040610100512060405190816020825491828152019161010051526020610100512090610100515b81811061414b5761054685610532818703826167d9565b8254845260209093019260019283019201614134565b346103eb57610100513660031901126103eb576020601854604051908152f35b346103eb5760203660031901126103eb576020610a3c600435616dac565b346103eb5760203660031901126103eb576141b8616d88565b506004356101005152600b602052610546604061010051206001600160401b03600260405192612dc584616733565b346103eb5760603660031901126103eb576004356001600160401b0381116103eb57366023820112156103eb5780600401356001600160401b0381116103eb5736602460e08302840101116103eb5761423e6163eb565b6044356001600160401b0381116103eb5761425d90369060040161642b565b90916002548015612d41578415612d1a57600354936001600160401b0385169360405160208101908860608201888452604080840152526080810160248b01610100515b8b8110614741575050926001600160401b03979592826142d261434097946143469a9703601f1981018352826167d9565b519020610100515060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527f8edbf41b64bab13aa09d5e590fb62757de47a32c3f5541e6a3cee757fde6d7e260808301528a871660a083015260c082015260c08152610edf60e0826167d9565b50616975565b16906001600160401b03191617600355601954916001600160401b0361436d818516616975565b1680936001600160401b03191617601955614387826169be565b92614391836169be565b91610100515b848110156146b45760e08181028301903682900360231901126103eb576040516143c0816167a2565b6024820135908181526020810191604484013583526143e160648501616611565b936040830194855260608301926084820135845261440160a48301616966565b926080820193845261442860e461441a60c4860161687a565b9460a0850195865201616417565b9060c083019182526101005152600660205260ff6040610100512054161561469a5785518015801561467a575b61465d5750928261ffff60ff8f948e6146078f9d8c60019f9e829f9c819f9e9b6145877f1924b91ab00797d4d2c4d71ee691a743d20afece46e1e02b5c41794959a23ae29f60a09f8f8f88908f93848f956004614581978f99978e988e5281516101005152600f60205260406101005120885161010051526020526001600160401b038360406101005120015460181c161561463a575b81516101005152600f602052604061010051208851610100515260205260ff60406101005120925198898455519960018b90850155511660ff60028401911660ff19825416179055516003820155019351161661ffff19835416178255511515815462ff00006affffffffffffffff0000008b5160181b169260101b16906affffffffffffffffff0000191617179055616b88565b92616a8c565b528787519351925116908c518a8d5116906001600160401b038d511515935116936040519560208701975f5160206182af5f395f51905f528952600860408901526060880152608087015260a086015260c085015260e084015261010083015261012082015261012081526145fe610140826167d9565b51902092616a8c565b5251975198511693519251169051151591604051938452602084015260408301526060820152886080820152a301614397565b81516101005152601060205261465860406101005120895190616a49565b6144ec565b825163c9c05b0b60e01b6101005152600452602452604461010051fd5b50835115158015614455575060ff88511615806144555750855115614455565b5051633f4b99b160e21b6101005152600452602461010051fd5b7f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168685823b156103eb5761470b92604051808095819463abf1570d60e01b8352610100519660048401616aa0565b039161010051905af1801561221957614726575b6101005180f35b61010051614733916167d9565b610100516103eb578061471f565b909160e080600192853581526020860135602082015260ff61476560408801616611565b1660408201526060860135606082015261ffff61478460808801616966565b16608082015261479660a0870161687a565b151560a08201526001600160401b036147b160c08801616417565b1660c082015201930191016142a1565b346103eb576020610a3c6147d4366165e7565b91616d49565b346103eb5760203660031901126103eb5760206147f5616601565b610100515060405160ff838201927fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f84521660408201526040815261483b6060826167d9565b519020604051908152f35b346103eb57610100513660031901126103eb576020600254604051908152f35b346103eb57610100513660031901126103eb5761010051546001600160a01b038116330361402b576bffffffffffffffffffffffff60a01b1661010051557f1b2d71eb44f882534bf4e86f940c56ccc869ffb927e2bab86561de93950c22166101005161010051a16101005180f35b346103eb576148f56148e6366165e7565b916148ef616cc7565b50616d49565b61010051526011602052610546604061010051206001600160401b03600c60405192613b7d84616786565b346103eb57610100513660031901126103eb5760206040516103e88152f35b346103eb5760203660031901126103eb57614958616c29565b506149646004356169f0565b90549060031b1c6101005152600560205261054660406101005120600d604051916134a98361676a565b346103eb5760803660031901126103eb576004356001600160401b0381116103eb576149be903690600401616580565b6001600160401b03602435116103eb573660236024350112156103eb5760243560040135906001600160401b0382116103eb57366024610240840281350101116103eb57614a0a616401565b6064356001600160401b0381116103eb57614a2990369060040161642b565b90916002548015612d41578415806155f0575b612d1a57600354936001600160401b038516936040518760808201876020840152606060408401525260a081018a90610100515b8a8110615562575060209150601f198382030160608401528a8152016024803501610100515b8b811061541e57505092614b38959282614ac66001600160401b039997946143409703601f1981018352826167d9565b60208151910120610100515060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527f5596fa6720176cbfa2a11ee0d5be50e2016480c70c477d3273492663856b2d0b60808301528a871660a083015260c082015260c08152610edf60e0826167d9565b16906001600160401b0319161760035560195490614b5e6001600160401b038316616975565b67ffffffffffffffff199092166001600160401b03831617601955614b838382616bd3565b614b95614b8f826169be565b916169be565b6101005190959092835b8181106151425750505061010051915b848310156150eb57610100515060246102408402813501019283356101005152600960205260ff604061010051205416156150d157614bed84617644565b614c0a602435610240830201606481013590604401358635616d49565b91826101005152601260205260ff6040610100512054161561506c575b61010080518490526011602052516040902085358155602435610240840201604481013560018301556064810135600283015590919060ff90614c6c90608401616bed565b60038401805460ff19169290911691909117905560243561024084020160a4810135600484015560c4810135600584015560e48101356006840155610104810135600784015561ffff90614cc39061012401616c09565b60088401805461ffff1916929091169190911790556024356102408402016101448101356009840155610164810135600a840155614d1d90614d089061018401616be0565b600b84019060ff801983541691151516179055565b614d55614d356101a461024086026024350101616c18565b600b84019064ffffffff0082549160081b169064ffffffff001916179055565b614d95614d6d6101c461024086026024350101616c18565b600b84019068ffffffff000000000082549160281b169068ffffffff00000000001916179055565b6024356102408402016101e401356001600160801b03811690036103eb5760019382600b614ffb94015461ffff60c81b614dda61020461024089026024350101616c09565b60c81b1660ff60d81b614df86024356102408a020161022401616be0565b7fffffffff00000000000000000000000000000000000000ffffffffffffffffff9093166024356102408a02016101e4013560481b78ffffffffffffffffffffffffffffffff00000000000000000016179190911791151560d81b1617600b820155600c8101805467ffffffffffffffff19166001600160401b038b1617815591614e838489616a8c565b52614fee5f5160206182af5f395f51905f52602083549489850154600286015460ff600388015416600488015460058901549060068a01549260078b01549461ffff60088d0154169660806040519e8f908c82019d8e52600460408301526060820152015260a08d015260c08c015260e08b01526101008a01526101208901526101408801526101608701526101608652614f20610180876167d9565b60098501549060ff6001600160401b03600b600a890154980154925416916040519785890194855260408901528181161515606089015263ffffffff8160081c16608089015263ffffffff8160281c1660a08901526001600160801b038160481c1660c089015261ffff8160c81c1660e089015260d81c1615156101008701526101208601526101208552614fb7610140866167d9565b60405194859383850197518091895e84019083820190610100518252519283915e010161010051815203601f1981018352826167d9565b519020611ba2828b616a8c565b9361501161022461024084026024350101616be0565b6040805191151582526001600160401b0388166020830152602435610240850201606481013593604490910135929035917f9738e55ce3503cada06dd3d3338d42623309a94afb5495119306f4a19a92350b91a40191614baf565b826101005152601260205260406101005120600160ff19825416179055601354600160401b811015611643576150ae611e0b8260018794016013556013616a34565b90558435610100515260146020526150cc8360406101005120616a49565b614c27565b836326ef68cf60e01b610100515235600452602461010051fd5b7f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168287823b156103eb5761470b92604051808095819463abf1570d60e01b8352610100519660048401616aa0565b61514d818385616bc2565b9485356101005152600660205260ff604061010051205416156154045760208601351580156153cf575b6153ad57906153236001928735610100515260156020526040610100512060208901356101005152602052604061010051206001600160401b03600782015460081c1615615388575b8835815560208901358582015560ff6151db60408b01616bed565b1660ff19600283015416176002820155606089013560038201556080890135600482015560a0890135600582015560c0890135600682015561523761522260e08b01616be0565b600783019060ff801983541691151516179055565b60078101805468ffffffffffffffff00191660088c901b68ffffffffffffffff001617905561526b60208a01358a35616b4e565b615275838a616a8c565b528054906001600160401b03868201549160ff600282015416906003810154600482015460058301549160076006850154940154946040519760208901995f5160206182af5f395f51905f528b52600560408b015260608a0152608089015260a088015260c087015260e086015261010085015261012084015260ff8116151561014084015260081c166101608201526101608152615316610180826167d9565b519020611ba2828d616a8c565b9561533060e08201616be0565b7f4b9c1fac36a2aeaf69e62fe75b58c9e69ec27b26ca89e6cde77296d160d452da6040518061537f8c6020870135963595839092916001600160401b0360209160408401951515845216910152565b0390a301614b9f565b8835610100515260166020526153a860208a013560406101005120616a49565b6151c0565b602086638940a78560e01b610100515280356004520135602452604461010051fd5b506153dc60e08701616be0565b8015615177575060ff6153f160408801616bed565b1615806151775750606086013515615177565b85633f4b99b160e21b610100515235600452602461010051fd5b909182358152602083013560208201526040830135604082015260ff61544660608501616611565b1660608201526080830135608082015260a083013560a082015260c083013560c082015260e083013560e082015261ffff6154846101008501616966565b166101008201526101208301356101208201526101408301356101408201526154b0610160840161687a565b151561016082015263ffffffff6154ca6101808501616869565b1661018082015263ffffffff6154e36101a08501616869565b166101a08201526101c0830135906001600160801b0382168092036103eb57610240816001936101c08394015261ffff6155206101e08801616966565b166101e0820152615534610200870161687a565b15156102008201526001600160401b036155516102208801616417565b166102208201520193019101614a96565b9061012080600192853581526020860135602082015260ff61558660408801616611565b166040820152606086013560608201526080860135608082015260a086013560a082015260c086013560c08201526155c060e0870161687a565b151560e08201526001600160401b036155dc6101008801616417565b166101008201520193019101919091614a70565b508515614a3c565b346103eb576156063661645b565b9061560f616b0b565b506101005152601560205260406101005120906101005152602052610120604061010051206001600160401b03604051916156498361674e565b8054928381526001820154916020820192835260ff600282015416604083019081526003820154606084019081526004830154906080850191825260ff60058501549360a08701948552600760068701549660c089019788520154978961010060e08a0199858c1615158b52019960081c1689526040519a8b525160208b0152511660408901525160608801525160808701525160a08601525160c085015251151560e08401525116610100820152f35b346103eb57610100513660031901126103eb576040517f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168152602090f35b346103eb5760603660031901126103eb576004356001600160401b0381116103eb57615771903690600401616580565b9061577a6163eb565b6044356001600160401b0381116103eb5761579990369060040161642b565b90916002548015612d41578515612d1a57600354936001600160401b0385169360405160208101908960608201888452604080840152526080810189610100515b8c8110615c99575050926001600160401b039795928261580b61434097946158799a9703601f1981018352826167d9565b519020610100515060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527f7d6f52ebac641da43fc11d5911cdb8aabf4931733ac493d1f5540628174e88d360808301528a871660a083015260c082015260c08152610edf60e0826167d9565b16906001600160401b03191617600355601954906001600160401b036158a0818416616975565b1680926001600160401b031916176019556158ba836169be565b916158c4846169be565b93610100515b818110615928577f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168587823b156103eb5761470b92604051808095819463abf1570d60e01b8352610100519660048401616aa0565b615933818386616bc2565b610120813603126103eb576040519061594b8261674e565b6159d461010082359283855261596360208201616611565b602086015261597460408201616966565b604086015261598560608201616869565b606086015261599660808201616966565b60808601526159a760a08201616417565b60a08601526159b860c08201616966565b60c08601526159c960e0820161687a565b60e086015201616417565b6101008301526101005152600660205260ff60406101005120541615615c8057604081015160ff60208301511661ffff6001821b83161615615c5c575050908184610100600194015280516101005152600e602052615b1a60406101005120848351918281550160ff806020860151161660ff19825416178155604084015181549066ffffffff000000606087015160181b169068ffff00000000000000608088015160381b169167ffffffffffffffff60481b60a089015160481b1661ffff60881b60c08a015160881b169260e08a0151151560981b946001600160401b0360a01b6101008c015160a01b16966001600160401b0360a01b199460ff60981b199361ffff60881b199262ffff0067ffffffffffffffff60481b199260081b169068ffffffffffffffff0019161716171617161716179060ff60981b1617179055616df8565b615b248389616a8c565b52805160ff60208301511661ffff60408401511663ffffffff60608501511661ffff6080860151166001600160401b0360a08701511661ffff60c0880151169160e08801511515936001600160401b036101008a015116956040519760208901995f5160206182af5f395f51905f528b52600760408b015260608a0152608089015260a088015260c087015260e08601526101008501526101208401526101408301526101608201526101608152615bde610180826167d9565b519020615beb838a616a8c565b527ffb325cc55af6e4500b502905b39f0679ffc3ea5f77a1534709444fdb64a99f0a60a082519260ff6020820151169061ffff6040820151169061ffff608063ffffffff6060840151169201511691604051938452602084015260408301526060820152876080820152a2016158ca565b61ffff9251633ecb481360e21b610100515260045260245216604452606461010051fd5b51633f4b99b160e21b6101005152600452602461010051fd5b9091610120806001928535815260ff615cb460208801616611565b16602082015261ffff615cc960408801616966565b16604082015263ffffffff615ce060608801616869565b16606082015261ffff615cf560808801616966565b1660808201526001600160401b03615d0f60a08801616417565b1660a082015261ffff615d2460c08801616966565b1660c0820152615d3660e0870161687a565b151560e08201526001600160401b03615d526101008801616417565b1661010082015201930191016157da565b346103eb5760803660031901126103eb57615d7c616527565b50615d8561653d565b506064356001600160401b0381116103eb57615da5903690600401616553565b5050604051630a85bd0160e11b8152602090f35b346103eb576020610a3c615dcc3661645b565b90616b88565b346103eb576020610a3c615de53661645b565b90616b4e565b346103eb5760203660031901126103eb57600435610100515260146020526040610100512060405190816020825491828152019161010051526020610100512090610100515b818110615e485761054685610532818703826167d9565b8254845260209093019260019283019201615e31565b346103eb57615e6c3661645b565b90615e75616b0b565b506101005152600a6020526040610100512090610100515260205261012060406101005120600560405191615ea98361674e565b805483526001810154602084015261ffff600282015460ff8116604086015260ff8160081c16151560608601526001600160401b038160101c16608086015260501c1660a0840152600381015460c0840152600481015460e08401520154610100820152612f6c6040518092616471565b346103eb5760203660031901126103eb576020610a3c600435616ad3565b3461387757606036600319011261387757600435906001600160401b0382116138775736602383011215613877578160040135906001600160401b038211613877573660248360071b8501011161387757615f916163eb565b906044356001600160401b03811161387757615fb190369060040161642b565b92906002549081156163dc5785156163cd57600354946001600160401b0386169460208101908860608201888452604080840152526080810160248b015f5b8b8110616380575050926001600160401b0397959282616021614340979461608a9a9703601f1981018352826167d9565b51902060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527fe3b31cc24d29add9661dc3a1f58e781f69070e12dc384bcd05cb5622a2f1c57b60808301528a871660a083015260c082015260c08152610edf60e0826167d9565b16906001600160401b031916176003556019546001600160401b036160b0818316616975565b1680916001600160401b031916176019556160ca826169be565b906160d4836169be565b935f5b848110156162f5578060071b820190608060231983360301126138775760405161610081616733565b60248301359283825260208201916044820135835261613460846161266064850161687a565b936040840194855201616417565b5060608101948786525f52600c6020528960ff60405f20541615616265575b91817fed4d5e005c9676e1dcabfeb18ab560394b1c30a4409c368d6b8e5b76095135359361624587600199606096515f52600b6020528d6161ec836145818c6161e7600260405f208c5193848255516001820155016161c18d511515829060ff801983541691151516179055565b8751815468ffffffffffffffff00191660089190911b68ffffffffffffffff0016179055565b616dac565b528451908951906001600160401b0388511515915116906040519260208401945f5160206182af5f395f51905f528652600360408601528b850152608084015260a083015260c082015260c081526145fe60e0826167d9565b52519351905115156040519182526020820152876040820152a2016160d7565b5080515f52600c60205260405f20600160ff19825416179055805191600d5491600160401b8310156162e1578b616245876001997fed4d5e005c9676e1dcabfeb18ab560394b1c30a4409c368d6b8e5b7609513535976162d1611e0b898e60609b01600d55600d616a34565b9055995050509193509150616153565b634e487b7160e01b5f52604160045260245ffd5b7f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168487823b156138775761634b925f928360405180968195829463abf1570d60e01b845260048401616aa0565b03925af1801561637557616360576101005180f35b5f61636a916167d9565b5f610100528061471f565b6040513d5f823e3d90fd5b909160808060019285358152602086013560208201526163a26040870161687a565b151560408201526001600160401b036163bd60608801616417565b1660608201520193019101615ff0565b63c2e5347d60e01b5f5260045ffd5b63d311bc3960e01b5f5260045ffd5b602435906001600160401b038216820361387757565b604435906001600160401b038216820361387757565b35906001600160401b038216820361387757565b9181601f84011215613877578235916001600160401b038311613877576020808501948460051b01011161387757565b6040906003190112613877576004359060243590565b6101008091805184526020810151602085015260ff60408201511660408501526060810151151560608501526001600160401b03608082015116608085015261ffff60a08201511660a085015260c081015160c085015260e081015160e08501520151910152565b90602080835192838152019201905f5b8181106164f65750505090565b82518452602093840193909201916001016164e9565b34613877575f36600319011261387757602060405160018152f35b600435906001600160a01b038216820361387757565b602435906001600160a01b038216820361387757565b9181601f84011215613877578235916001600160401b038311613877576020838186019501011161387757565b9181601f84011215613877578235916001600160401b03831161387757602080850194610120850201011161387757565b34613877575f36600319011261387757602060405160028152f35b34613877575f36600319011261387757602060405160038152f35b606090600319011261387757600435906024359060443590565b6004359060ff8216820361387757565b359060ff8216820361387757565b6001600160401b0360c08092805185526020810151602086015260ff60408201511660408601526060810151606086015261ffff608082015116608086015260a0810151151560a0860152015116910152565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90815181526020820151602082015260ff60408301511660408201526101206001600160401b03816166ee6166dc60608701516101406060880152610140870190616672565b60808701518682036080880152616672565b9460ff60a08201511660a086015263ffffffff60c08201511660c086015263ffffffff60e08201511660e0860152610100810151151561010086015201511691015290565b608081019081106001600160401b038211176162e157604052565b61012081019081106001600160401b038211176162e157604052565b6102c081019081106001600160401b038211176162e157604052565b61024081019081106001600160401b038211176162e157604052565b60e081019081106001600160401b038211176162e157604052565b61014081019081106001600160401b038211176162e157604052565b90601f801991011681019081106001600160401b038211176162e157604052565b6001600160401b0381116162e157601f01601f191660200190565b929192616821826167fa565b9161682f60405193846167d9565b829481845281830111613877578281602093845f960137010152565b9080601f830112156138775781602061686693359101616815565b90565b359063ffffffff8216820361387757565b3590811515820361387757565b9190916101408184031261387757604051906168a2826167bd565b819381358352602082013560208401526168be60408301616611565b604084015260608201356001600160401b03811161387757816168e291840161684b565b60608401526080820135906001600160401b038211613877578261691061012094926169619486940161684b565b608086015261692160a08201616611565b60a086015261693260c08201616869565b60c086015261694360e08201616869565b60e0860152616955610100820161687a565b61010086015201616417565b910152565b359061ffff8216820361387757565b6001600160401b036001911601906001600160401b03821161699357565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116162e15760051b60200190565b906169c8826169a7565b6169d560405191826167d9565b82815280926169e6601f19916169a7565b0190602036910137565b600454811015616a085760045f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b600754811015616a085760075f5260205f2001905f90565b8054821015616a08575f5260205f2001905f90565b805490600160401b8210156162e15781611e0b916001616a6b94018155616a34565b9055565b805115616a085760200190565b805160011015616a085760400190565b8051821015616a085760209160051b010190565b9091616ac56168669360068452600160208501526080604085015260808401906164d9565b9160608184039101526164d9565b60405160208101915f51602061824f5f395f51905f5283525f6040830152606082015260608152616b056080826167d9565b51902090565b60405190616b188261674e565b5f610100838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201520152565b906040519060208201925f51602061824f5f395f51905f528452600560408401526060830152608082015260808152616b0560a0826167d9565b906040519060208201925f51602061824f5f395f51905f528452600860408401526060830152608082015260808152616b0560a0826167d9565b9190811015616a0857610120020190565b9190820180921161699357565b3580151581036138775790565b3560ff811681036138775790565b5f1981146169935760010190565b3561ffff811681036138775790565b3563ffffffff811681036138775790565b60405190616c368261676a565b5f6102a0838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e082015282610200820152826102208201528261024082015282610260820152826102808201520152565b60405190616cd482616786565b5f610220838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e0820152826102008201520152565b916040519160208301935f51602061824f5f395f51905f528552600460408501526060840152608083015260a082015260a08152616b0560c0826167d9565b60405190616d9582616733565b5f6060838281528260208201528260408201520152565b60405160208101915f51602061824f5f395f51905f52835260036040830152606082015260608152616b056080826167d9565b90616dea9291617833565b8015616df35790565b505f90565b60405160208101915f51602061824f5f395f51905f52835260076040830152606082015260608152616b056080826167d9565b906040519060208201928352604082015260408152616b056060826167d9565b906040519060208201925f51602061824f5f395f51905f528452600260408401526060830152608082015260808152616b0560a0826167d9565b60405190616e92826167a2565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b9035601e19823603018112156138775701602081359101916001600160401b03821161387757813603831361387757565b908060209392818452848401375f828201840152601f01601f1916010190565b60041115616f1657565b634e487b7160e01b5f52602160045260245ffd5b3d15616f54573d90616f3b826167fa565b91616f4960405193846167d9565b82523d5f602084013e565b606090565b60405190616f66826167bd565b5f61012083828152826020820152826040820152606080820152606060808201528260a08201528260c08201528260e0820152826101008201520152565b90600182811c92168015616fd2575b6020831014616fbe57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691616fb3565b9060405191825f825492616fef84616fa4565b808452936001811690811561705a5750600114617016575b50617014925003836167d9565b565b90505f9291925260205f20905f915b81831061703e575050906020617014928201015f617007565b6020919350806001915483858901015201910190918492617025565b90506020925061701494915060ff191682840152151560051b8201015f617007565b90604051617089816167bd565b6101206001600160401b0360058395805485526001810154602086015260ff60028201541660408601526170bf60038201616fdc565b60608601526170d060048201616fdc565b6080860152015460ff811660a085015263ffffffff8160081c1660c085015263ffffffff8160281c1660e085015260ff8160481c16151561010085015260501c16910152565b80519060208101519060ff604082015116906060810151602081519101206001600160401b0361012060808401516020815191012093015116926040519460208601967f8db2525bc769e56adb6fd2402771f48c08c4503c9dc0bc507a776eaca65d700f885260408701526060860152608085015260a084015260c083015260e082015260e08152616b05610100826167d9565b356001600160401b03811681036138775790565b60405160208101915f51602061824f5f395f51905f52835260016040830152606082015260608152616b056080826167d9565b8051906060810151906001600160401b0361024060ff60808401511692015116906040519260208401947f2d9686edff91aeaad85c9917be9d5ee284127481a229d9857b748073fba5b1c0865260408501526060840152608083015260a082015260a08152616b0560c0826167d9565b60405160208101917faf7a3b9335082a3b4f22ddf585b52ecc40819755789412bb4253e79b45db1ab383525f51602061826f5f395f51905f526040830152606082015260608152616b056080826167d9565b906040519060208201927faf7a3b9335082a3b4f22ddf585b52ecc40819755789412bb4253e79b45db1ab384526040830152606082015260608152616b056080826167d9565b90617303826169a7565b61731060405191826167d9565b8281528092617321601f19916169a7565b01905f5b82811061733157505050565b60209061733c616f59565b82828501015201617325565b90617352826169a7565b61735f60405191826167d9565b8281528092617370601f19916169a7565b01905f5b82811061738057505050565b60209061738b616b0b565b82828501015201617374565b356001600160a01b03811681036138775790565b90816020910312613877575180151581036138775790565b93919594965f958815617635576001600160401b031643811161761f57804303438111616993576102581061760957506040519360208501526020845261740b6040856167d9565b5f975f965b888810156175df578760051b840135607e1985360301811215613877578401996174398b617397565b6001600160a01b0391821691168110156175b357506174578a617397565b9961749860208761746784617397565b604051632e4bfa5160e11b81526001600160a01b039091166004820152602481019190915291829081906044820190565b03816001600160a01b038d165afa908115616375575f91617585575b501561755e576020810190600460ff6174cc84616bed565b160361752b576174dd88828b617c02565b156174f75750506174ef600191616bfb565b970196617410565b9061750c61750660ff93617397565b91616bed565b9063bbf82ba360e01b5f5260018060a01b03166004521660245260445ffd5b9061753a61750660ff93617397565b9063587548c360e11b5f5260018060a01b031660045216602452600460445260645ffd5b6175688691617397565b63ae8bb03960e01b5f5260018060a01b031660045260245260445ffd5b6175a6915060203d81116175ac575b61759e81836167d9565b8101906173ab565b5f6174b4565b503d617594565b6175bc8b617397565b6311641feb60e21b5f9081526004929092526001600160a01b0316602452604490fd5b985095509550505050508083106175f35750565b826305bc216760e51b5f5260045260245260445ffd5b630ed38fd160e41b5f526004524360245260445ffd5b637b51505560e01b5f526004524360245260445ffd5b631fc460bf60e11b5f5260045ffd5b60408101359081156177c55761765d6102008201616be0565b1561782f5761ffff6176726101008301616c09565b16158015617823575b6177c55760608101600260ff61769083616bed565b16146177fc57600160ff6176a383616bed565b16141590816177e4575b506177c557602081013590815f52600660205260ff60405f205416156177b257815f52601560205260405f2060a082013590815f5260205260405f2090159081156177a2575b5061773857610120810135158015617795575b61773857610180810163ffffffff61771d82616c18565b1615908115617779575b8115617751575b5061773857505050565b638307aca560e01b5f523560045260245260445260645ffd5b905063ffffffff8061776f6177696101a08601616c18565b93616c18565b169116115f61772e565b905063ffffffff61778d6101a08401616c18565b161590617727565b5061014081013515617706565b60ff91506007015416155f6176f3565b50633f4b99b160e21b5f5260045260245ffd5b602090638307aca560e01b5f528035600452013560245260445260645ffd5b600391506177f360ff91616bed565b1614155f6176ad565b506020810135908115801590617816575b61773857505050565b5060a0810135151561780d565b5060808101351561767b565b5050565b9061783d82616f0c565b8115617982575f92839261785081616f0c565b600181146179595760029061786481616f0c565b146178d157604051627eeac760e11b6020820190815230602483015260448201929092526178958160648101613304565b51915afa6178a1616f2a565b90806178c5575b15616df35760208151918180820193849201010312613877575190565b506020815110156178a8565b60405160208101916331a9108f60e11b83526024820152602481526178f76044826167d9565b51915afa617903616f2a565b8161794b575b8161791e575b501561791a57600190565b5f90565b905060208180518101031261387757602001516001600160a01b038116908190036138775730145f61790f565b905060208151101590617909565b505060405160208101906370a0823160e01b8252306024820152602481526178956044826167d9565b5050504790565b5f546001600160a01b031680151590816179ab575b5061701457617014617d44565b905033145f61799e565b90813b15617a34575f816020829351910182855af16179d2616f2a565b9015908115617a04575b506179e45750565b6365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b8051801515925082617a19575b50505f6179dc565b617a2c92506020809183010191016173ab565b155f80617a11565b506365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b90916001600160a01b038111617a6a57505050565b617a93604051938493639a49f29360e01b85526004850152606060248501526064840190616672565b9060448301520390fd5b9080519060208101519060ff60408201511660608201516020815191012060808301516020815191012060ff60a0850151169063ffffffff60c0860151169263ffffffff60e087015116946001600160401b03610120610100890151151598015116976040519960208b019b5f5160206182af5f395f51905f528d52600160408d015260608c015260808b015260a08a015260c089015260e08801526101008701526101208601526101408501526101608401526101808301526101a08201526101a08152616b056101c0826167d9565b602081830312613877578051906001600160401b038211613877570181601f8201121561387757805190617ba1826167fa565b92617baf60405194856167d9565b8284526020838301011161387757815f9260208093018386015e8301015290565b903590601e198136030182121561387757018035906001600160401b0382116138775760200191813603831361387757565b9160208201600460ff617c1483616bed565b1614617cc35760ff617c27600592616bed565b1614617c34575050505f90565b5f617c3e83617397565b604051639e5adaeb60e01b81526001600160a01b0391821660048201529485916024918391165afa91821561637557616866935f93617c97575b50617c8a816040617c91930190617bd0565b3691616815565b91617fcc565b617c91919350617cbb617c8a913d805f833e617cb381836167d9565b810190617b6e565b939150617c78565b505f617cce83617397565b60405163b7af85d760e01b81526001600160a01b0391821660048201529485916024918391165afa91821561637557616866935f93617d20575b50617c8a816040617d1a930190617bd0565b91617f03565b617d1a919350617d3c617c8a913d805f833e617cb381836167d9565b939150617d08565b6040516328305db160e21b81527f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b031690602081600481855afa908115616375575f91617ee4575b501580617e6c575b617e695760405163e14c465b60e01b8152602081600481855afa908115616375575f91617e35575b50604051632e4bfa5160e11b815233600482015260248101919091529060209082908180604481015b03915afa908115616375575f91617e16575b506170145763321cbc0960e21b5f523360045260245ffd5b617e2f915060203d6020116175ac5761759e81836167d9565b5f617dfe565b90506020813d602011617e61575b81617e50602093836167d9565b810103126138775751617dec617dc3565b3d9150617e43565b50565b5060405163f5778b0360e01b8152602081600481855afa908115616375575f91617ea2575b506001600160a01b03163314617d9b565b90506020813d602011617edc575b81617ebd602093836167d9565b8101031261387757516001600160a01b0381168103613877575f617e91565b3d9150617eb0565b617efd915060203d6020116175ac5761759e81836167d9565b5f617d93565b610a20815114801590617fbf575b617fb8576020617f645f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f1981018352826167d9565b51906102045afa617f73616f2a565b81617fac575b81617f82575090565b9050602081519101519060208110617f9b575b50151590565b5f199060200360031b1b165f617f95565b80516020149150617f79565b5050505f90565b5061121383511415617f11565b604081511480159061803b575b617fb857602061802c5f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f1981018352826167d9565b51906102055afa617f73616f2a565b5061746083511415617fd9565b9080156182485760015b8181106181b657505f5b81811061817557505b6001811161807b575061807790616a6f565b5190565b5f905f5b81811061808d575050618065565b6001810180821161699357828114618157576180a98286616a8c565b51905f916180b78288616a8c565b51111561813b576180c88387616a8c565b5191616993576180d89086616a8c565b515b604051906020820192600160f81b845260218301526041820152604181526181036061826167d9565b51902061811961811285616bfb565b9486616a8c565b525b600281018091111561807f57634e487b7160e01b5f52601160045260245ffd5b618146915085616a8c565b516181518286616a8c565b516180da565b506181628185616a8c565b5161816f61811285616bfb565b5261811b565b8061818260019285616a8c565b5160405160208101915f83526021820152602181526181a26041826167d9565b5190206181af8286616a8c565b520161805c565b926181c384849394616a8c565b5192845b801580158061822a575b15618211575f19820191808311616993576181f76181ef8488616a8c565b519187616a8c565b52156181c757634e487b7160e01b5f52601160045260245ffd5b50936182236001939692949586616a8c565b5201618052565b505f19820182811161699357618241879187616a8c565b51116181d1565b50505f9056febe98de78ab1a89101b9b77b1b3013a49647e63c349ee09cc55e0a33f97e351a984e75c8576483b53128ed23c26158522388ddb0a5df8516c83887d522196f91fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267553bfca378ab6d3ea8c58df6b962c50de5515a3d2878713fa6dd8d84f220809ff7643c83e539cea2f6bf506545392e52cfd5f917e327efbcd0ba28f29c28d042e
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
0026PUSH20x5f38
0029JUMPI
002aPOP
002bDUP1
002cPUSH40x036f9a43
0031EQ
0032PUSH20x5f1a
0035JUMPI
0036DUP1
0037PUSH40x0489d038
003cEQ
003dPUSH20x5e5e
0040JUMPI
0041DUP1
0042PUSH40x087a5cf4
0047EQ
0048PUSH20x5deb
004bJUMPI
004cDUP1
004dPUSH40x08f0ce62
0052EQ
0053PUSH20x3af4
0056JUMPI
0057DUP1
0058PUSH40x0900767d
005dEQ
005ePUSH20x5dd2
0061JUMPI
0062DUP1
0063PUSH40x0eca1867
0068EQ
0069PUSH20x5db9
006cJUMPI
006dDUP1
006ePUSH40x150b7a02
0073EQ
0074PUSH20x5d63
0077JUMPI
0078DUP1
0079PUSH40x1591c4a0
007eEQ
007fPUSH20x5741
0082JUMPI
0083DUP1
0084PUSH40x178bcc93
0089EQ
008aPUSH20x56fa
008dJUMPI
008eDUP1
008fPUSH40x1945098e
0094EQ
0095PUSH20x0680
0098JUMPI
0099DUP1
009aPUSH40x1a633473
009fEQ
00a0PUSH20x0560
00a3JUMPI
00a4DUP1
00a5PUSH40x1e000aa5
00aaEQ
00abPUSH20x55f8
00aeJUMPI
00afDUP1
00b0PUSH40x200d43ad
00b5EQ
00b6PUSH20x498e
00b9JUMPI
00baDUP1
00bbPUSH40x20d4f696
00c0EQ
00c1PUSH20x0560
00c4JUMPI
00c5DUP1
00c6PUSH40x210de79d
00cbEQ
00ccPUSH20x493f
00cfJUMPI
00d0DUP1
00d1PUSH40x2b89089c
00d6EQ
00d7PUSH20x4920
00daJUMPI
00dbDUP1
00dcPUSH40x3ec4cc34
00e1EQ
00e2PUSH20x48d5
00e5JUMPI
00e6DUP1
00e7PUSH40x3fb27b85
00ecEQ
00edPUSH20x4866
00f0JUMPI
00f1DUP1
00f2PUSH40x42cde4e8
00f7EQ
00f8PUSH20x4846
00fbJUMPI
00fcDUP1
00fdPUSH40x4760fd43
0102EQ
0103PUSH20x47da
0106JUMPI
0107DUP1
0108PUSH40x4bcb06a9
010dEQ
010ePUSH20x47c1
0111JUMPI
0112DUP1
0113PUSH40x4e2ed4bb
0118EQ
0119PUSH20x41e7
011cJUMPI
011dDUP1
011ePUSH40x4ec824ff
0123EQ
0124PUSH20x3af4
0127JUMPI
0128DUP1
0129PUSH40x5483a0b5
012eEQ
012fPUSH20x419f
0132JUMPI
0133DUP1
0134PUSH40x57d70648
0139EQ
013aPUSH20x4181
013dJUMPI
013eDUP1
013fPUSH40x5b88f50e
0144EQ
0145PUSH20x3af4
0148JUMPI
0149DUP1
014aPUSH40x5eb320b8
014fEQ
0150PUSH20x4161
0153JUMPI
0154DUP1
0155PUSH40x5f61b15c
015aEQ
015bPUSH20x3af4
015eJUMPI
015fDUP1
0160PUSH40x608ed2ec
0165EQ
0166PUSH20x40ee
0169JUMPI
016aDUP1
016bPUSH40x60a18008
0170EQ
0171PUSH20x40ba
0174JUMPI
0175DUP1
0176PUSH40x60c29fac
017bEQ
017cPUSH20x0680
017fJUMPI
0180DUP1
0181PUSH40x622c3f21
0186EQ
0187PUSH20x409b
018aJUMPI
018bDUP1
018cPUSH40x65cea49c
0191EQ
0192PUSH20x407d
0195JUMPI
0196DUP1
0197PUSH40x68cf465e
019cEQ
019dPUSH20x0680
01a0JUMPI
01a1DUP1
01a2PUSH40x6c2c44e2
01a7EQ
01a8PUSH20x4064
01abJUMPI
01acDUP1
01adPUSH40x6db81752
01b2EQ
01b3PUSH20x4044
01b6JUMPI
01b7DUP1
01b8PUSH40x711f3db4
01bdEQ
01bePUSH20x3f62
01c1JUMPI
01c2DUP1
01c3PUSH40x767c39d2
01c8EQ
01c9PUSH20x0560
01ccJUMPI
01cdDUP1
01cePUSH40x7b103999
01d3EQ
01d4PUSH20x3f1b
01d7JUMPI
01d8DUP1
01d9PUSH40x7bb737bc
01deEQ
01dfPUSH20x3f02
01e2JUMPI
01e3DUP1
01e4PUSH40x7dcdde8e
01e9EQ
01eaPUSH20x3d7f
01edJUMPI
01eeDUP1
01efPUSH40x7f49d69f
01f4EQ
01f5PUSH20x3d55
01f8JUMPI
01f9DUP1
01faPUSH40x831f33a1
01ffEQ
0200PUSH20x3b35
0203JUMPI
0204DUP1
0205PUSH40x8513e2d5
020aEQ
020bPUSH20x3b17
020eJUMPI
020fDUP1
0210PUSH40x852a52e8
0215EQ
0216PUSH20x3af9
0219JUMPI
021aDUP1
021bPUSH40x88f06eaa
0220EQ
0221PUSH20x3af4
0224JUMPI
0225DUP1
0226PUSH40x92a2e0b3
022bEQ
022cPUSH20x3ad6
022fJUMPI
0230DUP1
0231PUSH40x9377c220
0236EQ
0237PUSH20x3aad
023aJUMPI
023bDUP1
023cPUSH40x9493ba1d
0241EQ
0242PUSH20x3a3a
0245JUMPI
0246DUP1
0247PUSH40x94bc4e96
024cEQ
024dPUSH20x372d
0250JUMPI
0251DUP1
0252PUSH40x95102c42
0257EQ
0258PUSH20x370e
025bJUMPI
025cDUP1
025dPUSH40x9648eca2
0262EQ
0263PUSH20x344e
0266JUMPI
0267DUP1
0268PUSH40x96f51f3a
026dEQ
026ePUSH20x3199
0271JUMPI
0272DUP1
0273PUSH40x97b9857b
0278EQ
0279PUSH20x312d
027cJUMPI
027dDUP1
027ePUSH40xa741113c
0283EQ
0284PUSH20x30f3
0287JUMPI
0288DUP1
0289PUSH40xaa9239f5
028eEQ
028fPUSH20x3097
0292JUMPI
0293DUP1
0294PUSH40xac65bc70
0299EQ
029aPUSH20x2f6e
029dJUMPI
029eDUP1
029fPUSH40xad844b96
02a4EQ
02a5PUSH20x2ec5
02a8JUMPI
02a9DUP1
02aaPUSH40xaffed0e0
02afEQ
02b0PUSH20x2e9c
02b3JUMPI
02b4DUP1
02b5PUSH40xb19f4805
02baEQ
02bbPUSH20x2e5f
02beJUMPI
02bfDUP1
02c0PUSH40xb8da302a
02c5EQ
02c6PUSH20x2e3f
02c9JUMPI
02caDUP1
02cbPUSH40xb9a7f076
02d0EQ
02d1PUSH20x2d56
02d4JUMPI
02d5DUP1
02d6PUSH40xb9ed4686
02dbEQ
02dcPUSH20x0cba
02dfJUMPI
02e0DUP1
02e1PUSH40xbbbcda44
02e6EQ
02e7PUSH20x0c9c
02eaJUMPI
02ebDUP1
02ecPUSH40xbc197c81
02f1EQ
02f2PUSH20x0c04
02f5JUMPI
02f6DUP1
02f7PUSH40xbda1019e
02fcEQ
02fdPUSH20x0be4
0300JUMPI
0301DUP1
0302PUSH40xbe380c85
0307EQ
0308PUSH20x0a63
030bJUMPI
030cDUP1
030dPUSH40xc5dbda66
0312EQ
0313PUSH20x0a44
0316JUMPI
0317DUP1
0318PUSH40xdc544a09
031dEQ
031ePUSH20x0a23
0321JUMPI
0322DUP1
0323PUSH40xde54d429
0328EQ
0329PUSH20x0a03
032cJUMPI
032dDUP1
032ePUSH40xdf46c713
0333EQ
0334PUSH20x089d
0337JUMPI
0338DUP1
0339PUSH40xe02e1bfd
033eEQ
033fPUSH20x087d
0342JUMPI
0343DUP1
0344PUSH40xe597e659
0349EQ
034aPUSH20x0685
034dJUMPI
034eDUP1
034fPUSH40xe70475a7
0354EQ
0355PUSH20x0680
0358JUMPI
0359DUP1
035aPUSH40xe94a52af
035fEQ
0360PUSH20x0662
0363JUMPI
0364DUP1
0365PUSH40xeafe7a74
036aEQ
036bPUSH20x0642
036eJUMPI
036fDUP1
0370PUSH40xeb777382
0375EQ
0376PUSH20x0565
0379JUMPI
037aDUP1
037bPUSH40xee076a50
0380EQ
0381PUSH20x0560
0384JUMPI
0385DUP1
0386PUSH40xee57e33c
038bEQ
038cPUSH20x04cb
038fJUMPI
0390DUP1
0391PUSH40xf23a6e61
0396EQ
0397PUSH20x0475
039aJUMPI
039bDUP1
039cPUSH40xf3178f43
03a1EQ
03a2PUSH20x0412
03a5JUMPI
03a6DUP1
03a7PUSH40xf4e885db
03acEQ
03adPUSH20x03f2
03b0JUMPI
03b1PUSH40xf851a440
03b6EQ
03b7PUSH20x03be
03baJUMPI
03bbPUSH0
03bcDUP1
03bdREVERT
03beJUMPDEST
03bfCALLVALUE
03c0PUSH20x03eb
03c3JUMPI
03c4PUSH20x0100
03c7MLOAD
03c8CALLDATASIZE
03c9PUSH10x03
03cbNOT
03ccADD
03cdSLT
03cePUSH20x03eb
03d1JUMPI
03d2PUSH20x0100
03d5MLOAD
03d6SLOAD
03d7PUSH10x40
03d9MLOAD
03daPUSH10x01
03dcPUSH10x01
03dePUSH10xa0
03e0SHL
03e1SUB
03e2SWAP1
03e3SWAP2
03e4AND
03e5DUP2
03e6MSTORE
03e7PUSH10x20
03e9SWAP1
03eaRETURN
03ebJUMPDEST
03ecPUSH20x0100
03efMLOAD
03f0DUP1
03f1REVERT
03f2JUMPDEST
03f3CALLVALUE
03f4PUSH20x03eb
03f7JUMPI
03f8PUSH20x0100
03fbMLOAD
03fcCALLDATASIZE
03fdPUSH10x03
03ffNOT
0400ADD
0401SLT
0402PUSH20x03eb
0405JUMPI
0406PUSH10x20
0408PUSH10x01
040aSLOAD
040bPUSH10x40
040dMLOAD
040eSWAP1
040fDUP2
0410MSTORE
0411RETURN
0412JUMPDEST
0413CALLVALUE
0414PUSH20x03eb
0417JUMPI
0418PUSH10x40
041aCALLDATASIZE
041bPUSH10x03
041dNOT
041eADD
041fSLT
0420PUSH20x03eb
0423JUMPI
0424PUSH20x042b
0427PUSH20x63eb
042aJUMP
042bJUMPDEST
042cPUSH10x04
042eCALLDATALOAD
042fPUSH20x0100
0432MLOAD
0433MSTORE
0434PUSH10x0a
0436PUSH10x20
0438MSTORE
0439PUSH20x0451
043cPUSH10x01
043ePUSH10x01
0440PUSH10x40
0442SHL
0443SUB
0444PUSH10x40
0446PUSH20x0100
0449MLOAD
044aKECCAK256
044bSWAP3
044cAND
044dPUSH20x7261
0450JUMP
0451JUMPDEST
0452PUSH20x0100
0455MLOAD
0456MSTORE
0457PUSH10x20
0459MSTORE
045aPUSH10x20
045cPUSH20xffff
045fPUSH10x02
0461PUSH10x40
0463PUSH20x0100
0466MLOAD
0467KECCAK256
0468ADD
0469SLOAD
046aPUSH10x50
046cSHR
046dAND
046ePUSH10x40
0470MLOAD
0471SWAP1
0472DUP2
0473MSTORE
0474RETURN
0475JUMPDEST
0476CALLVALUE
0477PUSH20x03eb
047aJUMPI
047bPUSH10xa0
047dCALLDATASIZE
047ePUSH10x03
0480NOT
0481ADD
0482SLT
0483PUSH20x03eb
0486JUMPI
0487PUSH20x048e
048aPUSH20x6527
048dJUMP
048eJUMPDEST
048fPOP
0490PUSH20x0497
0493PUSH20x653d
0496JUMP
0497JUMPDEST
0498POP
0499PUSH10x84
049bCALLDATALOAD
049cPUSH10x01
049ePUSH10x01
04a0PUSH10x40
04a2SHL
04a3SUB
04a4DUP2
04a5GT
04a6PUSH20x03eb
04a9JUMPI
04aaPUSH20x04b7
04adSWAP1
04aeCALLDATASIZE
04afSWAP1
04b0PUSH10x04
04b2ADD
04b3PUSH20x6553
04b6JUMP
04b7JUMPDEST
04b8POP
04b9POP
04baPUSH10x40
04bcMLOAD
04bdPUSH40xf23a6e61
04c2PUSH10xe0
04c4SHL
04c5DUP2
04c6MSTORE
04c7PUSH10x20
04c9SWAP1
04caRETURN
04cbJUMPDEST
04ccCALLVALUE
04cdPUSH20x03eb
04d0JUMPI
04d1PUSH20x0100
04d4MLOAD
04d5CALLDATASIZE
04d6PUSH10x03
04d8NOT
04d9ADD
04daSLT
04dbPUSH20x03eb
04deJUMPI
04dfPUSH10x40
04e1MLOAD
04e2DUP1
04e3PUSH10x20
04e5PUSH10x13
04e7SLOAD
04e8SWAP2
04e9DUP3
04eaDUP2
04ebMSTORE
04ecADD
04edSWAP1
04eePUSH10x13
04f0PUSH20x0100
04f3MLOAD
04f4MSTORE
04f5PUSH320x66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090
0516SWAP1
0517PUSH20x0100
051aMLOAD
051bJUMPDEST
051cDUP2
051dDUP2
051eLT
051fPUSH20x054a
0522JUMPI
0523PUSH20x0546
0526DUP6
0527PUSH20x0532
052aDUP2
052bDUP8
052cSUB
052dDUP3
052ePUSH20x67d9
0531JUMP
0532JUMPDEST
0533PUSH10x40
0535MLOAD
0536SWAP2
0537DUP3
0538SWAP2
0539PUSH10x20
053bDUP4
053cMSTORE
053dPUSH10x20
053fDUP4
0540ADD
0541SWAP1
0542PUSH20x64d9
0545JUMP
0546JUMPDEST
0547SUB
0548SWAP1
0549RETURN
054aJUMPDEST
054bDUP3
054cSLOAD
054dDUP5
054eMSTORE
054fPUSH10x20
0551SWAP1
0552SWAP4
0553ADD
0554SWAP3
0555PUSH10x01
0557SWAP3
0558DUP4
0559ADD
055aSWAP3
055bADD
055cPUSH20x051b
055fJUMP
0560JUMPDEST
0561PUSH20x65cc
0564JUMP
0565JUMPDEST
0566CALLVALUE
0567PUSH20x03eb
056aJUMPI
056bPUSH20x0100
056eMLOAD
056fCALLDATASIZE
0570PUSH10x03
0572NOT
0573ADD
0574SLT
0575PUSH20x03eb
0578JUMPI
0579PUSH10x04
057bSLOAD
057cPUSH20x0584
057fDUP2
0580PUSH20x69be
0583JUMP
0584JUMPDEST
0585PUSH20x0100
0588MLOAD
0589SWAP1
058aSWAP2
058bDUP2
058cJUMPDEST
058dDUP2
058eDUP2
058fLT
0590PUSH20x05e0
0593JUMPI
0594POP
0595POP
0596PUSH20x059e
0599DUP2
059aPUSH20x69be
059dJUMP
059eJUMPDEST
059fSWAP2
05a0PUSH20x0100
05a3MLOAD
05a4JUMPDEST
05a5DUP3
05a6DUP2
05a7LT
05a8PUSH20x05c1
05abJUMPI
05acPUSH10x40
05aeMLOAD
05afPUSH10x20
05b1DUP1
05b2DUP3
05b3MSTORE
05b4DUP2
05b5SWAP1
05b6PUSH20x0546
05b9SWAP1
05baDUP3
05bbADD
05bcDUP8
05bdPUSH20x64d9
05c0JUMP
05c1JUMPDEST
05c2DUP1
05c3PUSH20x05ce
05c6PUSH10x01
05c8SWAP3
05c9DUP5
05caPUSH20x6a8c
05cdJUMP
05ceJUMPDEST
05cfMLOAD
05d0PUSH20x05d9
05d3DUP3
05d4DUP8
05d5PUSH20x6a8c
05d8JUMP
05d9JUMPDEST
05daMSTORE
05dbADD
05dcPUSH20x05a4
05dfJUMP
05e0JUMPDEST
05e1DUP1
05e2PUSH20x05ec
05e5PUSH10x01
05e7SWAP3
05e8PUSH20x69f0
05ebJUMP
05ecJUMPDEST
05edSWAP1
05eeSLOAD
05efSWAP1
05f0PUSH10x03
05f2SHL
05f3SHR
05f4PUSH20x0100
05f7MLOAD
05f8MSTORE
05f9PUSH10x05
05fbPUSH10x20
05fdMSTORE
05fePUSH10xff
0600PUSH10x0c
0602PUSH10x40
0604PUSH20x0100
0607MLOAD
0608KECCAK256
0609ADD
060aSLOAD
060bPUSH10x08
060dSHR
060eAND
060fPUSH20x0619
0612JUMPI
0613JUMPDEST
0614ADD
0615PUSH20x058c
0618JUMP
0619JUMPDEST
061aPUSH20x0622
061dDUP2
061ePUSH20x69f0
0621JUMP
0622JUMPDEST
0623SWAP1
0624SLOAD
0625SWAP1
0626PUSH10x03
0628SHL
0629SHR
062aPUSH20x063c
062dPUSH20x0635
0630DUP7
0631PUSH20x6bfb
0634JUMP
0635JUMPDEST
0636SWAP6
0637DUP8
0638PUSH20x6a8c
063bJUMP
063cJUMPDEST
063dMSTORE
063ePUSH20x0613
0641JUMP
0642JUMPDEST
0643CALLVALUE
0644PUSH20x03eb
0647JUMPI
0648PUSH20x0100
064bMLOAD
064cCALLDATASIZE
064dPUSH10x03
064fNOT
0650ADD
0651SLT
0652PUSH20x03eb
0655JUMPI
0656PUSH10x20
0658PUSH10x07
065aSLOAD
065bPUSH10x40
065dMLOAD
065eSWAP1
065fDUP2
0660MSTORE
0661RETURN
0662JUMPDEST
0663CALLVALUE
0664PUSH20x03eb
0667JUMPI
0668PUSH20x0100
066bMLOAD
066cCALLDATASIZE
066dPUSH10x03
066fNOT
0670ADD
0671SLT
0672PUSH20x03eb
0675JUMPI
0676PUSH10x20
0678PUSH10x40
067aMLOAD
067bPUSH10x05
067dDUP2
067eMSTORE
067fRETURN
0680JUMPDEST
0681PUSH20x65b1
0684JUMP
0685JUMPDEST
0686CALLVALUE
0687PUSH20x03eb
068aJUMPI
068bPUSH10x40
068dCALLDATASIZE
068ePUSH10x03
0690NOT
0691ADD
0692SLT
0693PUSH20x03eb
0696JUMPI
0697PUSH10x24
0699CALLDATALOAD
069aPUSH10x04
069cCALLDATALOAD
069dPUSH10xff
069fDUP3
06a0AND
06a1DUP3
06a2SUB
06a3PUSH20x03eb
06a6JUMPI
06a7PUSH10x07
06a9SLOAD
06aaPUSH20x06b2
06adDUP2
06aePUSH20x7348
06b1JUMP
06b2JUMPDEST
06b3PUSH20x0100
06b6MLOAD
06b7SWAP1
06b8SWAP4
06b9SWAP1
06baSWAP3
06bbDUP4
06bcJUMPDEST
06bdDUP4
06beDUP2
06bfLT
06c0PUSH20x0759
06c3JUMPI
06c4DUP6
06c5DUP6
06c6PUSH20x06ce
06c9DUP2
06caPUSH20x7348
06cdJUMP
06ceJUMPDEST
06cfSWAP2
06d0PUSH20x0100
06d3MLOAD
06d4JUMPDEST
06d5DUP3
06d6DUP2
06d7LT
06d8PUSH20x072f
06dbJUMPI
06dcDUP4
06ddPUSH10x40
06dfMLOAD
06e0DUP1
06e1SWAP2
06e2PUSH10x20
06e4DUP3
06e5ADD
06e6PUSH10x20
06e8DUP4
06e9MSTORE
06eaDUP2
06ebMLOAD
06ecDUP1
06edSWAP2
06eeMSTORE
06efPUSH10x20
06f1PUSH10x40
06f3DUP5
06f4ADD
06f5SWAP3
06f6ADD
06f7SWAP1
06f8PUSH20x0100
06fbMLOAD
06fcJUMPDEST
06fdDUP2
06feDUP2
06ffLT
0700PUSH20x070a
0703JUMPI
0704POP
0705POP
0706POP
0707SUB
0708SWAP1
0709RETURN
070aJUMPDEST
070bSWAP2
070cSWAP4
070dPOP
070eSWAP2
070fPUSH10x20
0711PUSH20x0120
0714DUP3
0715PUSH20x0721
0718PUSH10x01
071aSWAP5
071bDUP9
071cMLOAD
071dPUSH20x6471
0720JUMP
0721JUMPDEST
0722ADD
0723SWAP5
0724ADD
0725SWAP2
0726ADD
0727SWAP2
0728DUP5
0729SWAP4
072aSWAP3
072bPUSH20x06fc
072eJUMP
072fJUMPDEST
0730DUP1
0731PUSH20x073c
0734PUSH10x01
0736SWAP3
0737DUP5
0738PUSH20x6a8c
073bJUMP
073cJUMPDEST
073dMLOAD
073ePUSH20x0747
0741DUP3
0742DUP8
0743PUSH20x6a8c
0746JUMP
0747JUMPDEST
0748MSTORE
0749PUSH20x0752
074cDUP2
074dDUP7
074ePUSH20x6a8c
0751JUMP
0752JUMPDEST
0753POP
0754ADD
0755PUSH20x06d4
0758JUMP
0759JUMPDEST
075aPUSH20x0762
075dDUP2
075ePUSH20x6a1c
0761JUMP
0762JUMPDEST
0763SWAP1
0764SLOAD
0765SWAP1
0766PUSH10x03
0768SHL
0769SHR
076aDUP1
076bPUSH20x0100
076eMLOAD
076fMSTORE
0770PUSH10x08
0772PUSH10x20
0774MSTORE
0775PUSH10xff
0777PUSH10x05
0779PUSH10x40
077bPUSH20x0100
077eMLOAD
077fKECCAK256
0780ADD
0781SLOAD
0782PUSH10x48
0784SHR
0785AND
0786ISZERO
0787PUSH20x0874
078aJUMPI
078bPUSH20x0100
078eMLOAD
078fMSTORE
0790PUSH10x0a
0792PUSH10x20
0794MSTORE
0795PUSH10x40
0797PUSH20x0100
079aMLOAD
079bKECCAK256
079cDUP3
079dPUSH20x0100
07a0MLOAD
07a1MSTORE
07a2PUSH10x20
07a4MSTORE
07a5PUSH10x40
07a7PUSH20x0100
07aaMLOAD
07abKECCAK256
07acPUSH10x40
07aeMLOAD
07afSWAP1
07b0PUSH20x07b8
07b3DUP3
07b4PUSH20x674e
07b7JUMP
07b8JUMPDEST
07b9DUP1
07baSLOAD
07bbDUP3
07bcMSTORE
07bdPUSH10x01
07bfDUP2
07c0ADD
07c1SLOAD
07c2PUSH10x20
07c4DUP4
07c5ADD
07c6MSTORE
07c7DUP5
07c8PUSH10x02
07caDUP3
07cbADD
07ccSLOAD
07cdPUSH10xff
07cfDUP2
07d0AND
07d1PUSH10x40
07d3DUP6
07d4ADD
07d5MSTORE
07d6PUSH10x05
07d8PUSH10xff
07daDUP3
07dbPUSH10x08
07ddSHR
07deAND
07dfISZERO
07e0ISZERO
07e1SWAP4
07e2DUP5
07e3PUSH10x60
07e5DUP8
07e6ADD
07e7MSTORE
07e8PUSH10x01
07eaPUSH10x01
07ecPUSH10x40
07eeSHL
07efSUB
07f0DUP4
07f1PUSH10x10
07f3SHR
07f4AND
07f5PUSH10x80
07f7DUP8
07f8ADD
07f9MSTORE
07faPUSH20xffff
07fdDUP4
07fePUSH10x50
0800SHR
0801AND
0802PUSH10xa0
0804DUP8
0805ADD
0806MSTORE
0807PUSH10x03
0809DUP2
080aADD
080bSLOAD
080cPUSH10xc0
080eDUP8
080fADD
0810MSTORE
0811PUSH10x04
0813DUP2
0814ADD
0815SLOAD
0816PUSH10xe0
0818DUP8
0819ADD
081aMSTORE
081bADD
081cSLOAD
081dPUSH20x0100
0820DUP6
0821ADD
0822MSTORE
0823DUP3
0824PUSH20x0865
0827JUMPI
0828JUMPDEST
0829POP
082aPOP
082bPUSH20x083a
082eJUMPI
082fJUMPDEST
0830POP
0831PUSH10x01
0833SWAP1
0834JUMPDEST
0835ADD
0836PUSH20x06bc
0839JUMP
083aJUMPDEST
083bSWAP5
083cSWAP1
083dPUSH20x085e
0840DUP3
0841PUSH20x084c
0844PUSH10x01
0846SWAP5
0847SWAP2
0848PUSH20x6bfb
084bJUMP
084cJUMPDEST
084dSWAP8
084ePUSH20x0857
0851DUP3
0852DUP12
0853PUSH20x6a8c
0856JUMP
0857JUMPDEST
0858MSTORE
0859DUP9
085aPUSH20x6a8c
085dJUMP
085eJUMPDEST
085fPOP
0860SWAP1
0861PUSH20x082f
0864JUMP
0865JUMPDEST
0866PUSH10xff
0868SWAP3
0869POP
086aAND
086bAND
086cISZERO
086dISZERO
086eDUP5
086fDUP10
0870PUSH20x0828
0873JUMP
0874JUMPDEST
0875POP
0876PUSH10x01
0878SWAP1
0879PUSH20x0834
087cJUMP
087dJUMPDEST
087eCALLVALUE
087fPUSH20x03eb
0882JUMPI
0883PUSH20x0100
0886MLOAD
0887CALLDATASIZE
0888PUSH10x03
088aNOT
088bADD
088cSLT
088dPUSH20x03eb
0890JUMPI
0891PUSH10x20
0893PUSH10x04
0895SLOAD
0896PUSH10x40
0898MLOAD
0899SWAP1
089aDUP2
089bMSTORE
089cRETURN
089dJUMPDEST
089eCALLVALUE
089fPUSH20x03eb
08a2JUMPI
08a3PUSH10x20
08a5CALLDATASIZE
08a6PUSH10x03
08a8NOT
08a9ADD
08aaSLT
08abPUSH20x03eb
08aeJUMPI
08afPUSH20x08b6
08b2PUSH20x6601
08b5JUMP
08b6JUMPDEST
08b7PUSH10x07
08b9SLOAD
08baSWAP1
08bbPUSH20x08c3
08beDUP3
08bfPUSH20x72f9
08c2JUMP
08c3JUMPDEST
08c4PUSH20x0100
08c7MLOAD
08c8SWAP1
08c9SWAP3
08caSWAP1
08cbSWAP2
08ccDUP3
08cdJUMPDEST
08ceDUP3
08cfDUP2
08d0LT
08d1PUSH20x0981
08d4JUMPI
08d5POP
08d6POP
08d7POP
08d8PUSH20x08e0
08dbDUP2
08dcPUSH20x72f9
08dfJUMP
08e0JUMPDEST
08e1SWAP2
08e2PUSH20x0100
08e5MLOAD
08e6JUMPDEST
08e7DUP3
08e8DUP2
08e9LT
08eaPUSH20x0957
08edJUMPI
08eeDUP4
08efPUSH10x40
08f1MLOAD
08f2DUP1
08f3SWAP2
08f4PUSH10x20
08f6DUP3
08f7ADD
08f8PUSH10x20
08faDUP4
08fbMSTORE
08fcDUP2
08fdMLOAD
08feDUP1
08ffSWAP2
0900MSTORE
0901PUSH10x40
0903DUP4
0904ADD
0905SWAP1
0906PUSH10x20
0908PUSH10x40
090aDUP3
090bPUSH10x05
090dSHL
090eDUP7
090fADD
0910ADD
0911SWAP4
0912ADD
0913SWAP2
0914PUSH20x0100
0917MLOAD
0918SWAP1
0919JUMPDEST
091aDUP3
091bDUP3
091cLT
091dPUSH20x0928
0920JUMPI
0921POP
0922POP
0923POP
0924POP
0925SUB
0926SWAP1
0927RETURN
0928JUMPDEST
0929SWAP2
092aSWAP4
092bPUSH10x01
092dSWAP2
092eSWAP4
092fSWAP6
0930POP
0931PUSH10x20
0933PUSH20x0947
0936DUP2
0937SWAP3
0938PUSH10x3f
093aNOT
093bDUP11
093cDUP3
093dSUB
093eADD
093fDUP7
0940MSTORE
0941DUP9
0942MLOAD
0943PUSH20x6696
0946JUMP
0947JUMPDEST
0948SWAP7
0949ADD
094aSWAP3
094bADD
094cSWAP3
094dADD
094eDUP6
094fSWAP5
0950SWAP4
0951SWAP2
0952SWAP3
0953PUSH20x0919
0956JUMP
0957JUMPDEST
0958DUP1
0959PUSH20x0964
095cPUSH10x01
095eSWAP3
095fDUP5
0960PUSH20x6a8c
0963JUMP
0964JUMPDEST
0965MLOAD
0966PUSH20x096f
0969DUP3
096aDUP8
096bPUSH20x6a8c
096eJUMP
096fJUMPDEST
0970MSTORE
0971PUSH20x097a
0974DUP2
0975DUP7
0976PUSH20x6a8c
0979JUMP
097aJUMPDEST
097bPOP
097cADD
097dPUSH20x08e6
0980JUMP
0981JUMPDEST
0982PUSH20x098a
0985DUP2
0986PUSH20x6a1c
0989JUMP
098aJUMPDEST
098bSWAP1
098cSLOAD
098dSWAP1
098ePUSH10x03
0990SHL
0991SHR
0992PUSH20x0100
0995MLOAD
0996MSTORE
0997PUSH10x08
0999PUSH10x20
099bMSTORE
099cPUSH20x09aa
099fPUSH10x40
09a1PUSH20x0100
09a4MLOAD
09a5KECCAK256
09a6PUSH20x707c
09a9JUMP
09aaJUMPDEST
09abPUSH20x0100
09aeDUP2
09afADD
09b0MLOAD
09b1ISZERO
09b2ISZERO
09b3DUP1
09b4PUSH20x09f1
09b7JUMPI
09b8JUMPDEST
09b9PUSH20x09c6
09bcJUMPI
09bdJUMPDEST
09bePOP
09bfPUSH10x01
09c1ADD
09c2PUSH20x08cd
09c5JUMP
09c6JUMPDEST
09c7SWAP4
09c8SWAP1
09c9PUSH20x09ea
09ccDUP3
09cdPUSH20x09d8
09d0PUSH10x01
09d2SWAP5
09d3SWAP2
09d4PUSH20x6bfb
09d7JUMP
09d8JUMPDEST
09d9SWAP7
09daPUSH20x09e3
09ddDUP3
09deDUP11
09dfPUSH20x6a8c
09e2JUMP
09e3JUMPDEST
09e4MSTORE
09e5DUP8
09e6PUSH20x6a8c
09e9JUMP
09eaJUMPDEST
09ebPOP
09ecSWAP1
09edPUSH20x09bd
09f0JUMP
09f1JUMPDEST
09f2POP
09f3PUSH10xff
09f5DUP4
09f6PUSH10xa0
09f8DUP4
09f9ADD
09faMLOAD
09fbAND
09fcAND
09fdISZERO
09feISZERO
09ffPUSH20x09b8
0a02JUMP
0a03JUMPDEST
0a04CALLVALUE
0a05PUSH20x03eb
0a08JUMPI
0a09PUSH20x0100
0a0cMLOAD
0a0dCALLDATASIZE
0a0ePUSH10x03
0a10NOT
0a11ADD
0a12SLT
0a13PUSH20x03eb
0a16JUMPI
0a17PUSH10x20
0a19PUSH10x0d
0a1bSLOAD
0a1cPUSH10x40
0a1eMLOAD
0a1fSWAP1
0a20DUP2
0a21MSTORE
0a22RETURN
0a23JUMPDEST
0a24CALLVALUE
0a25PUSH20x03eb
0a28JUMPI
0a29PUSH10x20
0a2bPUSH20x0a3c
0a2ePUSH20x0a36
0a31CALLDATASIZE
0a32PUSH20x645b
0a35JUMP
0a36JUMPDEST
0a37SWAP1
0a38PUSH20x72b3
0a3bJUMP
0a3cJUMPDEST
0a3dPUSH10x40
0a3fMLOAD
0a40SWAP1
0a41DUP2
0a42MSTORE
0a43RETURN
0a44JUMPDEST
0a45CALLVALUE
0a46PUSH20x03eb
0a49JUMPI
0a4aPUSH20x0100
0a4dMLOAD
0a4eCALLDATASIZE
0a4fPUSH10x03
0a51NOT
0a52ADD
0a53SLT
0a54PUSH20x03eb
0a57JUMPI
0a58PUSH10x20
0a5aPUSH10x40
0a5cMLOAD
0a5dPUSH20x01f4
0a60DUP2
0a61MSTORE
0a62RETURN
0a63JUMPDEST
0a64CALLVALUE
0a65PUSH20x03eb
0a68JUMPI
0a69PUSH20x02c0
0a6cCALLDATASIZE
0a6dPUSH10x03
0a6fNOT
0a70ADD
0a71SLT
0a72PUSH20x03eb
0a75JUMPI
0a76PUSH10x40
0a78MLOAD
0a79PUSH20x0a81
0a7cDUP2
0a7dPUSH20x676a
0a80JUMP
0a81JUMPDEST
0a82PUSH10x04
0a84CALLDATALOAD
0a85DUP2
0a86MSTORE
0a87PUSH10x24
0a89CALLDATALOAD
0a8aPUSH10x20
0a8cDUP3
0a8dADD
0a8eMSTORE
0a8fPUSH10x44
0a91CALLDATALOAD
0a92PUSH10x40
0a94DUP3
0a95ADD
0a96MSTORE
0a97PUSH10x64
0a99CALLDATALOAD
0a9aPUSH10x60
0a9cDUP3
0a9dADD
0a9eMSTORE
0a9fPUSH10x84
0aa1CALLDATALOAD
0aa2PUSH10xff
0aa4DUP2
0aa5AND
0aa6DUP2
0aa7SUB
0aa8PUSH20x03eb
0aabJUMPI
0aacPUSH10x80
0aaeDUP3
0aafADD
0ab0MSTORE
0ab1PUSH10xa4
0ab3CALLDATALOAD
0ab4PUSH10xa0
0ab6DUP3
0ab7ADD
0ab8MSTORE
0ab9PUSH10xc4
0abbCALLDATALOAD
0abcPUSH10xc0
0abeDUP3
0abfADD
0ac0MSTORE
0ac1PUSH10xe4
0ac3CALLDATALOAD
0ac4PUSH10xff
0ac6DUP2
0ac7AND
0ac8DUP2
0ac9SUB
0acaPUSH20x03eb
0acdJUMPI
0acePUSH10xe0
0ad0DUP3
0ad1ADD
0ad2MSTORE
0ad3PUSH20x0104
0ad6CALLDATALOAD
0ad7PUSH10x01
0ad9PUSH10x01
0adbPUSH10x40
0addSHL
0adeSUB
0adfDUP2
0ae0AND
0ae1DUP2
0ae2SUB
0ae3PUSH20x03eb
0ae6JUMPI
0ae7PUSH20x0100
0aeaDUP3
0aebADD
0aecMSTORE
0aedPUSH20x0124
0af0CALLDATALOAD
0af1PUSH40xffffffff
0af6DUP2
0af7AND
0af8DUP2
0af9SUB
0afaPUSH20x03eb
0afdJUMPI
0afePUSH20x0120
0b01DUP3
0b02ADD
0b03MSTORE
0b04PUSH20x0144
0b07CALLDATALOAD
0b08PUSH20xffff
0b0bDUP2
0b0cAND
0b0dDUP2
0b0eSUB
0b0fPUSH20x03eb
0b12JUMPI
0b13PUSH20x0140
0b16DUP3
0b17ADD
0b18MSTORE
0b19PUSH20x0164
0b1cCALLDATALOAD
0b1dPUSH20xffff
0b20DUP2
0b21AND
0b22DUP2
0b23SUB
0b24PUSH20x03eb
0b27JUMPI
0b28PUSH20x0160
0b2bDUP3
0b2cADD
0b2dMSTORE
0b2ePUSH20x0184
0b31CALLDATALOAD
0b32PUSH20x0180
0b35DUP3
0b36ADD
0b37MSTORE
0b38PUSH20x01a4
0b3bCALLDATALOAD
0b3cPUSH10xff
0b3eDUP2
0b3fAND
0b40DUP2
0b41SUB
0b42PUSH20x03eb
0b45JUMPI
0b46PUSH20x01a0
0b49DUP3
0b4aADD
0b4bMSTORE
0b4cPUSH20x01c4
0b4fCALLDATALOAD
0b50PUSH20x01c0
0b53DUP3
0b54ADD
0b55MSTORE
0b56PUSH20x01e4
0b59CALLDATALOAD
0b5aPUSH20x01e0
0b5dDUP3
0b5eADD
0b5fMSTORE
0b60PUSH20x0204
0b63CALLDATALOAD
0b64PUSH10xff
0b66DUP2
0b67AND
0b68DUP2
0b69SUB
0b6aPUSH20x03eb
0b6dJUMPI
0b6ePUSH20x0200
0b71DUP3
0b72ADD
0b73MSTORE
0b74PUSH20x0224
0b77CALLDATALOAD
0b78DUP1
0b79ISZERO
0b7aISZERO
0b7bDUP2
0b7cSUB
0b7dPUSH20x03eb
0b80JUMPI
0b81PUSH20x0220
0b84DUP3
0b85ADD
0b86MSTORE
0b87PUSH20x0244
0b8aCALLDATALOAD
0b8bPUSH10x01
0b8dPUSH10x01
0b8fPUSH10x40
0b91SHL
0b92SUB
0b93DUP2
0b94AND
0b95DUP2
0b96SUB
0b97PUSH20x03eb
0b9aJUMPI
0b9bPUSH20x0240
0b9eDUP3
0b9fADD
0ba0MSTORE
0ba1PUSH20x0264
0ba4CALLDATALOAD
0ba5PUSH10x01
0ba7PUSH10x01
0ba9PUSH10x40
0babSHL
0bacSUB
0badDUP2
0baeAND
0bafDUP2
0bb0SUB
0bb1PUSH20x03eb
0bb4JUMPI
0bb5PUSH20x0260
0bb8DUP3
0bb9ADD
0bbaMSTORE
0bbbPUSH20x0284
0bbeCALLDATALOAD
0bbfPUSH10xff
0bc1DUP2
0bc2AND
0bc3DUP2
0bc4SUB
0bc5PUSH20x03eb
0bc8JUMPI
0bc9DUP2
0bcaPUSH20x0a3c
0bcdSWAP2
0bcePUSH20x0280
0bd1PUSH10x20
0bd3SWAP5
0bd4ADD
0bd5MSTORE
0bd6PUSH20x02a4
0bd9CALLDATALOAD
0bdaPUSH20x02a0
0bddDUP3
0bdeADD
0bdfMSTORE
0be0PUSH20x71f1
0be3JUMP
0be4JUMPDEST
0be5CALLVALUE
0be6PUSH20x03eb
0be9JUMPI
0beaPUSH20x0100
0bedMLOAD
0beeCALLDATASIZE
0befPUSH10x03
0bf1NOT
0bf2ADD
0bf3SLT
0bf4PUSH20x03eb
0bf7JUMPI
0bf8PUSH10x20
0bfaPUSH10x17
0bfcSLOAD
0bfdPUSH10x40
0bffMLOAD
0c00SWAP1
0c01DUP2
0c02MSTORE
0c03RETURN
0c04JUMPDEST
0c05CALLVALUE
0c06PUSH20x03eb
0c09JUMPI
0c0aPUSH10xa0
0c0cCALLDATASIZE
0c0dPUSH10x03
0c0fNOT
0c10ADD
0c11SLT
0c12PUSH20x03eb
0c15JUMPI
0c16PUSH20x0c1d
0c19PUSH20x6527
0c1cJUMP
0c1dJUMPDEST
0c1ePOP
0c1fPUSH20x0c26
0c22PUSH20x653d
0c25JUMP
0c26JUMPDEST
0c27POP
0c28PUSH10x44
0c2aCALLDATALOAD
0c2bPUSH10x01
0c2dPUSH10x01
0c2fPUSH10x40
0c31SHL
0c32SUB
0c33DUP2
0c34GT
0c35PUSH20x03eb
0c38JUMPI
0c39PUSH20x0c46
0c3cSWAP1
0c3dCALLDATASIZE
0c3eSWAP1
0c3fPUSH10x04
0c41ADD
0c42PUSH20x642b
0c45JUMP
0c46JUMPDEST
0c47POP
0c48POP
0c49PUSH10x64
0c4bCALLDATALOAD
0c4cPUSH10x01
0c4ePUSH10x01
0c50PUSH10x40
0c52SHL
0c53SUB
0c54DUP2
0c55GT
0c56PUSH20x03eb
0c59JUMPI
0c5aPUSH20x0c67
0c5dSWAP1
0c5eCALLDATASIZE
0c5fSWAP1
0c60PUSH10x04
0c62ADD
0c63PUSH20x642b
0c66JUMP
0c67JUMPDEST
0c68POP
0c69POP
0c6aPUSH10x84
0c6cCALLDATALOAD
0c6dPUSH10x01
0c6fPUSH10x01
0c71PUSH10x40
0c73SHL
0c74SUB
0c75DUP2
0c76GT
0c77PUSH20x03eb
0c7aJUMPI
0c7bPUSH20x0c88
0c7eSWAP1
0c7fCALLDATASIZE
0c80SWAP1
0c81PUSH10x04
0c83ADD
0c84PUSH20x6553
0c87JUMP
0c88JUMPDEST
0c89POP
0c8aPOP
0c8bPUSH10x40
0c8dMLOAD
0c8ePUSH40xbc197c81
0c93PUSH10xe0
0c95SHL
0c96DUP2
0c97MSTORE
0c98PUSH10x20
0c9aSWAP1
0c9bRETURN
0c9cJUMPDEST
0c9dCALLVALUE
0c9ePUSH20x03eb
0ca1JUMPI
0ca2PUSH10x20
0ca4CALLDATASIZE
0ca5PUSH10x03
0ca7NOT
0ca8ADD
0ca9SLT
0caaPUSH20x03eb
0cadJUMPI
0caePUSH10x20
0cb0PUSH20x0a3c
0cb3PUSH10x04
0cb5CALLDATALOAD
0cb6PUSH20x71be
0cb9JUMP
0cbaJUMPDEST
0cbbCALLVALUE
0cbcPUSH20x03eb
0cbfJUMPI
0cc0PUSH10xa0
0cc2CALLDATASIZE
0cc3PUSH10x03
0cc5NOT
0cc6ADD
0cc7SLT
0cc8PUSH20x03eb
0ccbJUMPI
0cccPUSH10x01
0ccePUSH10x01
0cd0PUSH10x40
0cd2SHL
0cd3SUB
0cd4PUSH10x04
0cd6CALLDATALOAD
0cd7GT
0cd8PUSH20x03eb
0cdbJUMPI
0cdcCALLDATASIZE
0cddPUSH10x23
0cdfPUSH10x04
0ce1CALLDATALOAD
0ce2ADD
0ce3SLT
0ce4ISZERO
0ce5PUSH20x03eb
0ce8JUMPI
0ce9PUSH10x01
0cebPUSH10x01
0cedPUSH10x40
0cefSHL
0cf0SUB
0cf1PUSH10x04
0cf3CALLDATALOAD
0cf4PUSH10x04
0cf6ADD
0cf7CALLDATALOAD
0cf8GT
0cf9PUSH20x03eb
0cfcJUMPI
0cfdCALLDATASIZE
0cfePUSH10x24
0d00PUSH20x02c0
0d03PUSH10x04
0d05CALLDATALOAD
0d06PUSH10x04
0d08ADD
0d09CALLDATALOAD
0d0aMUL
0d0bPUSH10x04
0d0dCALLDATALOAD
0d0eADD
0d0fADD
0d10GT
0d11PUSH20x03eb
0d14JUMPI
0d15PUSH10x24
0d17CALLDATALOAD
0d18PUSH10x01
0d1aPUSH10x01
0d1cPUSH10x40
0d1eSHL
0d1fSUB
0d20DUP2
0d21GT
0d22PUSH20x03eb
0d25JUMPI
0d26PUSH20x0d33
0d29SWAP1
0d2aCALLDATASIZE
0d2bSWAP1
0d2cPUSH10x04
0d2eADD
0d2fPUSH20x642b
0d32JUMP
0d33JUMPDEST
0d34PUSH10xc0
0d36MSTORE
0d37PUSH10x44
0d39CALLDATALOAD
0d3aPUSH10x01
0d3cPUSH10x01
0d3ePUSH10x40
0d40SHL
0d41SUB
0d42DUP2
0d43GT
0d44PUSH20x03eb
0d47JUMPI
0d48PUSH20x0d55
0d4bSWAP1
0d4cCALLDATASIZE
0d4dSWAP1
0d4ePUSH10x04
0d50ADD
0d51PUSH20x6580
0d54JUMP
0d55JUMPDEST
0d56PUSH10x64
0d58CALLDATALOAD
0d59SWAP3
0d5aSWAP1
0d5bPUSH10x01
0d5dPUSH10x01
0d5fPUSH10x40
0d61SHL
0d62SUB
0d63DUP5
0d64AND
0d65DUP5
0d66SUB
0d67PUSH20x03eb
0d6aJUMPI
0d6bPUSH10x84
0d6dCALLDATALOAD
0d6ePUSH10x01
0d70PUSH10x01
0d72PUSH10x40
0d74SHL
0d75SUB
0d76DUP2
0d77GT
0d78PUSH20x03eb
0d7bJUMPI
0d7cPUSH20x0d89
0d7fSWAP1
0d80CALLDATASIZE
0d81SWAP1
0d82PUSH10x04
0d84ADD
0d85PUSH20x642b
0d88JUMP
0d89JUMPDEST
0d8aSWAP5
0d8bPUSH10x02
0d8dSLOAD
0d8eDUP1
0d8fISZERO
0d90PUSH20x2d41
0d93JUMPI
0d94PUSH10x04
0d96CALLDATALOAD
0d97PUSH10x04
0d99ADD
0d9aCALLDATALOAD
0d9bISZERO
0d9cDUP1
0d9dPUSH20x2d37
0da0JUMPI
0da1JUMPDEST
0da2DUP1
0da3PUSH20x2d2f
0da6JUMPI
0da7JUMPDEST
0da8PUSH20x2d1a
0dabJUMPI
0dacPUSH10x03
0daeSLOAD
0dafSWAP3
0db0PUSH10x40
0db2MLOAD
0db3SWAP8
0db4PUSH10xa0
0db6DUP10
0db7ADD
0db8PUSH10x01
0dbaPUSH10x01
0dbcPUSH10x40
0dbeSHL
0dbfSUB
0dc0DUP7
0dc1AND
0dc2PUSH10x20
0dc4DUP12
0dc5ADD
0dc6MSTORE
0dc7PUSH10x80
0dc9PUSH10x40
0dcbDUP12
0dccADD
0dcdMSTORE
0dcePUSH10x04
0dd0CALLDATALOAD
0dd1PUSH10x04
0dd3ADD
0dd4CALLDATALOAD
0dd5SWAP1
0dd6MSTORE
0dd7PUSH10xc0
0dd9DUP10
0ddaADD
0ddbPUSH10x24
0dddPUSH10x04
0ddfCALLDATALOAD
0de0ADD
0de1PUSH20x0100
0de4MLOAD
0de5JUMPDEST
0de6PUSH10x04
0de8CALLDATALOAD
0de9PUSH10x04
0debADD
0decCALLDATALOAD
0dedDUP2
0deeLT
0defPUSH20x2b87
0df2JUMPI
0df3POP
0df4POP
0df5PUSH10x1f
0df7NOT
0df8DUP11
0df9DUP3
0dfaSUB
0dfbADD
0dfcPUSH10x60
0dfeDUP12
0dffADD
0e00MSTORE
0e01PUSH10xc0
0e03MLOAD
0e04DUP2
0e05MSTORE
0e06PUSH10x20
0e08DUP2
0e09ADD
0e0aPUSH10x20
0e0cPUSH10xc0
0e0eMLOAD
0e0fPUSH10x05
0e11SHL
0e12DUP4
0e13ADD
0e14ADD
0e15SWAP1
0e16DUP10
0e17SWAP3
0e18PUSH20x0100
0e1bMLOAD
0e1cJUMPDEST
0e1dPUSH10xc0
0e1fMLOAD
0e20DUP2
0e21LT
0e22PUSH20x2a81
0e25JUMPI
0e26POP
0e27POP
0e28POP
0e29PUSH10x20
0e2bSWAP2
0e2cPOP
0e2dPUSH10x1f
0e2fNOT
0e30DUP12
0e31DUP3
0e32SUB
0e33ADD
0e34PUSH10x80
0e36DUP13
0e37ADD
0e38MSTORE
0e39DUP8
0e3aDUP2
0e3bMSTORE
0e3cADD
0e3dSWAP9
0e3eDUP9
0e3fPUSH20x0100
0e42MLOAD
0e43JUMPDEST
0e44DUP9
0e45DUP2
0e46LT
0e47PUSH20x29ea
0e4aJUMPI
0e4bPOP
0e4cPOP
0e4dPUSH20x0e66
0e50DUP2
0e51PUSH20x0f0d
0e54SWAP8
0e55SWAP9
0e56SWAP10
0e57SWAP11
0e58SWAP12
0e59SUB
0e5aPUSH10x1f
0e5cNOT
0e5dDUP2
0e5eADD
0e5fDUP4
0e60MSTORE
0e61DUP3
0e62PUSH20x67d9
0e65JUMP
0e66JUMPDEST
0e67PUSH10x20
0e69DUP2
0e6aMLOAD
0e6bSWAP2
0e6cADD
0e6dKECCAK256
0e6ePUSH20x0100
0e71MLOAD
0e72POP
0e73PUSH10x40
0e75MLOAD
0e76PUSH10x20
0e78DUP2
0e79ADD
0e7aSWAP2
0e7bPUSH0
0e7cMLOAD
0e7dPUSH10x20
0e7fPUSH20x828f
0e82PUSH0
0e83CODECOPY
0e84PUSH0
0e85MLOAD
0e86SWAP1
0e87PUSH0
0e88MSTORE
0e89DUP4
0e8aMSTORE
0e8bCHAINID
0e8cPUSH10x40
0e8eDUP4
0e8fADD
0e90MSTORE
0e91ADDRESS
0e92PUSH10x60
0e94DUP4
0e95ADD
0e96MSTORE
0e97PUSH320xe10634eb0bf7bd6ad00dc59a6c67fe9b09037b979405fa59137e7e530d601414
0eb8PUSH10x80
0ebaDUP4
0ebbADD
0ebcMSTORE
0ebdPUSH10x01
0ebfPUSH10x01
0ec1PUSH10x40
0ec3SHL
0ec4SUB
0ec5DUP8
0ec6AND
0ec7PUSH10xa0
0ec9DUP4
0ecaADD
0ecbMSTORE
0eccPUSH10xc0
0eceDUP3
0ecfADD
0ed0MSTORE
0ed1PUSH10xc0
0ed3DUP2
0ed4MSTORE
0ed5PUSH20x0edf
0ed8PUSH10xe0
0edaDUP3
0edbPUSH20x67d9
0edeJUMP
0edfJUMPDEST
0ee0MLOAD
0ee1SWAP1
0ee2KECCAK256
0ee3SWAP1
0ee4PUSH10x01
0ee6SLOAD
0ee7SWAP3
0ee8PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0f09PUSH20x73c3
0f0cJUMP
0f0dJUMPDEST
0f0ePOP
0f0fPUSH10x01
0f11PUSH10x01
0f13PUSH10x40
0f15SHL
0f16SUB
0f17PUSH20x0f21
0f1aDUP2
0f1bDUP4
0f1cAND
0f1dPUSH20x6975
0f20JUMP
0f21JUMPDEST
0f22AND
0f23SWAP1
0f24PUSH10x01
0f26PUSH10x01
0f28PUSH10x40
0f2aSHL
0f2bSUB
0f2cNOT
0f2dAND
0f2eOR
0f2fPUSH10x03
0f31SSTORE
0f32PUSH10x19
0f34SLOAD
0f35PUSH20x0f46
0f38PUSH10x01
0f3aPUSH10x01
0f3cPUSH10x40
0f3eSHL
0f3fSUB
0f40DUP3
0f41AND
0f42PUSH20x6975
0f45JUMP
0f46JUMPDEST
0f47PUSH10xa0
0f49MSTORE
0f4aPUSH10x01
0f4cPUSH10x01
0f4ePUSH10x40
0f50SHL
0f51SUB
0f52PUSH10xa0
0f54MLOAD
0f55AND
0f56SWAP1
0f57PUSH10x01
0f59PUSH10x01
0f5bPUSH10x40
0f5dSHL
0f5eSUB
0f5fNOT
0f60AND
0f61OR
0f62PUSH10x19
0f64SSTORE
0f65PUSH20x0f76
0f68PUSH10xc0
0f6aMLOAD
0f6bPUSH10x04
0f6dCALLDATALOAD
0f6ePUSH10x04
0f70ADD
0f71CALLDATALOAD
0f72PUSH20x6bd3
0f75JUMP
0f76JUMPDEST
0f77PUSH10x01
0f79PUSH10x01
0f7bPUSH10xff
0f7dSHL
0f7eSUB
0f7fDUP3
0f80AND
0f81DUP3
0f82SUB
0f83PUSH20x29d0
0f86JUMPI
0f87PUSH20x0f97
0f8aPUSH20x0fa8
0f8dSWAP2
0f8eDUP4
0f8fPUSH10x01
0f91SHL
0f92SWAP1
0f93PUSH20x6bd3
0f96JUMP
0f97JUMPDEST
0f98PUSH20x0fa0
0f9bDUP2
0f9cPUSH20x69be
0f9fJUMP
0fa0JUMPDEST
0fa1PUSH10xe0
0fa3MSTORE
0fa4PUSH20x69be
0fa7JUMP
0fa8JUMPDEST
0fa9PUSH20x0100
0facMLOAD
0fadSWAP3
0faeSWAP1
0fafSWAP2
0fb0SWAP1
0fb1DUP4
0fb2JUMPDEST
0fb3PUSH10x04
0fb5CALLDATALOAD
0fb6PUSH10x04
0fb8ADD
0fb9CALLDATALOAD
0fbaDUP6
0fbbLT
0fbcISZERO
0fbdPUSH20x18e0
0fc0JUMPI
0fc1PUSH20x0fdb
0fc4PUSH10x04
0fc6CALLDATALOAD
0fc7PUSH20x02c0
0fcaDUP8
0fcbMUL
0fccADD
0fcdPUSH10x64
0fcfDUP2
0fd0ADD
0fd1CALLDATALOAD
0fd2SWAP1
0fd3PUSH10x44
0fd5ADD
0fd6CALLDATALOAD
0fd7PUSH20x72b3
0fdaJUMP
0fdbJUMPDEST
0fdcPUSH10x24
0fdePUSH20x02c0
0fe1DUP8
0fe2MUL
0fe3PUSH10x04
0fe5CALLDATALOAD
0fe6ADD
0fe7ADD
0fe8CALLDATALOAD
0fe9SUB
0feaPUSH20x189e
0fedJUMPI
0feePUSH20x1002
0ff1PUSH20x0244
0ff4PUSH20x02c0
0ff7DUP8
0ff8MUL
0ff9PUSH10x04
0ffbCALLDATALOAD
0ffcADD
0ffdADD
0ffePUSH20x6be0
1001JUMP
1002JUMPDEST
1003DUP1
1004PUSH20x1811
1007JUMPI
1008JUMPDEST
1009PUSH20x17ec
100cJUMPI
100dPUSH10x01
100fPUSH10xff
1011PUSH20x1025
1014PUSH20x02a4
1017PUSH20x02c0
101aDUP10
101bMUL
101cPUSH10x04
101eCALLDATALOAD
101fADD
1020ADD
1021PUSH20x6bed
1024JUMP
1025JUMPDEST
1026AND
1027GT
1028PUSH20x17ad
102bJUMPI
102cPUSH20x1040
102fPUSH20x0244
1032PUSH20x02c0
1035DUP8
1036MUL
1037PUSH10x04
1039CALLDATALOAD
103aADD
103bADD
103cPUSH20x6be0
103fJUMP
1040JUMPDEST
1041DUP1
1042PUSH20x178e
1045JUMPI
1046JUMPDEST
1047DUP1
1048PUSH20x177a
104bJUMPI
104cJUMPDEST
104dPUSH20x1755
1050JUMPI
1051PUSH10x04
1053CALLDATALOAD
1054PUSH20x02c0
1057DUP7
1058MUL
1059ADD
105aPUSH10x44
105cADD
105dCALLDATALOAD
105ePUSH0
105fMLOAD
1060PUSH10x20
1062PUSH20x826f
1065PUSH0
1066CODECOPY
1067PUSH0
1068MLOAD
1069SWAP1
106aPUSH0
106bMSTORE
106cEQ
106dDUP1
106eDUP1
106fPUSH20x1733
1072JUMPI
1073JUMPDEST
1074PUSH20x16f4
1077JUMPI
1078PUSH20x16ad
107bJUMPI
107cJUMPDEST
107dPUSH20x01e4
1080PUSH20x02c0
1083DUP7
1084MUL
1085PUSH10x04
1087CALLDATALOAD
1088ADD
1089ADD
108aCALLDATALOAD
108bISZERO
108cISZERO
108dDUP1
108ePUSH20x1683
1091JUMPI
1092JUMPDEST
1093PUSH20x165d
1096JUMPI
1097PUSH10x24
1099PUSH20x02c0
109cDUP7
109dMUL
109ePUSH10x04
10a0CALLDATALOAD
10a1ADD
10a2ADD
10a3CALLDATALOAD
10a4PUSH20x0100
10a7MLOAD
10a8MSTORE
10a9PUSH10x06
10abPUSH10x20
10adMSTORE
10aePUSH10xff
10b0PUSH10x40
10b2PUSH20x0100
10b5MLOAD
10b6KECCAK256
10b7SLOAD
10b8AND
10b9ISZERO
10baPUSH20x15ca
10bdJUMPI
10beJUMPDEST
10bfPUSH20x0100
10c2DUP1
10c3MLOAD
10c4PUSH20x02c0
10c7DUP8
10c8MUL
10c9PUSH10x04
10cbCALLDATALOAD
10ccADD
10cdPUSH10x24
10cfDUP2
10d0ADD
10d1CALLDATALOAD
10d2SWAP2
10d3DUP3
10d4SWAP1
10d5MSTORE
10d6PUSH10x05
10d8PUSH10x20
10daMSTORE
10dbSWAP2
10dcMLOAD
10ddPUSH10x40
10dfSWAP1
10e0KECCAK256
10e1SWAP1
10e2DUP2
10e3SSTORE
10e4PUSH10x44
10e6DUP3
10e7ADD
10e8CALLDATALOAD
10e9PUSH10x01
10ebDUP3
10ecADD
10edSSTORE
10eePUSH10x64
10f0DUP3
10f1ADD
10f2CALLDATALOAD
10f3PUSH10x02
10f5DUP3
10f6ADD
10f7SWAP1
10f8DUP2
10f9SSTORE
10faPUSH10x84
10fcDUP4
10fdADD
10feCALLDATALOAD
10ffPUSH10x03
1101DUP4
1102ADD
1103SSTORE
1104SWAP1
1105SWAP2
1106PUSH20x1111
1109SWAP1
110aPUSH10xa4
110cADD
110dPUSH20x6bed
1110JUMP
1111JUMPDEST
1112PUSH10x04
1114DUP4
1115DUP2
1116ADD
1117DUP1
1118SLOAD
1119PUSH10xff
111bNOT
111cAND
111dPUSH10xff
111fSWAP4
1120SWAP1
1121SWAP4
1122AND
1123SWAP3
1124SWAP1
1125SWAP3
1126OR
1127SWAP1
1128SWAP2
1129SSTORE
112aPUSH10xc4
112cSWAP1
112dCALLDATALOAD
112ePUSH20x02c0
1131DUP10
1132MUL
1133ADD
1134SWAP1
1135DUP2
1136ADD
1137CALLDATALOAD
1138PUSH10x05
113aDUP5
113bADD
113cSSTORE
113dPUSH10xe4
113fDUP2
1140ADD
1141CALLDATALOAD
1142PUSH10x06
1144DUP5
1145ADD
1146SSTORE
1147PUSH20x1153
114aSWAP1
114bPUSH20x0104
114eADD
114fPUSH20x6bed
1152JUMP
1153JUMPDEST
1154PUSH10xff
1156AND
1157PUSH10xff
1159NOT
115aPUSH10x07
115cDUP5
115dADD
115eSLOAD
115fAND
1160OR
1161PUSH10x07
1163DUP4
1164ADD
1165SSTORE
1166PUSH20x02c0
1169DUP8
116aMUL
116bPUSH10x04
116dCALLDATALOAD
116eADD
116fPUSH20x0124
1172ADD
1173PUSH20x117b
1176SWAP1
1177PUSH20x71aa
117aJUMP
117bJUMPDEST
117cPUSH10x07
117eDUP4
117fADD
1180SWAP1
1181PUSH20x11a8
1184SWAP2
1185SWAP1
1186PUSH90xffffffffffffffff00
1190DUP3
1191SLOAD
1192SWAP2
1193PUSH10x08
1195SHL
1196AND
1197SWAP1
1198PUSH90xffffffffffffffff00
11a2NOT
11a3AND
11a4OR
11a5SWAP1
11a6SSTORE
11a7JUMP
11a8JUMPDEST
11a9PUSH20x11bd
11acPUSH20x0144
11afPUSH10x04
11b1CALLDATALOAD
11b2PUSH20x02c0
11b5DUP11
11b6MUL
11b7ADD
11b8ADD
11b9PUSH20x6c18
11bcJUMP
11bdJUMPDEST
11bePUSH10x07
11c0DUP4
11c1ADD
11c2SLOAD
11c3PUSH20xffff
11c6PUSH10x68
11c8SHL
11c9PUSH20x11dd
11ccPUSH20x0164
11cfPUSH10x04
11d1CALLDATALOAD
11d2PUSH20x02c0
11d5DUP14
11d6MUL
11d7ADD
11d8ADD
11d9PUSH20x6c09
11dcJUMP
11ddJUMPDEST
11dePUSH10x68
11e0SHL
11e1AND
11e2SWAP1
11e3PUSH20xffff
11e6PUSH10x78
11e8SHL
11e9PUSH20x11fd
11ecPUSH20x0184
11efPUSH10x04
11f1CALLDATALOAD
11f2PUSH20x02c0
11f5DUP15
11f6MUL
11f7ADD
11f8ADD
11f9PUSH20x6c09
11fcJUMP
11fdJUMPDEST
11fePUSH10x78
1200SHL
1201AND
1202SWAP3
1203PUSH10x48
1205SHL
1206PUSH130xffffffff000000000000000000
1214AND
1215SWAP1
1216PUSH80xffffffffffffffff
121fPUSH10x48
1221SHL
1222NOT
1223AND
1224OR
1225OR
1226OR
1227PUSH10x07
1229DUP4
122aADD
122bSSTORE
122cPUSH20x02c0
122fDUP8
1230MUL
1231PUSH10x04
1233CALLDATALOAD
1234ADD
1235PUSH20x01a4
1238ADD
1239CALLDATALOAD
123aPUSH10x08
123cDUP4
123dADD
123eSSTORE
123fPUSH20x02c0
1242DUP8
1243MUL
1244PUSH10x04
1246CALLDATALOAD
1247ADD
1248PUSH20x01c4
124bADD
124cPUSH20x1254
124fSWAP1
1250PUSH20x6bed
1253JUMP
1254JUMPDEST
1255PUSH10x09
1257DUP4
1258ADD
1259DUP1
125aSLOAD
125bPUSH10xff
125dNOT
125eAND
125fPUSH10xff
1261SWAP3
1262SWAP1
1263SWAP3
1264AND
1265SWAP2
1266SWAP1
1267SWAP2
1268OR
1269SWAP1
126aSSTORE
126bPUSH20x01e4
126ePUSH10x04
1270CALLDATALOAD
1271PUSH20x02c0
1274DUP10
1275MUL
1276ADD
1277SWAP1
1278DUP2
1279ADD
127aCALLDATALOAD
127bPUSH10x0a
127dDUP5
127eADD
127fSSTORE
1280PUSH20x0204
1283DUP2
1284ADD
1285CALLDATALOAD
1286PUSH10x0b
1288DUP5
1289ADD
128aSSTORE
128bPUSH20x1297
128eSWAP1
128fPUSH20x0224
1292ADD
1293PUSH20x6bed
1296JUMP
1297JUMPDEST
1298PUSH10xff
129aAND
129bPUSH10xff
129dNOT
129ePUSH10x0c
12a0DUP5
12a1ADD
12a2SLOAD
12a3AND
12a4OR
12a5PUSH10x0c
12a7DUP4
12a8ADD
12a9SSTORE
12aaPUSH20x02c0
12adDUP8
12aeMUL
12afPUSH10x04
12b1CALLDATALOAD
12b2ADD
12b3PUSH20x0244
12b6ADD
12b7PUSH20x12bf
12baSWAP1
12bbPUSH20x6be0
12beJUMP
12bfJUMPDEST
12c0PUSH10x0c
12c2DUP4
12c3ADD
12c4DUP1
12c5SLOAD
12c6PUSH10xa0
12c8MLOAD
12c9PUSH100xffffffffffffffff0000
12d4PUSH10x10
12d6SWAP2
12d7SWAP1
12d8SWAP2
12d9SHL
12daAND
12dbSWAP3
12dcISZERO
12ddISZERO
12dePUSH10x08
12e0SHL
12e1PUSH20xff00
12e4AND
12e5PUSH100xffffffffffffffffff00
12f0NOT
12f1SWAP1
12f2SWAP2
12f3AND
12f4OR
12f5SWAP2
12f6SWAP1
12f7SWAP2
12f8OR
12f9SWAP1
12faSSTORE
12fbPUSH20x130f
12fePUSH20x0284
1301PUSH10x04
1303CALLDATALOAD
1304PUSH20x02c0
1307DUP11
1308MUL
1309ADD
130aADD
130bPUSH20x71aa
130eJUMP
130fJUMPDEST
1310PUSH10x0c
1312DUP4
1313ADD
1314DUP1
1315SLOAD
1316PUSH80xffffffffffffffff
131fPUSH10x50
1321SHL
1322NOT
1323AND
1324PUSH10x50
1326SWAP3
1327SWAP1
1328SWAP3
1329SHL
132aPUSH80xffffffffffffffff
1333PUSH10x50
1335SHL
1336AND
1337SWAP2
1338SWAP1
1339SWAP2
133aOR
133bSWAP1
133cSSTORE
133dPUSH20x1351
1340PUSH20x02a4
1343PUSH10x04
1345CALLDATALOAD
1346PUSH20x02c0
1349DUP11
134aMUL
134bADD
134cADD
134dPUSH20x6bed
1350JUMP
1351JUMPDEST
1352PUSH10x0c
1354DUP4
1355ADD
1356DUP1
1357SLOAD
1358PUSH10xff
135aPUSH10x90
135cSHL
135dPUSH10x90
135fDUP5
1360SWAP1
1361SHL
1362AND
1363PUSH10xff
1365PUSH10x90
1367SHL
1368NOT
1369DUP3
136aAND
136bOR
136cSWAP1
136dSWAP2
136eSSTORE
136fPUSH20x02c0
1372DUP10
1373MUL
1374PUSH10x04
1376CALLDATALOAD
1377ADD
1378PUSH20x02c4
137bDUP2
137cADD
137dCALLDATALOAD
137ePUSH10x0d
1380DUP7
1381ADD
1382SSTORE
1383SWAP1
1384SWAP3
1385SWAP1
1386PUSH20x1392
1389SWAP1
138aPUSH10x24
138cADD
138dCALLDATALOAD
138ePUSH20x6ad3
1391JUMP
1392JUMPDEST
1393DUP6
1394PUSH10xe0
1396MLOAD
1397SWAP1
1398PUSH20x13a0
139bSWAP2
139cPUSH20x6a8c
139fJUMP
13a0JUMPDEST
13a1MSTORE
13a2DUP4
13a3SLOAD
13a4SWAP4
13a5PUSH10x01
13a7DUP2
13a8ADD
13a9SLOAD
13aaSWAP2
13abSLOAD
13acPUSH10x03
13aeDUP3
13afADD
13b0SLOAD
13b1PUSH10x04
13b3DUP4
13b4ADD
13b5SLOAD
13b6PUSH10xff
13b8AND
13b9PUSH10x05
13bbDUP5
13bcADD
13bdSLOAD
13bePUSH10x06
13c0DUP6
13c1ADD
13c2SLOAD
13c3SWAP1
13c4PUSH10x07
13c6DUP7
13c7ADD
13c8SLOAD
13c9SWAP3
13caPUSH10x08
13ccDUP8
13cdADD
13ceSLOAD
13cfSWAP5
13d0PUSH10x09
13d2DUP9
13d3ADD
13d4SLOAD
13d5PUSH10xff
13d7AND
13d8SWAP7
13d9PUSH10x0a
13dbDUP10
13dcADD
13ddSLOAD
13deSWAP9
13dfPUSH10x0b
13e1ADD
13e2SLOAD
13e3SWAP10
13e4PUSH10x40
13e6MLOAD
13e7DUP1
13e8SWAP15
13e9PUSH10x20
13ebDUP3
13ecADD
13edPUSH0
13eeMLOAD
13efPUSH10x20
13f1PUSH20x82af
13f4PUSH0
13f5CODECOPY
13f6PUSH0
13f7MLOAD
13f8SWAP1
13f9PUSH0
13faMSTORE
13fbSWAP1
13fcMSTORE
13fdPUSH20x0100
1400MLOAD
1401PUSH10x40
1403DUP4
1404ADD
1405MSTORE
1406PUSH10x60
1408DUP3
1409ADD
140aMSTORE
140bPUSH10x80
140dADD
140eMSTORE
140fPUSH10xa0
1411DUP14
1412ADD
1413MSTORE
1414PUSH10xc0
1416DUP13
1417ADD
1418MSTORE
1419PUSH10xe0
141bDUP12
141cADD
141dMSTORE
141ePUSH20x0100
1421DUP11
1422ADD
1423MSTORE
1424PUSH20x0120
1427DUP10
1428ADD
1429MSTORE
142aPUSH10xff
142cDUP2
142dAND
142ePUSH20x0140
1431DUP10
1432ADD
1433MSTORE
1434DUP1
1435PUSH10x08
1437SHR
1438PUSH10x01
143aPUSH10x01
143cPUSH10x40
143eSHL
143fSUB
1440AND
1441PUSH20x0160
1444DUP10
1445ADD
1446MSTORE
1447DUP1
1448PUSH10x48
144aSHR
144bPUSH40xffffffff
1450AND
1451PUSH20x0180
1454DUP10
1455ADD
1456MSTORE
1457DUP1
1458PUSH10x68
145aSHR
145bPUSH20xffff
145eAND
145fPUSH20x01a0
1462DUP10
1463ADD
1464MSTORE
1465PUSH10x78
1467SHR
1468PUSH20xffff
146bAND
146cPUSH20x01c0
146fDUP9
1470ADD
1471MSTORE
1472PUSH20x01e0
1475DUP8
1476ADD
1477MSTORE
1478PUSH20x0200
147bDUP7
147cADD
147dMSTORE
147ePUSH20x0220
1481DUP6
1482ADD
1483MSTORE
1484PUSH20x0240
1487DUP5
1488ADD
1489MSTORE
148aPUSH10xff
148cDUP3
148dAND
148ePUSH20x0260
1491DUP5
1492ADD
1493MSTORE
1494PUSH10xff
1496PUSH10x90
1498SHL
1499DUP2
149aPUSH10x90
149cSHL
149dAND
149ePUSH10xff
14a0PUSH10x90
14a2SHL
14a3NOT
14a4DUP4
14a5AND
14a6OR
14a7PUSH10x08
14a9SHR
14aaPUSH10xff
14acAND
14adISZERO
14aeISZERO
14afPUSH20x0280
14b2DUP5
14b3ADD
14b4MSTORE
14b5PUSH10xff
14b7PUSH10x90
14b9SHL
14baDUP2
14bbPUSH10x90
14bdSHL
14beAND
14bfPUSH10xff
14c1PUSH10x90
14c3SHL
14c4NOT
14c5DUP4
14c6AND
14c7OR
14c8PUSH10x10
14caSHR
14cbPUSH10x01
14cdPUSH10x01
14cfPUSH10x40
14d1SHL
14d2SUB
14d3AND
14d4PUSH20x02a0
14d7DUP5
14d8ADD
14d9MSTORE
14daPUSH10xff
14dcPUSH10x90
14deSHL
14dfDUP2
14e0PUSH10x90
14e2SHL
14e3AND
14e4PUSH10xff
14e6PUSH10x90
14e8SHL
14e9NOT
14eaDUP4
14ebAND
14ecOR
14edPUSH10x50
14efSHR
14f0PUSH10x01
14f2PUSH10x01
14f4PUSH10x40
14f6SHL
14f7SUB
14f8AND
14f9PUSH20x02c0
14fcDUP5
14fdADD
14feMSTORE
14ffPUSH10xff
1501PUSH10x90
1503SHL
1504SWAP1
1505PUSH10x90
1507SHL
1508AND
1509SWAP1
150aPUSH10xff
150cPUSH10x90
150eSHL
150fNOT
1510AND
1511OR
1512PUSH10x90
1514SHR
1515PUSH10xff
1517AND
1518PUSH20x02e0
151bDUP3
151cADD
151dMSTORE
151ePUSH20x02c0
1521DUP7
1522MUL
1523PUSH10x04
1525CALLDATALOAD
1526ADD
1527PUSH20x02c4
152aADD
152bCALLDATALOAD
152cPUSH20x0300
152fDUP3
1530ADD
1531MSTORE
1532PUSH20x0300
1535DUP2
1536MSTORE
1537PUSH20x1542
153aPUSH20x0320
153dDUP3
153ePUSH20x67d9
1541JUMP
1542JUMPDEST
1543DUP1
1544MLOAD
1545SWAP1
1546PUSH10x20
1548ADD
1549KECCAK256
154aPUSH20x1553
154dDUP3
154eDUP7
154fPUSH20x6a8c
1552JUMP
1553JUMPDEST
1554MSTORE
1555PUSH20x155d
1558SWAP1
1559PUSH20x6bfb
155cJUMP
155dJUMPDEST
155eSWAP4
155fPUSH20x1573
1562PUSH20x0244
1565PUSH10x04
1567CALLDATALOAD
1568PUSH20x02c0
156bDUP5
156cMUL
156dADD
156eADD
156fPUSH20x6be0
1572JUMP
1573JUMPDEST
1574PUSH10x40
1576DUP1
1577MLOAD
1578PUSH10xa0
157aMLOAD
157bSWAP3
157cISZERO
157dISZERO
157eDUP2
157fMSTORE
1580PUSH10x01
1582PUSH10x01
1584PUSH10x40
1586SHL
1587SUB
1588SWAP1
1589SWAP3
158aAND
158bPUSH10x20
158dDUP4
158eADD
158fMSTORE
1590PUSH20x02c0
1593DUP4
1594MUL
1595PUSH10x04
1597CALLDATALOAD
1598ADD
1599PUSH10x24
159bADD
159cCALLDATALOAD
159dSWAP2
159ePUSH320x523b72ac5fbe8982a57ea63c8ca4daf1602c22eda42d6cc14a884c0310698b80
15bfSWAP2
15c0SWAP1
15c1LOG2
15c2PUSH10x01
15c4ADD
15c5SWAP4
15c6PUSH20x0fb2
15c9JUMP
15caJUMPDEST
15cbPUSH10x24
15cdPUSH20x02c0
15d0DUP7
15d1MUL
15d2PUSH10x04
15d4CALLDATALOAD
15d5ADD
15d6ADD
15d7CALLDATALOAD
15d8PUSH20x0100
15dbMLOAD
15dcMSTORE
15ddPUSH10x06
15dfPUSH10x20
15e1MSTORE
15e2PUSH10x40
15e4PUSH20x0100
15e7MLOAD
15e8KECCAK256
15e9PUSH10x01
15ebPUSH10xff
15edNOT
15eeDUP3
15efSLOAD
15f0AND
15f1OR
15f2SWAP1
15f3SSTORE
15f4PUSH10x04
15f6SLOAD
15f7PUSH10x01
15f9PUSH10x40
15fbSHL
15fcDUP2
15fdLT
15feISZERO
15ffPUSH20x1643
1602JUMPI
1603PUSH20x1617
1606DUP2
1607PUSH10x01
1609PUSH20x163c
160cSWAP4
160dADD
160ePUSH10x04
1610SSTORE
1611PUSH10x04
1613PUSH20x6a34
1616JUMP
1617JUMPDEST
1618PUSH10x24
161aPUSH20x02c0
161dDUP10
161eSWAP5
161fSWAP4
1620SWAP5
1621MUL
1622PUSH10x04
1624CALLDATALOAD
1625ADD
1626ADD
1627CALLDATALOAD
1628SWAP1
1629DUP4
162aSLOAD
162bSWAP1
162cPUSH10x03
162eSHL
162fSWAP2
1630DUP3
1631SHL
1632SWAP2
1633PUSH0
1634NOT
1635SWAP1
1636SHL
1637NOT
1638AND
1639OR
163aSWAP1
163bJUMP
163cJUMPDEST
163dSWAP1
163eSSTORE
163fPUSH20x10be
1642JUMP
1643JUMPDEST
1644PUSH40x4e487b71
1649PUSH10xe0
164bSHL
164cPUSH20x0100
164fMLOAD
1650MSTORE
1651PUSH10x41
1653PUSH10x04
1655MSTORE
1656PUSH10x24
1658PUSH20x0100
165bMLOAD
165cREVERT
165dJUMPDEST
165ePUSH20x01e4
1661PUSH20x02c0
1664DUP7
1665PUSH40x3f4b99b1
166aPUSH10xe2
166cSHL
166dPUSH20x0100
1670MLOAD
1671MSTORE
1672MUL
1673PUSH10x04
1675CALLDATALOAD
1676ADD
1677ADD
1678CALLDATALOAD
1679PUSH10x04
167bMSTORE
167cPUSH10x24
167ePUSH20x0100
1681MLOAD
1682REVERT
1683JUMPDEST
1684POP
1685PUSH20x01e4
1688PUSH20x02c0
168bDUP7
168cMUL
168dPUSH10x04
168fCALLDATALOAD
1690ADD
1691ADD
1692CALLDATALOAD
1693PUSH20x0100
1696MLOAD
1697MSTORE
1698PUSH10x06
169aPUSH10x20
169cMSTORE
169dPUSH10xff
169fPUSH10x40
16a1PUSH20x0100
16a4MLOAD
16a5KECCAK256
16a6SLOAD
16a7AND
16a8ISZERO
16a9PUSH20x1092
16acJUMP
16adJUMPDEST
16aePUSH10x40
16b0DUP1
16b1MLOAD
16b2PUSH20x16ef
16b5SWAP2
16b6PUSH20x16bf
16b9SWAP1
16baDUP3
16bbPUSH20x67d9
16beJUMP
16bfJUMPDEST
16c0PUSH10x09
16c2DUP2
16c3MSTORE
16c4PUSH90x3830bcb6b0b9ba32b9
16cePUSH10xb9
16d0SHL
16d1PUSH10x20
16d3DUP3
16d4ADD
16d5MSTORE
16d6PUSH10x04
16d8CALLDATALOAD
16d9PUSH20x02c0
16dcDUP9
16ddMUL
16deADD
16dfPUSH20x02c4
16e2DUP2
16e3ADD
16e4CALLDATALOAD
16e5SWAP2
16e6SWAP1
16e7PUSH10x24
16e9ADD
16eaCALLDATALOAD
16ebPUSH20x7a55
16eeJUMP
16efJUMPDEST
16f0PUSH20x107c
16f3JUMP
16f4JUMPDEST
16f5PUSH10xff
16f7DUP7
16f8PUSH10x24
16faPUSH20x02c0
16fdPUSH20x170f
1700PUSH20x0224
1703DUP3
1704DUP6
1705MUL
1706PUSH10x04
1708CALLDATALOAD
1709ADD
170aADD
170bPUSH20x6bed
170eJUMP
170fJUMPDEST
1710SWAP3
1711PUSH40x1e9a7d75
1716PUSH10xe0
1718SHL
1719PUSH20x0100
171cMLOAD
171dMSTORE
171eMUL
171fPUSH10x04
1721CALLDATALOAD
1722ADD
1723ADD
1724CALLDATALOAD
1725PUSH10x04
1727MSTORE
1728AND
1729PUSH10x24
172bMSTORE
172cPUSH10x44
172ePUSH20x0100
1731MLOAD
1732REVERT
1733JUMPDEST
1734POP
1735PUSH10x01
1737PUSH10xff
1739PUSH20x174d
173cPUSH20x0224
173fPUSH20x02c0
1742DUP11
1743MUL
1744PUSH10x04
1746CALLDATALOAD
1747ADD
1748ADD
1749PUSH20x6bed
174cJUMP
174dJUMPDEST
174eAND
174fEQ
1750ISZERO
1751PUSH20x1073
1754JUMP
1755JUMPDEST
1756PUSH10x24
1758PUSH20x02c0
175bDUP7
175cPUSH40x2eb72ead
1761PUSH10xe1
1763SHL
1764PUSH20x0100
1767MLOAD
1768MSTORE
1769MUL
176aPUSH10x04
176cCALLDATALOAD
176dADD
176eADD
176fCALLDATALOAD
1770PUSH10x04
1772MSTORE
1773PUSH10x24
1775PUSH20x0100
1778MLOAD
1779REVERT
177aJUMPDEST
177bPOP
177cPUSH10x84
177ePUSH20x02c0
1781DUP7
1782MUL
1783PUSH10x04
1785CALLDATALOAD
1786ADD
1787ADD
1788CALLDATALOAD
1789ISZERO
178aPUSH20x104c
178dJUMP
178eJUMPDEST
178fPOP
1790PUSH10xff
1792PUSH20x17a6
1795PUSH20x02a4
1798PUSH20x02c0
179bDUP9
179cMUL
179dPUSH10x04
179fCALLDATALOAD
17a0ADD
17a1ADD
17a2PUSH20x6bed
17a5JUMP
17a6JUMPDEST
17a7AND
17a8ISZERO
17a9PUSH20x1046
17acJUMP
17adJUMPDEST
17aePUSH10xff
17b0DUP6
17b1PUSH10x24
17b3PUSH20x02c0
17b6PUSH20x17c8
17b9PUSH20x02a4
17bcDUP3
17bdDUP6
17beMUL
17bfPUSH10x04
17c1CALLDATALOAD
17c2ADD
17c3ADD
17c4PUSH20x6bed
17c7JUMP
17c8JUMPDEST
17c9SWAP3
17caPUSH40x1a7ce1dd
17cfPUSH10xe0
17d1SHL
17d2PUSH20x0100
17d5MLOAD
17d6MSTORE
17d7MUL
17d8PUSH10x04
17daCALLDATALOAD
17dbADD
17dcADD
17ddCALLDATALOAD
17dePUSH10x04
17e0MSTORE
17e1AND
17e2PUSH10x24
17e4MSTORE
17e5PUSH10x44
17e7PUSH20x0100
17eaMLOAD
17ebREVERT
17ecJUMPDEST
17edPUSH10x24
17efPUSH20x02c0
17f2DUP7
17f3PUSH40x3dc2fc11
17f8PUSH10xe0
17faSHL
17fbPUSH20x0100
17feMLOAD
17ffMSTORE
1800MUL
1801PUSH10x04
1803CALLDATALOAD
1804ADD
1805ADD
1806CALLDATALOAD
1807PUSH10x04
1809MSTORE
180aPUSH10x24
180cPUSH20x0100
180fMLOAD
1810REVERT
1811JUMPDEST
1812POP
1813PUSH40xffffffff
1818PUSH20x182c
181bPUSH20x0144
181ePUSH20x02c0
1821DUP9
1822MUL
1823PUSH10x04
1825CALLDATALOAD
1826ADD
1827ADD
1828PUSH20x6c18
182bJUMP
182cJUMPDEST
182dAND
182eISZERO
182fDUP1
1830ISZERO
1831PUSH20x187f
1834JUMPI
1835JUMPDEST
1836DUP1
1837ISZERO
1838PUSH20x1860
183bJUMPI
183cJUMPDEST
183dDUP1
183ePUSH20x1008
1841JUMPI
1842POP
1843PUSH10xff
1845PUSH20x1859
1848PUSH20x0224
184bPUSH20x02c0
184eDUP9
184fMUL
1850PUSH10x04
1852CALLDATALOAD
1853ADD
1854ADD
1855PUSH20x6bed
1858JUMP
1859JUMPDEST
185aAND
185bISZERO
185cPUSH20x1008
185fJUMP
1860JUMPDEST
1861POP
1862PUSH10xff
1864PUSH20x1878
1867PUSH20x01c4
186aPUSH20x02c0
186dDUP9
186eMUL
186fPUSH10x04
1871CALLDATALOAD
1872ADD
1873ADD
1874PUSH20x6bed
1877JUMP
1878JUMPDEST
1879AND
187aISZERO
187bPUSH20x183c
187eJUMP
187fJUMPDEST
1880POP
1881PUSH10xff
1883PUSH20x1897
1886PUSH20x0104
1889PUSH20x02c0
188cDUP9
188dMUL
188ePUSH10x04
1890CALLDATALOAD
1891ADD
1892ADD
1893PUSH20x6bed
1896JUMP
1897JUMPDEST
1898AND
1899ISZERO
189aPUSH20x1835
189dJUMP
189eJUMPDEST
189fDUP5
18a0PUSH10x24
18a2PUSH20x02c0
18a5PUSH20x18bd
18a8PUSH10x04
18aaCALLDATALOAD
18abDUP3
18acDUP6
18adMUL
18aeADD
18afPUSH10x64
18b1DUP2
18b2ADD
18b3CALLDATALOAD
18b4SWAP1
18b5PUSH10x44
18b7ADD
18b8CALLDATALOAD
18b9PUSH20x72b3
18bcJUMP
18bdJUMPDEST
18beSWAP3
18bfPUSH40x1a35ac99
18c4PUSH10xe0
18c6SHL
18c7PUSH20x0100
18caMLOAD
18cbMSTORE
18ccMUL
18cdPUSH10x04
18cfCALLDATALOAD
18d0ADD
18d1ADD
18d2CALLDATALOAD
18d3PUSH10x04
18d5MSTORE
18d6PUSH10x24
18d8MSTORE
18d9PUSH10x44
18dbPUSH20x0100
18deMLOAD
18dfREVERT
18e0JUMPDEST
18e1SWAP3
18e2SWAP1
18e3DUP6
18e4PUSH20x0100
18e7MLOAD
18e8JUMPDEST
18e9PUSH10xc0
18ebMLOAD
18ecDUP2
18edLT
18eeISZERO
18efPUSH20x1ec5
18f2JUMPI
18f3PUSH10x05
18f5DUP2
18f6SWAP1
18f7SHL
18f8DUP4
18f9ADD