Final Chainexplorer Φ₀ · Final Chain · 20678
cs

Kontrakt

0xf5c6689d42285c9cd157e784c00d601bc7d526de

Adresa
0xf5c6689d42285c9cd157e784c00d601bc7d526de
Druh
ověřený kontrakt FinalAssetRegistry
Zůstatek
0 vETH
Nonce
1
Kód
27,628 bajtů codehash 0xcbdb3c160d07a2f7bb292d2814a16eefc5040c68e4171375bfc7e005addaf16d

strom účtů

Strom
1 · účty
Přítomen
žádný list
Klíč
0x6cfb63f6f5f00add530ad8c85167e2b55e6ae6f97652e7c2468cf59c6bffe722
Živý kořen
0xe7eb646dfe4cfe9c975fd970c7c565c8d96e5c1edae4440e5f6a2560325fb328
Tato adresa nemá ve stromu účtů list. Každý Final Wallet — včetně servisních identit — ho má, takže chybějící list znamená běžný účet, ne peněženku.
transakceudálostipřevody tokenůkontrakt

zdroj ověřeno

Kontrakt
FinalAssetRegistry přesná shoda · immutables maskovány
Kompilátor
v0.8.33+commit.64118f21
Optimalizátor
zapnut · 200 průchodů
Verze EVM
prague
Ověřeno
2026-09-07T05:09:33.677Z
Původ
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
//
// @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";

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

    bytes32 internal constant ACTION_SEED = keccak256("FinalAssetRegistry.seed.v01");
    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");
    bytes32 internal constant ACTION_SET_CHAIN_TERMS = keccak256("FinalAssetRegistry.setChainTerms.v01");
    /// @dev Kinds 4 and 5, same arrangement: their own door, the shared nonce.
    bytes32 internal constant ACTION_SET_SOURCES = keccak256("FinalAssetRegistry.setSources.v01");

    uint8 internal constant TREE_SETTLEMENT = 5;
    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;
    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");
    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;
    uint8 internal constant USE_MORPH = 2;
    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;
    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;
    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;
    uint8 public constant FINALITY_CONFIRMATIONS = 2;
    uint8 public constant FINALITY_L2_SETTLED = 3;

    /// @notice How a chain prices gas. Zero is unset.
    uint8 public constant GAS_MODEL_EIP1559 = 1;
    uint8 public constant GAS_MODEL_LEGACY = 2;
    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;
    uint8 public constant VM_SVM = 2;
    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;
    uint8 public constant PROTOCOL_UNISWAP_V3 = 2;
    uint8 public constant PROTOCOL_UNISWAP_V4 = 3;
    uint8 public constant PROTOCOL_VELODROME = 4;
    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;
    uint32 public constant PRICE_MAX_AGE_DEFAULT_MS = 120_000;
    uint32 public constant PRICE_CADENCE_FAST_MS = 1_000;
    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;
        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;
        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 (ruled
        ///      2026-09-03, "fill with blanks before even trying to resync").
        ///      0 = unknown, scan from the cursor as before.
        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 (ruled 2026-09-04: the test chains the
        ///      oracles cover without a wallet deployment).
        uint8 role;
    }

    /// @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 {
        bytes32 originChainRef;
        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;
        uint32 maxAgeMs;
        bool enabled;
        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 {
        bytes32 assetId;
        bytes32 chainRef;
        /// @dev `USE_FEE` and/or `USE_REFILL`. Other bits are refused.
        uint8 uses;
        bool enabled;
        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` (ruled 2026-09-04: "leverage can be set
        ///      per asset per chain, also in asset lists on FC").
        uint16 maxLeveragePct;
    }

    /// @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 {
        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;
        bytes32 quoteToken;
        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;
        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;
        bool enabled;
        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 {
        bytes32 chainRef;
        bytes32 protocolId;
        /// @dev `PROTOCOL_*`.
        uint8 protocolKind;
        bytes32 factory;
        bytes32 quoter;
        bytes32 router;
        bytes32 positionManager;
        bool enabled;
        uint64 epoch;
    }

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

    FinalIdentityRegistry public immutable registry;
    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;
    uint256 public threshold;

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

    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 {
        bytes32 paramId;
        uint256 value;
        bool enabled;
        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
    ///         (ruled 2026-09-04: "default lane plus other available variants on
    ///         a given chain should be there in the tree, changeable there per
    ///         chain"). A zero numeric field means "the fee schedule's default".
    struct ChainTermsEntry {
        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 table (arch/intent-plane.md): 0 = the relayer-
        ///      staged PQ 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;
        bool enabled;
        uint64 epoch;
    }

    mapping(bytes32 chainRef => ChainEntry) internal _chains;
    mapping(bytes32 chainRef => bool) internal _chainKnown;

    bytes32[] internal _assetIds;
    mapping(bytes32 assetId => AssetEntry) internal _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;
    mapping(bytes32 paramId => bool) internal _policyKnown;
    bytes32[] internal _policyIds;
    mapping(bytes32 chainRef => ChainTermsEntry) internal _chainTerms;

    /// @dev Kind 4 — price sources, keyed by their tree-6 key so one mapping
    ///      serves rows on a chain and CEX rows alike; `_sourceKeys` is the
    ///      iteration order and `_sourceKeysFor[assetId]` the per-asset view.
    mapping(bytes32 key => PriceSourceEntry) internal _sources;
    mapping(bytes32 key => bool) internal _sourceKnown;
    bytes32[] internal _sourceKeys;
    mapping(bytes32 assetId => bytes32[]) internal _sourceKeysFor;

    /// @dev Kind 5 — DEX protocol deployments, `chainRef -> protocolId -> entry`.
    mapping(bytes32 chainRef => mapping(bytes32 protocolId => DexProtocolEntry)) internal _protocols;
    mapping(bytes32 chainRef => bytes32[]) internal _protocolIdsOn;

    /// @notice The roots `FinalSettlement` consumes. Recomputed on every write.
    bytes32 public chainRegistryRoot;
    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

    event RegistryConfigured(uint256 role, uint256 threshold);
    event ChainSet(bytes32 indexed chainRef, bool enabled, uint64 epoch);
    event AssetSet(bytes32 indexed assetId, uint8 uses, bool enabled, uint64 epoch);
    event AssetChainSet(
        bytes32 indexed assetId, bytes32 indexed chainRef, uint8 uses, bool enabled, uint64 epoch
    );
    event PolicySet(bytes32 indexed paramId, uint256 value, bool enabled, uint64 epoch);
    event ChainTermsSet(
        bytes32 indexed chainRef, uint8 defaultLane, uint16 lanes, uint32 quoteWindowSeconds, uint16 floatPremiumBps, uint64 epoch
    );
    event PriceSourceSet(
        bytes32 indexed assetId, bytes32 indexed chainRef, bytes32 indexed venueId, bool enabled, uint64 epoch
    );
    event DexProtocolSet(bytes32 indexed chainRef, bytes32 indexed protocolId, bool enabled, uint64 epoch);
    event RootsPublished(bytes32 chainRoot, bytes32 assetRoot, uint64 epoch);
    event Sealed();

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

    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);
    error AlreadySealed();
    error ThresholdUnreachable(uint256 live, uint256 asked);
    error NotConfigured();
    error UnknownChain(bytes32 chainRef);
    error UnknownAsset(bytes32 assetId);
    error EmptyBatch();
    error ChainNotRegistered(bytes32 chainRef);
    error ChainRefMismatch(bytes32 claimed, bytes32 derived);
    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);
    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);

    // ----------------------------------------------------------- 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. NO-WIPE redeploy, ruled
     *         2026-09-03. Bootstrap admin only, only while 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.
     */
    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);
            }
            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 a seventeen-field struct.
            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;
            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);
            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);
    }

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

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

        uint64 e = registryEpoch + 1;
        registryEpoch = e;

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

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

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

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

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

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

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

        uint64 e = registryEpoch + 1;
        registryEpoch = e;

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

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

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

        uint64 e = registryEpoch + 1;
        registryEpoch = e;

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

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

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

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

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

    function allowlistKeyForChain(bytes32 chainRef) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(0), chainRef));
    }

    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;
    }

    function allowlistKeyForAssetOnChain(bytes32 assetId, bytes32 chainRef)
        public
        pure
        returns (bytes32)
    {
        return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(2), assetId, chainRef));
    }

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

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

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

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

    function registryRootKey(uint8 which) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_REGISTRY_ROOT_KEY, which));
    }

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

    /// @dev Kind 0, v2: the complete chain description. Pinned against the
    ///      backend's `chainLeafHash` by test.
    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
            )
        );
    }

    /// @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, v2: policy only — the venue is a kind-4 row.
    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
            )
        );
    }

    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)
        );
    }

    function _allowlistLeafForChainTerms(ChainTermsEntry memory t) private pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ALLOWLIST_LEAF, uint8(7), t.chainRef, t.defaultLane, t.lanes, t.quoteWindowSeconds,
                t.floatPremiumBps, t.admissionFloorUsdMicros, t.maxLegsPerIntent, t.enabled, t.epoch
            )
        );
    }


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

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

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

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

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

    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);
    }

    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];
    }

    function policyCount() external view returns (uint256) {
        return _policyIds.length;
    }

    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];
    }

    function chainCount() external view returns (uint256) {
        return _chainRefs.length;
    }

    function assetCount() external view returns (uint256) {
        return _assetIds.length;
    }

    function chainAt(uint256 i) external view returns (ChainEntry memory) {
        return _chains[_chainRefs[i]];
    }

    function assetAt(uint256 i) external view returns (AssetEntry memory) {
        return _assets[_assetIds[i]];
    }

    function chainOf(bytes32 chainRef) external view returns (ChainEntry memory) {
        if (!_chainKnown[chainRef]) revert UnknownChain(chainRef);
        return _chains[chainRef];
    }

    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: a seventeen-field row array 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];
    }
}

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
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

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

/**
 * @title FinalCertificate
 * @notice Reads a Final Certificate (`.fcert`, schema v3) on chain.
 *
 * @dev Final Chain only — it needs the SHA3-256 precompile, because the schema
 * hashes with FIPS-202 SHA3 and the EVM has `keccak256`, which is a different
 * function.
 *
 * ## Why the chain parses this at all
 *
 * `FinalIdentityRegistry.registerWithCertificate` used to take the TBS bytes
 * AND the public keys as separate arguments. It derived `certHash` from the
 * bytes, which sounds like verification and is not: nothing compared the keys
 * to the certificate, so a registrar could bind any certificate to any keypair.
 * The registry would then 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 way for
 * two arguments to disagree.
 *
 * ## The SubjectKeyId check
 *
 * The schema defines `SubjectKeyId` as SHA3-256 of the `PublicKeyBlock`. Having
 * parsed the block, this recomputes that digest and compares. The field is
 * inside the TBS, so it is covered by the CA's signatures — which makes the
 * check a statement about what the CA attested, not merely about internal
 * consistency of bytes the caller supplied.
 *
 * ## What this does NOT do
 *
 * It does not verify the CA's signatures over the TBS, and it does not walk the
 * chain to the root. Both are possible here — the precompiles verify ML-DSA-87
 * and SLH-DSA-SHAKE-256s — and both are deliberately out of scope for the
 * registry's bootstrap path, where the registrar is the party that issued the
 * certificate in the first place. `verifyIssuerSignatures` below is provided for
 * callers that need it, and the identity registry uses it once a CA is itself
 * registered.
 */
library FinalCertificate {
    /// `"PQCF"`.
    uint32 internal constant MAGIC = 0x50514346;
    /// The current wire generation — v5's `Version = 2` (chain-attested
    /// issuance; ruled 2026-09-01). The v4 wire (`Version = 1`) stays
    /// PARSEABLE so pre-cutover artifacts still read; encoders write 2.
    /// fails to parse rather than being reinterpreted: `pqKeysHash` and every
    /// wallet address derive from this exact layout.
    uint32 internal constant VERSION = 2;
    /// The v4 generation, accepted on parse for pre-cutover artifacts.
    uint32 internal constant VERSION_V4 = 1;

    /// @notice The 0x0102 Institution identity extension (issuer profile).
    uint16 internal constant EXT_INSTITUTION = 0x0102;

    /// Algorithm ids ARE the FIPS numbers, in one space for signatures and KEMs
    /// — the same ids the quorum wire format and the backend registry use, and
    /// the numbers the precompile addresses end in.
    /// ML-KEM-1024 (FIPS 203), the lattice half of the encapsulation pair.
    uint16 internal constant ALG_ML_KEM_1024 = 0x0003;
    /// ML-DSA-87 (FIPS 204). Transaction class.
    uint16 internal constant ALG_ML_DSA_87 = 0x0004;
    /// SLH-DSA-SHAKE-256s (FIPS 205). Access class, and the seal.
    uint16 internal constant ALG_SLH_DSA_SHAKE_256S = 0x0005;
    /// FN-DSA (FIPS 206). Reserved: no implementation, never accepted.
    uint16 internal constant ALG_FN_DSA = 0x0006;
    /// HQC-5 (FIPS 207), the code-based half of the encapsulation pair.
    uint16 internal constant ALG_HQC_5 = 0x0007;

    /// Certificate signing. Says which key to verify WITH; it grants nothing —
    /// that comes from `Depth` and `MaxDelegationDepth`.
    uint16 internal constant PURPOSE_CERT_SIGNING = 0x0004;

    /// The wallet's four slots, in two stages of two.
    ///
    /// A certificate carries ONE stage, never all four. The stage is what gets
    /// 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.
    ///
    /// 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;
    uint16 internal constant PURPOSE_ACTIVE_ACCESS = 0x0011;
    uint16 internal constant PURPOSE_RECOVERY_TX = 0x0012;
    uint16 internal constant PURPOSE_RECOVERY_ACCESS = 0x0013;
    /// @dev v4's encapsulation purposes. Parsed, and each stage's pair is
    ///      resolved alongside its signing pair — `FinalIdentityRegistry` then
    ///      stores them so a sender can encapsulate to a registered party
    ///      without a second lookup somewhere less authoritative.
    ///
    ///      They were declared and skipped for one release, which is how the
    ///      registry's four encapsulation-key mappings ended up read in three
    ///      places and written in none: `kemCommitments` hashed the empty
    ///      string for every account and `kemKeysOf` returned nothing.
    uint16 internal constant PURPOSE_ACTIVE_KEM = 0x0014;
    uint16 internal constant PURPOSE_RECOVERY_KEM = 0x0015;
    /// @dev The seal: a second SLH-DSA-SHAKE-256s key, distinct from the access
    ///      key, that co-signs execution-class quorum decisions. Carried by
    ///      SERVICE certificates only — a user's wallet never seals — and
    ///      optional in the schema, so a certificate without it parses
    ///      unchanged. Outside `keysHash`: a seal is operational, rotated by
    ///      issuing a new live certificate, and it must not move a wallet
    ///      address it plays no part in.
    uint16 internal constant PURPOSE_ACTIVE_SEAL = 0x0016;

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

    /// Nanoseconds per second. The schema's validity fields are nanoseconds and
    /// `block.timestamp` is seconds; a comparison across the two units is a bug
    /// waiting for the first certificate anybody actually checks.
    /// @dev The schema stamps validity in NANOseconds and this chain's clock is
    ///      MILLIseconds, so a certificate converts down by 1e6 rather than by
    ///      1e9. It was 1e9 — seconds — which made every `notBefore` look 1000x
    ///      too small against `block.timestamp` and every certificate
    ///      permanently "already valid", including one issued for the future.
    uint64 internal constant NS_PER_MILLISECOND = FinalChainTime.NS_PER_MILLISECOND;

    /// @notice What the chain keeps out of one certificate.
    struct Parsed {
        bytes32 certHash;
        bytes32 serial;
        /// keccak256 of the IssuerDN bytes, for the chain-issuer pin: a
        /// chain-attested certificate carries the ruled constant DN and the
        /// registry compares hashes rather than strings.
        bytes32 issuerDnHash;
        /// The SubjectDN bytes verbatim — the jurisdiction rule reads its
        /// `C=` component at issuer registration.
        bytes subjectDn;
        /// The 0x0102 Institution extension VALUE, when present; empty
        /// otherwise. Issuer registration parses jurisdiction out of it.
        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;
        uint8 depth;
        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;
    }

    error BadMagic(uint32 got);
    error BadVersion(uint32 got);
    error Truncated(uint256 needed, uint256 got);
    error SubjectKeyIdMismatch(bytes32 derived, bytes32 declared);
    error MissingSlot(uint16 purpose);
    error WrongAlgorithmForSlot(uint16 purpose, uint16 algorithm);
    error DuplicateKey(uint16 purpose, uint16 algorithm);
    error KeysNotSorted();
    error BadKeyLength(uint16 algorithm, uint256 length);
    error InvalidDepth(uint8 depth, uint8 maxDelegationDepth);
    error ValidityInverted(uint64 notBefore, uint64 notAfter);

    /**
     * @notice Parse and self-check a `TBSCertificate`.
     * @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.
     *
     * @dev Checking for a CAPABILITY rather than a type is the 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.
     */
    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 generations. v4 artifacts predate chain-attested issuance
        // and still parse — supersession is handled at admission (PoP and the
        // chain-issuer pins), not by refusing to read history.
        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: `activeTransaction` + `activeAccess`.
    /// @dev `external`, like the other three entry points below: the registry
    /// sits against the EIP-170 ceiling and the TBS parser is its single
    /// largest inlined dependency, so the four doors it actually calls are
    /// DEPLOY-LINKED — the library is one more contract in the plane's fixed
    /// nonce-0 deploy order (doctrine §2 of `arch/final-chain-regenesis.md`),
    /// its address baked immutably into the registry's bytecode. A linked
    /// library is code, not a key: nothing can repoint it after deployment.
    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, an ordinary action for that
    /// account, uses the live access key. Keeping the two stages in separate
    /// certificates is what makes that boundary something a verifier can see.
    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 CA certificate, whose two keys are both cert-signing.
    /// @dev No encapsulation purpose: a CA signs and is never sealed to, so
    /// `PURPOSE_ACTIVE_KEM` is passed as a value the loop can never match. A
    /// CA certificate carrying encapsulation keys would parse them into slots
    /// `_write` then discards, which is a shape worth refusing to have.
    function parseCa(bytes calldata tbs) external view returns (Parsed memory) {
        return parse(tbs, PURPOSE_CERT_SIGNING, PURPOSE_CERT_SIGNING, NO_KEM_PURPOSE);
    }

    /**
     * @notice Verify a CA's dual signature over `tbs`.
     * @dev Both must verify, not either. Two signatures under two different
     * hardness assumptions is the entire reason the schema carries two, and
     * accepting one would collapse that to whichever family breaks first.
     */
    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);
    }

    function _need(bytes calldata tbs, uint256 upto) private pure {
        if (tbs.length < upto) revert Truncated(upto, tbs.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);
    }

    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
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

/**
 * @title FinalChainPrecompiles
 * @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 `final-reth`, the node binary in `FinalBackend/vendor/reth/final/`, 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
pragma solidity ^0.8.20;

/**
 * @title FinalChainTime
 * @notice **On Final Chain, `block.timestamp` is MILLISECONDS.**
 *
 * @dev Every other EVM chain stamps seconds. This one cannot: it mints a block
 * every 100 ms and Ethereum requires block timestamps to strictly increase, so
 * a second-denominated clock would run out of distinct values ten times over
 * per second. Milliseconds is the deliberate choice, and it is a property of
 * the CHAIN — `final-reth` — not of any contract here.
 *
 * Every duration on this chain is therefore in milliseconds, and this library
 * exists so that is stated in one place instead of assumed in fifteen.
 *
 * ## How this was found, which is the reason for the naming rules below
 *
 * It was not found by the test suite. Foundry's `block.timestamp` is seconds,
 * so all 1249 tests agreed with the contracts and every one of them was wrong
 * about the chain they deploy to. It was found the first time anything
 * exercised a deadline against the real chain — a posted intent, which reverted
 * `DeadlinePassed` against a header whose deadline had been computed from wall
 * time.
 *
 * What was actually broken was worse than a posting. `rotationInitiatedAt` is
 * written from `block.timestamp` and compared against `rotationInitiatedAt +
 * delaySeconds`: a millisecond clock plus a second-denominated delay. The
 * 24-hour default recovery delay elapsed in **86 seconds**, and the two-year
 * dormancy threshold in about seventeen hours. That delay is the thing standing
 * between a stolen recovery key and an account.
 *
 * Nothing had noticed because nothing time-dependent had ever run: `walletCount`
 * is 0, `FinalBundleLog.size` is 0, and no intent had been posted.
 *
 * ## The naming rule
 *
 * A field or constant carrying a duration or an instant on this chain ends in
 * `Ms`. Not decoration — the bug was a field named `delaySeconds` that held
 * milliseconds, and a name that lies is how the next reader reintroduces it.
 * `SECONDS` names are gone from `contracts/finalchain/` and must not come back.
 *
 * Solidity's `hours` / `days` suffixes are still the clearest way to write a
 * duration, so they are written as `24 hours * MS_PER_SECOND` rather than as a
 * literal: the intent stays readable and the unit stays explicit.
 */
library FinalChainTime {
    /// @notice Milliseconds per second. The whole conversion, named once.
    uint64 internal constant MS_PER_SECOND = 1_000;

    /// @notice Milliseconds per nanosecond divisor — the certificate schema
    /// stamps validity in NANOseconds, so a certificate converts down to this
    /// chain's clock rather than up.
    uint64 internal constant NS_PER_MILLISECOND = 1_000_000;

    /// @notice This chain's clock, stated as a function so a caller reads the
    /// unit rather than remembering it.
    /// @dev No arithmetic. It exists to make `FinalChainTime.nowMs()` the thing
    /// people write, which is self-describing where `block.timestamp` is not.
    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
//
// @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";

/**
 * @title FinalIdentityRegistry
 * @notice Who every party in the system IS, on chain, with its certificate.
 *
 * @dev Final Chain only. Every service, every co-signer, every certificate
 * authority and every operator has one record here, and that record carries the
 * party's actual public keys — not commitments to them.
 *
 * ## Why the full key and not a hash
 *
 * 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: 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` reads them from storage, and
 * "which key is co-signer 3" has exactly one answer. That question previously
 * had three: an environment variable, an on-chain roster, and a Secret Manager
 * entry, with nothing comparing them. Every configuration failure in this
 * program has been those three disagreeing.
 *
 * ## The certificate is the record, not a pointer to one
 *
 * `certHash` is `SHA3-256(TBSCertificate)` — the certificate's own identity per
 * the v3 schema, and the handle revocation is keyed on. The schema says
 * revocation exists "on Final Chain only"; this is that place.
 *
 `registerWithCertificate` takes the TBS bytes and **reads everything out of
 * them**: the digest, the serial, the key identifiers, the depth pair, the
 * validity window and both public keys. It takes no key arguments at all.
 *
 * That is a correction, and the version it replaces is worth naming because it
 * looked right. It took the TBS *and* the keys, derived `certHash` from the
 * TBS, and never compared the two — so a registrar could bind any certificate
 * to any keypair, and the registry would hold a key the certificate does not
 * contain. Every signature that key produced would then verify against a
 * certificate that never authorised it.
 *
 * ## The root is the first record on this chain, not a file somewhere
 *
 * The schema says Final Chain is the only root CA and that "the root is pinned,
 * not distributed" — chain validation terminates at Final Chain **by identity**,
 * never by finding a self-signed certificate in a local store.
 *
 * `registerRoot` is that pin, and it is the only entry point that accepts a
 * certificate without checking an issuer's signature. It takes a depth-0,
 * self-issued certificate from the bootstrap admin, once. Everything after it
 * is `registerWithCertificate`, which **verifies the issuer's ML-DSA and
 * SLH-DSA signatures on chain, through the precompiles**, against the issuer's
 * own registered keys, and checks that the child's `AuthorityKeyId` is the
 * issuer's `SubjectKeyId` and that the issuer's depth admits it.
 *
 * So there is no path by which a key enters this registry unattested. Not
 * "a registrar should only register certified keys" — a registrar *cannot*
 * register anything else.
 *
 * ## Roles are a bitmask
 *
 * One party is legitimately several things — a co-signer that is also a
 * publisher, an operator that is also a guardian. A single enum would force
 * either duplicate records for one key (two sources of truth about one party)
 * or a role hierarchy nobody agrees on. A mask has neither problem, and a
 * quorum asks "does this account carry ROLE_X" rather than "is this account an
 * X", which is the same distinction the certificate schema draws when it says
 * verifiers check for capabilities and never for types.
 *
 * ## 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, an LMS key,
 * the registrar threshold itself) and every state-plane configuration change
 * that routes 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 exception, and it is the only one: while it is
 * open the bootstrap admin writes alone, because every roster has to be
 * installed by someone before it can install itself.
 *
 * ## The sender is not the account
 *
 * Final Chain transactions are type 0x46, signed by ML-DSA-87, and the node
 * derives `msg.sender` from the key: `keccak256(0x04 ‖ publicKey)[12:]`. That
 * address pays gas and holds no authority. {accountOfSender} binds it to the
 * identity whose `activeTransaction` 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 rather than the roster.
 */
/// @dev Domain for a stage's encapsulation commitment. Byte-equal to
/// `FinalWalletFactory.DOMAIN_KEM_BUNDLE` and to `DOMAIN_KEM_BUNDLE_PREIMAGE` in
/// the issuer; three derivations of one word, and a mismatch in any of them is a
/// certificate that verifies nowhere.
bytes32 constant DOMAIN_KEM_BUNDLE = keccak256("FINAL_KEM_BUNDLE_v01");

/// @dev Tree 8's leaf domain — byte-equal to
/// `IdentityRootModule.DOMAIN_IDENTITY_LEAF` on every execution chain.
/// Restated rather than imported because the module lives on other chains and
/// there is no import that would make them one value; the cross-contract
/// parity test pins the pair. The `_PQ_` spelling is historical and FROZEN:
/// the premined vanity certificates were mined against this exact constant,
/// and the leaf it derives is the `certHash` inside every wallet's CREATE2
/// derivation.
bytes32 constant DOMAIN_IDENTITY_LEAF = keccak256("FINAL_IDENTITY_LEAF_PQ_v01");

/// @dev D7 (ruled 2026-09-01): ISSUER records project into tree 8 under their
/// own domain — `DOMAIN_ISSUER_LEAF ‖ certHash ‖ version ‖ issuerTreeRoot` —
/// so an issuer is stapleable for offline licence verification while the
/// distinct domain keeps its leaf out of wallet admission (the gateway folds
/// with the wallet domain, so an issuer leaf can never satisfy
/// `verifyIdentityCert`). `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 the fixed-depth insertion-ordered state
/// trees cannot prove non-inclusion.
bytes32 constant DOMAIN_ISSUER_LEAF = keccak256("FINAL_ISSUER_LEAF_v01");

/// @dev Chain-issuer constants (ruled 2026-09-01, amended same day: C-less).
/// The chain is the issuer but holds no keypair, so every chain-attested
/// certificate carries these two NAMED values in its issuer fields — required
/// by the wire format, verifying nothing, covered by `certHash`. The DN is
/// deliberately env-agnostic AND jurisdiction-silent: the issuer is the
/// worldwide network, not a legal entity, and an env-specific DN would fork
/// `certHash` per environment. Reference implementation:
/// `dashboard/public/fcert.js` (`CHAIN_ISSUER_DN`, `CHAIN_AUTHORITY_KEY_ID`);
/// `docs/developers/certificate-schema.md` § Chain-issuer constants.
bytes32 constant CHAIN_ISSUER_DN_HASH = keccak256("CN=Final Chain,O=Final DeFi");

/// @dev `SHA3-256(utf8("FINAL_CHAIN_AUTHORITY_v01"))` — a DOMAIN constant, not
/// a key digest (the chain has no PublicKeyBlock). Precomputed because the
/// mock SHA3 precompile under Foundry is deliberately not the real function;
/// pinned against `hashlib.sha3_256` and the dashboard's value by test.
/// Zero-length AuthorityKeyId stays reserved for the retired genesis root
/// alone and is admitted nowhere.
bytes32 constant CHAIN_AUTHORITY_KEY_ID =
    0x9a6a5d8139ad2d28957698330aaa691017dba7dc80eb7cbec585239fb680bbab;

/// @notice The identity tree's projection door on `FinalStateTrees`. 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. Same pattern as `IChainSource` on the trees side.
interface IIdentityLeafSink {
    function syncIdentityLeaves(address[] calldata accounts) external;
}

/// @notice `FinalRevocationLog`'s recording door, same narrow-interface
/// reasoning. `recorded` is read first so a fingerprint someone already
/// recorded permissionlessly cannot revert the registry mutation feeding it.
interface IRevocationRecorder {
    function record(bytes32 signerId) external;
    function recorded(bytes32 signerId) external view returns (bool);
}

contract FinalIdentityRegistry {
    // ---------------------------------------------------------------- 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

    /// @dev One per membership mutation, so an approval to grant a role can
    /// never be replayed as one to revoke. The registry is its own verifying
    /// contract for these.
    bytes32 public constant DOMAIN_REGISTER_WALLET = keccak256("FINAL_REGISTRY_REGISTER_WALLET_v01");
    bytes32 public constant DOMAIN_REGISTER_ISSUER = keccak256("FINAL_REGISTRY_REGISTER_ISSUER_v01");
    /// @notice The admission proof-of-possession digest domain (schema §v5).
    /// 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.
    bytes32 public constant DOMAIN_IDENTITY_ADMISSION = keccak256("FINAL_IDENTITY_ADMISSION_v01");
    /// @notice Root-plane global certificate revocation (D5).
    bytes32 public constant DOMAIN_REVOKE_CERTIFICATE =
        keccak256("FINAL_REGISTRY_REVOKE_CERTIFICATE_v01");
    /// @notice The ISSUING identity's certificate-revocation digest domain.
    bytes32 public constant DOMAIN_ISSUER_CERT_REVOCATION =
        keccak256("FINAL_ISSUER_CERT_REVOCATION_v01");
    bytes32 public constant DOMAIN_REGISTER_LMS_KEY = keccak256("FINAL_REGISTRY_REGISTER_LMS_KEY_v01");
    bytes32 public constant DOMAIN_SET_ROLES = keccak256("FINAL_REGISTRY_SET_ROLES_v01");
    bytes32 public constant DOMAIN_REVOKE = keccak256("FINAL_REGISTRY_REVOKE_v01");
    bytes32 public constant DOMAIN_SET_REGISTRAR_THRESHOLD =
        keccak256("FINAL_REGISTRY_SET_REGISTRAR_THRESHOLD_v01");

    /// @dev The algorithm id the sender derivation is domain-separated by:
    /// ML-DSA-87, FIPS 204, the only algorithm the transaction envelope admits.
    uint8 private constant ENVELOPE_ALG_ML_DSA_87 = 4;

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

    /**
     * @notice One party's on-chain identity.
     * @dev `version` increments on every mutation and is what a rotation is:
     * the record is replaced, not appended to, and the version is how a reader
     * on another chain knows which of two copies it saw 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;
        /// Seconds since epoch. The schema's TBS is nanoseconds; the conversion
        /// happens off chain because block timestamps are seconds and a
        /// comparison across units is a bug waiting for a leap.
        /// @dev MILLISECONDS — this chain's clock. See `FinalChainTime`.
        uint64 notBefore;
        /// Seconds since epoch, or 0 for "never expires" — which the schema
        /// allows and personal identity certificates use.
        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;
    }

    /**
     * @notice A hash-based (LMS) signing key held by a registered account.
     *
     * The protocol plane's quorums verify LMS, not ML-DSA: an execution chain
     * has no PQ precompiles, so `FinalRootAuthority` checks a keccak hash loop
     * instead (`arch/hash-based-authority.md`). Those keys are the authority
     * over `masterRoot`, and therefore over PQ execution — which makes "who
     * holds signer 0x39bb…?" 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 LMS signing key for an account, if it holds one.
    /// @dev One slot per (account, chain) — LMS-01. `nextLeaf` on an
    /// authority is a complete single-use counter 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.
    mapping(address account => mapping(uint64 chainId => LmsKey)) private _lmsKey;
    /// @notice Which account a signer fingerprint belongs to. This is the
    /// lookup the whole record exists for: a gateway roster names fingerprints
    /// and nothing else, so without it the keys are unattributable.
    /// @dev What a fingerprint is bound to: the account that holds it and the
    /// chain it signs for — one slot, written once at registration and left in
    /// place when superseded (attribution is history). The chain names the
    /// (account, chain) slot `lmsSignerIsLive` resolves against.
    // NOTE: this contract sits ~13 bytes under EIP-170 (24,563 of 24,576 at
    // the pinned optimizer settings). The next feature here pays for itself
    // in bytecode first — see the LMS-binding merge and the off-chain
    // zero-chain check for what that looks like.
    struct LmsBinding {
        address account;
        uint64 chainId;
    }

    mapping(bytes32 signerId => LmsBinding) private _lmsBinding;

    /// @notice The identity record for an account.
    mapping(address account => Identity) private _identity;
    /// The four slots, verbatim. All four are stored in full because the
    /// precompiles verify against a KEY, not a commitment — and a key that
    /// arrived in calldata proves nothing about who signed.
    ///
    /// A CA has two keys, not 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;
    mapping(address account => bytes) private _activeAccessKey;
    mapping(address account => bytes) private _recoveryTransactionKey;
    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. Empty for every identity
    /// whose certificate carries no `PURPOSE_ACTIVE_SEAL` entry: users, CAs.
    mapping(address account => bytes) private _activeSealKey;
    /// @notice Encapsulation keys, per stage. Two algorithms each — ML-KEM-1024
    /// (lattice) and HQC-5 (code-based) — so a break in either family leaves the
    /// other standing, the same reasoning that pairs ML-DSA with SLH-DSA above.
    /// @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;
    mapping(address account => bytes) private _activeKemHqc;
    mapping(address account => bytes) private _recoveryKemMlKem;
    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

    event IdentityRegistered(
        address indexed account, bytes32 indexed certHash, uint256 roles, uint64 version
    );
    event IdentityRolesChanged(address indexed account, uint256 previousRoles, uint256 newRoles);
    event LmsKeyRegistered(
        address indexed account,
        bytes32 indexed signerId,
        uint64 indexed chainId,
        bytes16 keyId,
        uint8 height,
        bytes32 root,
        uint64 version
    );
    event IdentityRevoked(address indexed account, bytes32 indexed certHash);
    /// @notice One revocation-lane entry: `revoker` is `address(0)` for the
    /// root plane, the issuing identity otherwise.
    event CertificateRevoked(bytes32 indexed certHash, address indexed revoker);
    event BootstrapSealed(address indexed sealedBy);
    /// @notice The one-shot state-plane wiring landed.
    event StatePlaneWired(address stateTrees, address revocationLog);
    event RegistrarThresholdSet(uint256 threshold);
    /// @notice A registrar quorum authorized an action. `nonce` is the value
    /// the approvals were made over; the next action needs the next one.
    event RegistrarQuorumApproved(
        address indexed verifyingContract, bytes32 indexed actionDomain, uint64 nonce, uint256 valid
    );

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

    error NotAuthorized(address caller);
    error BootstrapAlreadySealed();
    error UnknownAccount(address account);
    /// @notice A certificate's encapsulation key failed the chain's own
    /// well-formedness check. Names the algorithm, because the pair is stored
    /// together and "one of these two" is not an actionable answer.
    error MalformedEncapsulationKey(address account, uint16 algorithmId);
    error CertificateAlreadyBound(bytes32 certHash, address boundTo);
    error CertificateIsRevoked(bytes32 certHash);
    error VersionNotNewer(uint64 current, uint64 offered);
    error IssuerNotACertificateAuthority(address issuer);
    error IssuerMayNotSign(address issuer, uint8 depth, uint8 maxDelegationDepth);
    error WrongDepth(uint8 got, uint8 want);
    error DelegationWidened(uint8 child, uint8 issuer);
    error AuthorityKeyIdMismatch(bytes32 got, bytes32 want);
    error StagesDisagree(bytes32 liveSerial, bytes32 recoverySerial);
    /// @notice `height` outside 1..24. See `FinalLms.MAX_HEIGHT`.
    error LmsHeightOutOfRange(uint8 height);
    /// @notice A zero root commits to no tree.
    error LmsRootIsZero();
    /// @notice This fingerprint already belongs to a different account.
    error LmsKeyAlreadyBound(bytes32 signerId, address boundTo);
    /// @notice Two identities cannot share a transaction key: the sender it
    /// derives would be attributable to both.
    error SenderAlreadyBound(address sender, address boundTo);
    /// @notice Fewer registrars able to seal than the threshold asks for.
    error RegistrarThresholdUnreachable(uint256 sealable, uint256 threshold);
    error RegistrarThresholdIsZero();
    /// @notice {wireStatePlane} ran already, or was handed a zero address.
    error StatePlaneAlreadyWired();
    error ZeroStatePlane();
    /// @notice The holder's admission proof of possession did not verify —
    /// one family failed, or the digest was built over the wrong nonce.
    error AdmissionProofInvalid(address account);
    /// @notice The certificate does not carry the ruled chain-issuer
    /// AuthorityKeyId — it is not a chain-attested certificate.
    error NotChainAttested(bytes32 authorityKeyId);
    /// @notice The certificate's IssuerDN is not the ruled constant.
    error WrongIssuerDn(bytes32 issuerDnHash);
    /// @notice A chain-attested end entity sits at depth 1 with
    /// `maxDelegationDepth == depth`; anything else is not an end entity.
    error NotAnEndEntity(uint8 depth, uint8 maxDelegationDepth);
    /// @notice An issuer that cannot sign is an end entity wearing a profile.
    error IssuerCannotSign(uint8 depth, uint8 maxDelegationDepth);
    /// @notice Third-party issuers carry a real `NotAfter` (ruling 3) —
    /// expiry is the passive half of their lifecycle.
    error IssuerMustExpire();
    /// @notice An issuer validity window past the ~2-year ceiling (ruling 3).
    error IssuerValidityTooLong(uint64 notBefore, uint64 notAfter);
    /// @notice An institution registration without a real ISO 3166 `C=` in
    /// its subject DN, or with a jurisdiction that does not match its
    /// Institution extension. Only the trust root is jurisdiction-silent.
    error JurisdictionMissing();
    error JurisdictionMismatch();

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

    /**
     * @param admin The bootstrap registrar. Genesis names the chain deployer.
     * @dev The precompile probe is the point of the constructor. This contract
     * is meaningless on a chain that cannot verify PQ signatures, and deploying
     * it there would produce a registry full of keys nothing can check.
     */
    constructor(address admin) {
        FinalChainPrecompiles.assertAvailable();
        bootstrapAdmin = admin;
    }

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

    /**
     * @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
     * pretending otherwise produced the one roster that could not be
     * bootstrapped in `FinalRootAuthority`. It is closed by
     * `sealBootstrap`, which is irreversible.
     *
     * While it 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.
     */
    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 state-plane contracts.
     * @dev `msg.sender` — the calling contract — is the verifying contract the
     * digest binds and the counter it burns, so an approval collected for the
     * trees' configuration cannot be spent on the bundle log'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 the quorum.
     */
    function requireRegistrarQuorum(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireRegistrarQuorum(msg.sender, actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /// @dev The digest is `FinalPqQuorum.digest(verifyingContract, actionDomain,
    /// anchorBlock, keccak256(abi.encode(nonce, payloadDigest)))`; the seal is
    /// required — membership is the hybrid class.
    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 Bootstrap admin while the window is open; the current registrar
     * quorum afterwards, so a registrar set that grows or shrinks can move it.
     * Refuses a threshold the sealable registrars cannot meet, and refuses zero:
     * both are a registry that can never be written to again.
     */
    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.
    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`. Restated rather
     * than imported because the two live on different chains and there is no
     * import that 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.
     */
    function lmsSignerId(bytes16 keyId, uint8 height, bytes32 root) public pure returns (bytes32) {
        return keccak256(abi.encode(keyId, height, root));
    }

    /**
     * @notice Record the LMS signing key an already-registered account holds.
     * @dev Membership-gated, same as 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-per-key-they-hold.
     *
     * @param account Must already be registered and not revoked.
     * @param version Strictly increasing. 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; see
     *   `FinalPqQuorum`. 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, chain) — LMS-01 made the same
        // operator a different signer on every chain, so chain B starting at
        // version 1 says nothing about chain A 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.
    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. Zeroes for a fingerprint never registered.
    /// @dev The revocation log's permanence gate reads this to find the
    /// (account, chain) SLOT a fingerprint belongs to — the slot's current key
    /// is what separates a superseded fingerprint (permanent, recordable) from
    /// a merely lapsed one (expiry, temporary, refused). Attribution is
    /// history: the binding survives supersession, exactly as the mapping
    /// behind {lmsSignerIsLive} does, because it IS that mapping.
    function lmsBindingOf(bytes32 signerId) external view returns (address account, uint64 chainId) {
        LmsBinding storage binding = _lmsBinding[signerId];
        return (binding.account, binding.chainId);
    }

    /**
     * @notice Is this signer fingerprint held by a live, unrevoked account?
     * @dev The question a verifier actually has. A gateway roster names
     * fingerprints and nothing else, so "is 0x39bb… still good?" is otherwise
     * unanswerable from the state plane.
     */
    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. The
    /// count is of registrars that can SEAL — a certificate authority carrying
    /// the role has no seal key and can never contribute an approval.
    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 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 — both contracts take THIS registry as one — so the deploy
     * tooling calls it in the same nonce-fixed block that deploys them, before
     * any identity is registered.
     */
    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_);
    }

    /// @dev Project `account`'s tree-8 leaf, same-tx. Skipped while the plane
    /// is unwired — the 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 besides forgetting to call it.
    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);
    }

    /// @dev Record a PERMANENTLY retired fingerprint, same-tx, unless the log
    /// is unwired or someone already recorded it permissionlessly.
    function _recordRevokedSigner(bytes32 signerId) private {
        address log = revocationLog;
        if (log == address(0)) return;
        if (IRevocationRecorder(log).recorded(signerId)) return;
        IRevocationRecorder(log).record(signerId);
    }

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

    /// @notice The holder's admission proof of possession: both live-stage
    /// families over the admission digest (schema §v5). There is no root
    /// keypair and no CA signature any more — the chain admits, and the
    /// "2 signatures at creation" are the HOLDER's, verified by the
    /// precompiles inside this very transaction.
    struct AdmissionProof {
        bytes mlDsaSignature;
        bytes slhDsaSignature;
    }

    /**
     * @notice Register or rotate a Final Wallet identity from its two public
     *         certificates — CHAIN-ATTESTED (schema §v5, ruled 2026-09-01).
     *
     * @param account The wallet address the certificate set derives.
     * @param liveTbs `live.pub.fcert` TBS — `activeTransaction` + `activeAccess`.
     * @param recoveryTbs `recovery.pub.fcert` TBS — 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), verified in the precompiles inside this
     *        transaction. This replaced the CA signature: issuance authority
     *        is the registrar quorum, possession is this proof, and there is
     *        no root keypair anywhere.
     * @param roles Capability bitmask. The one thing the certificates do not
     *        say, because capability is this system's decision.
     * @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.
     *
     * @dev **Both stages, together.** A wallet has four keys in two stages and
     * the recovery pair is PRE-COMMITTED — written at `initialize` from the same
     * certificate set that determined the address, which is why PQ migration
     * takes no key arguments. The two must share a `SerialNumber`: a serial is
     * per certificate SET, so two stages disagreeing are two different wallets.
     *
     * **Chain-attested means pinned, per stage:** the ruled IssuerDN and
     * AuthorityKeyId constants, depth exactly 1 (directly under the chain),
     * and `maxDelegationDepth == depth` (an end entity signs nothing — the
     * same immutable pair `identityTreeLeafOf` discriminates records 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 our own
     *         intermediate) that signs certificates OFF-chain with the keys
     *         registered here (D2: the superCA).
     *
     * @param account The issuer's account on this chain.
     * @param tbs The single issuer certificate's TBS: two CERT_SIGNING keys
     *        (ML-DSA-87 + SLH-DSA-SHAKE-256s), 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;
     *        `address(0)` for an issuer hanging directly under the chain.
     * @param proof The issuer's OWN two cert-signing keys over the admission
     *        digest (`recoveryCertHash` slot is zero — there is no recovery
     *        stage to bind).
     *
     * @dev Admission is chain-native like any identity: registrar quorum plus
     * the holder's PoP. What the v4 delegation rules said survives verbatim as
     * LINEAGE — a nested issuer's depth, delegation bound and AuthorityKeyId
     * must chain to its registered parent — but no parent SIGNS anything; the
     * chain's admission is the issuance.
     *
     * Ruling 3: a registered issuer always expires (`NotAfter` real, window
     * bounded ~2 years) — the passive liveness touchpoint; renewal re-issues
     * under the same registered keys with a version bump.
     *
     * The jurisdiction rule (ruled 2026-09-01, amended): only the trust root
     * is jurisdiction-silent. An institution MUST carry its real ISO 3166
     * `C=` in its subject DN, matching the `jurisdiction` field of its
     * `0x0102` Institution extension — CA/Browser-Forum practice, enforced at
     * the door because a verifier's legal recourse starts with knowing where
     * an issuer answers for itself.
     */
    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 Ruling 3's validity ceiling for registered issuers, in this
    /// chain's milliseconds: two 366-day years.
    uint64 public constant MAX_ISSUER_VALIDITY_MS = 2 * 366 days * 1000;

    /// @dev The chain-attested end-entity pins, run once per 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);
        }
    }

    /// @dev The v4 delegation rules, surviving as lineage: a nested issuer
    /// chains to a registered, signing-capable parent one level up; a direct
    /// issuer hangs under the chain at depth 1.
    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);
        }
    }

    /// @dev The jurisdiction rule: a real ISO 3166 alpha-2 `C=` in the subject
    /// DN, equal to the Institution extension's `jurisdiction` field. The DN
    /// is canonical comma-separated form, so `C=` matches at the start or
    /// right after a comma; the component value is exactly two bytes.
    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();
    }

    /// @dev Verify the holder's PoP: both live-stage families over the
    /// admission digest, in the precompiles, inside this transaction. Burns
    /// the gate nonce on the bootstrap path (the quorum path burned it in
    /// `_requireRegistrarQuorum` already), so an admission is one-shot in
    /// both regimes.
    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;
        }
    }

    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);
    }

    /**
     * @dev Store one stage's encapsulation pair, or clear it.
     *
     * Empty is legitimate and is not the same as absent-and-wrong: a CA has no
     * encapsulation stage, and a certificate issued before v4 carries none.
     * `FinalCertificate.parse` 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 intent then never decrypts — the failure
     * mode with no error attached, and the one this whole pairing exists to
     * avoid.
     */
    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.
    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);
    }

    /// @dev Once sealed, no mutation may leave the registrar quorum unreachable
    /// — that is the one change nothing could ever undo. Checked after the
    /// write so the count reflects it.
    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.
    /// @param chainIds The chains whose LMS-key slots this account holds — the
    /// registrars supply the list (the digest binds it) because a mapping
    /// cannot enumerate its own keys. Each named slot's current fingerprint is
    /// recorded into the revocation log same-tx; a chain with no slot is
    /// skipped, and a fingerprint missed by an incomplete list stays
    /// permanently recordable through the log's permissionless door, since a
    /// revoked account never regains standing.
    /// @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.
    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` (D5).
     *
     * @dev The half of the one revocation lane that gates registration and
     * covers break-glass: any certificate — registered, off-chain-issued, or
     * never seen — can be killed by handle under the registrar quorum. When
     * the handle is a registered identity's CURRENT certificate the identity
     * falls with it (flag, roles, same-tx projection), so a break-glass by
     * handle is never weaker than {revoke} — it only skips the LMS-slot
     * enumeration, which stays permanently recordable through the revocation
     * log's permissionless door.
     */
    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 "Sub-issuer and us alike" (D5) — but SCOPED: this records WHO
     * revoked, and a verifier honours the entry only when the revoker is the
     * certificate's own issuer (which the verifier knows — it holds the
     * cert). 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, the
     * chain, the handle and the issuer's own gate nonce. 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 the lane models one.
     */
    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. `registered` is the field to branch on.
    function identityOf(address account) external view returns (Identity memory) {
        return _identity[account];
    }

    /// @notice `activeTransaction` — ML-DSA-87. What a quorum verifies against.
    function activeTransactionKeyOf(address account) external view returns (bytes memory) {
        return _activeTransactionKey[account];
    }

    /// @notice `activeAccess` — SLH-DSA-SHAKE-256s. Identity, and guardianship.
    function activeAccessKeyOf(address account) external view returns (bytes memory) {
        return _activeAccessKey[account];
    }

    /// @notice `activeSeal` — SLH-DSA-SHAKE-256s. What `FinalPqQuorum` verifies
    /// an execution-class approval's `seal` against. Empty when the identity
    /// carries no seal, in which case it cannot take part in a sealed quorum.
    function activeSealKeyOf(address account) external view returns (bytes memory) {
        return _activeSealKey[account];
    }

    /// @notice `recoveryTransaction`. Authorizes rotating this account's own
    /// credentials and nothing else. Empty for a CA.
    function recoveryTransactionKeyOf(address account) external view returns (bytes memory) {
        return _recoveryTransactionKey[account];
    }

    /// @notice `recoveryAccess`. Empty for a CA.
    function recoveryAccessKeyOf(address account) external view returns (bytes memory) {
        return _recoveryAccessKey[account];
    }

    /// @notice The four commitments, in the order tree 1's leaf wants them.
    /// @dev keccak, not SHA3 — these feed `FinalWalletFactory.accountStateLeafHash`,
    /// which every other chain verifies with, and that one hashes with keccak.
    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 the wallet's CREATE2 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 (`minePqVanityCerts.cjs` is the reference encoder;
     * the parity test pins this function against the premined fixtures).
     *
     * 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
     * CA. The CA exclusion is structural, not a role read: an end entity has
     * `depth == maxDelegationDepth` (it issues nothing), a CA never does, and
     * the depth pair is immutable per version where roles are not.
     *
     * Lives HERE rather than on `FinalStateTrees` (whose tree 8 consumes it)
     * because every input is this contract's storage and the trees contract
     * sits against EIP-170.
     */
    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) {
            // D7 (ruled 2026-09-01): an ISSUER exists in tree 8 under its own
            // domain, so its record is stapleable for offline licence
            // verification. `certHash` suffices (it covers the whole TBS and
            // the verifier holds the cert), `version` makes supersession move
            // the leaf, and the third word RESERVES the issuer's own
            // certificate-tree anchor — zero until wired. The distinct domain
            // does the wallet-admission exclusion the zero projection used to
            // do; zero-on-revoke above is now 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 `AccountStateLeaf` order.
    /// @dev One word per STAGE, over both of that stage's KEM public keys. The
    /// pair is the unit — an account holds both or neither — so committing 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 registered before the encapsulation slots existed hashes the
    /// empty string here rather than reverting: `syncIdentities` must keep
    /// projecting it, and a leaf that cannot be built is a party that cannot be
    /// revoked.
    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 encapsulation keys themselves, for a party composing a message.
    function kemKeysOf(address account)
        external
        view
        returns (bytes memory activeMlKem, bytes memory activeHqc)
    {
        return (_activeKemMlKem[account], _activeKemHqc[account]);
    }

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

    /**
     * @notice The Final Chain sender a transaction key produces.
     * @dev `keccak256(uint8(4) ‖ publicKey)[12:]` — byte-identical to what the
     * node derives from a type-0x46 envelope and to the backend's
     * `pqTransaction.senderOf`. Pure, so a client can compute it from a
     * certificate before the identity is registered.
     */
    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, 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. False for a sender no identity claims.
    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.
    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.
    function accountCount() external view returns (uint256) {
        return _accounts.length;
    }

    /// @notice Registered account by index, in registration order.
    function accountAt(uint256 index) external view returns (address) {
        return _accounts[index];
    }

    /// @notice Every account carrying every bit in `roleMask`.
    /// @dev A view, so the O(n) scan costs nothing. Callers that need this in a
    /// transaction should pass the member list explicitly instead — see
    /// `FinalPqQuorum`, which takes signers rather than searching for them.
    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 that reverts forever with nothing naming the
     * roster as the cause.
     */
    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 should not be a universal pass.
     */
    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.
    function isActive(address account) public view returns (bool) {
        Identity storage id = _identity[account];
        return id.registered && !id.revoked && _withinValidity(id);
    }

    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;
    }

}

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
//
// @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;
    }

    error ThresholdNotMet(uint256 valid, uint256 required);
    error SignersNotAscending(address previous, address next);
    error SignerLacksRole(address signer, uint256 roleMask);
    error WrongAlgorithm(address signer, uint8 got, uint8 required);
    error BadSignature(address signer, uint8 algorithm);
    error BadSeal(address signer);
    error AnchorAhead(uint64 anchorBlock, uint256 blockNumber);
    error AnchorStale(uint64 anchorBlock, uint256 blockNumber);
    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);
    }

    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
//
// @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";

/// @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 {
    function enabledChainRefs() external view returns (bytes32[] memory);
}

/// @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 {
    function slotKeyLeafOf(address member, uint64 slotIndex) external view returns (bytes32);
}

/// @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 {
    function endpointLeafOf(bytes32 endpointId) external view returns (bytes32);
}

/**
 * @title FinalStateTrees
 * @notice The eight trees. Final Chain's state plane, and the source of truth
 *         every other chain projects from.
 *
 * @dev 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 |
 *
 * ## 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 (2026-09-04)
 *
 * 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 {
    // ---------------------------------------------------------------- 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` — a
    /// CONTINUOUS root over this tree replaces the cold-set snapshot the
    /// retired `publishIdentityRoot.cjs` ceremony folded off-chain. 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 (WAL-02), never the
    ///   certificate's genesis keys.
    ///
    /// A quorum of service signatures must not be able to state an identity
    /// neither ruler decided, so `setLeaves` refuses this tree outright.
    uint8 public constant TREE_IDENTITY = 8;
    /// @notice Count, for iteration. Trees are 1-indexed; 0 is not a tree.
    uint8 public constant TREE_COUNT = 8;

    /// @notice 2^24 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 in it.
    /// A million rows per branch is far past where this design gets replaced
    /// by Final Chain proper. Raising any of this later is a migration, not a
    /// parameter change: the depth is in every root.
    uint256 public constant DEPTH = 24;
    uint256 public constant BRANCH_BITS = 4;
    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.
    uint256 public constant BRANCH_CAPACITY = 1 << BRANCH_DEPTH;
    /// @notice Slots per tree, all branches together.
    uint256 public constant CAPACITY = 1 << DEPTH;
    /// @notice 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-28 tree
    /// whose level-24 nodes are the eight tree roots, which is what lets one
    /// path prove a leaf against it.
    uint256 public constant FOREST_BITS = 4;
    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 (user ruling 2026-09-05).
    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;

    /// @dev Must equal `FinalWalletFactory.DOMAIN_ACCOUNT_STATE_LEAF`. Pinned
    /// by the cross-repo parity test; a field reordered on one side and not the
    /// other is a root every chain rejects.
    ///
    /// `v02`: `deployedChains` became the `(chainRef, account)` table. A v01
    /// leaf and a v02 leaf never share a domain, so a proof built against the
    /// retired plane cannot verify against this one by accident.
    bytes32 public constant DOMAIN_ACCOUNT_STATE_LEAF =
        keccak256("FINAL_ACCOUNT_STATE_LEAF_v02");

    bytes32 private constant ACTION_SET_LEAVES = keccak256("FinalStateTrees.setLeaves.v01");
    /// @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");
    bytes32 public constant ACTION_SET_TREE_WRITER = keccak256("FINAL_STATE_TREES_SET_TREE_WRITER_v01");
    bytes32 public constant ACTION_SET_CHAIN_SOURCE = keccak256("FINAL_STATE_TREES_SET_CHAIN_SOURCE_v01");
    bytes32 public constant ACTION_SET_SLOT_KEY_SOURCE = keccak256("FINAL_STATE_TREES_SET_SLOT_KEY_SOURCE_v01");
    bytes32 public constant ACTION_SET_ENDPOINT_SOURCE = keccak256("FINAL_STATE_TREES_SET_ENDPOINT_SOURCE_v01");
    bytes32 public constant ACTION_SEED_COUNTERS = keccak256("FINAL_STATE_TREES_SEED_COUNTERS_v01");
    bytes32 public constant ACTION_SET_TYPED_WRITER = keccak256("FINAL_STATE_TREES_SET_TYPED_WRITER_v01");
    bytes32 public constant ACTION_SET_CONFIG = keccak256("FINAL_STATE_TREES_SET_CONFIG_v01");

    /// @dev Key domains. Both are full-width hashes rather than the packed
    /// address they 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");
    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");
    bytes32 private constant DOMAIN_SLOT_KEY = keccak256("FinalStateTrees.key.slotKey.v01");
    bytes32 private constant DOMAIN_ENDPOINT_KEY = keccak256("FinalStateTrees.key.endpoint.v01");
    bytes32 private constant DOMAIN_CONFIG_KEY = keccak256("FinalStateTrees.key.config.v01");

    /// @notice Leaf domains for the owner index (tree 8, branch 2) and for
    /// configuration rows (branch 0 of every tree). The config leaf binds the
    /// tree too, so the same row in two trees is two different leaves.
    bytes32 public constant DOMAIN_OWNER_INDEX_LEAF = keccak256("FINAL_OWNER_INDEX_LEAF_v01");
    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;

    /// Raw (untagged) leaf value by tree and slot.
    mapping(uint8 => mapping(uint256 => bytes32)) private _leaf;
    /// Internal nodes, levels 1..DEPTH. Level 0 is derived from `_leaf`.
    mapping(uint8 => mapping(uint256 => mapping(uint256 => bytes32))) private _node;
    /// Empty-subtree hash per level, computed once at construction — up to
    /// the round root's height, since the forest's empty positions are empty
    /// trees.
    bytes32[ROUND_DEPTH + 1] private _zero;

    /// Permanent slot for a key, 1-based so 0 means unassigned. The slot's top
    /// `BRANCH_BITS` are the branch the key lives in.
    mapping(uint8 => mapping(bytes32 => uint256)) private _slotPlusOne;
    /// The key a slot was handed to — the reverse of `_slotPlusOne`, so any
    /// branch enumerates on chain (`keyAt` over `0 .. branchSlotsUsed`) with
    /// no log window. One extra word per NEW key, never per update.
    mapping(uint8 => mapping(uint256 => bytes32)) private _keyAt;
    /// @notice Slots handed out per tree, all branches together.
    mapping(uint8 => uint256) public slotsUsed;
    /// Slots handed out per branch — the next position in it.
    mapping(uint8 => mapping(uint8 => uint256)) private _branchSlotsUsed;
    /// The VALUE behind a configuration row (branch 0), by tree and key.
    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 {
        bytes32[TREE_COUNT + 1] roots;
        bytes32 roundRoot;
        uint64 blockNumber;
        uint64 timestamp;
    }

    /// @notice Published rounds, 1-indexed. Round 0 is "nothing published".
    mapping(uint64 => Round) private _rounds;
    /// @notice Highest published round.
    uint64 public round;
    /// Tree versions as of the last published round.
    mapping(uint8 => uint64) private _publishedVersion;

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

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

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

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

    event LeavesSet(uint8 indexed treeId, uint256 count, bytes32 newRoot, uint64 treeVersion);
    event RoundPublished(uint64 indexed round, uint64 blockNumber, uint64 timestamp);
    event TreeConfigured(uint8 indexed treeId, uint256 writerRole, uint256 threshold);
    event TreeWriterSet(uint8 indexed treeId, address writer);
    event ChainSourceSet(address source);
    event SlotKeySourceSet(address source);
    event EndpointSourceSet(address source);
    /// @notice A fresh plane took over the previous plane's counters.
    event CountersSeeded(uint64 round, uint64[] versions);
    event TypedWriterSet(address writer);
    event ConfigSet(uint8 indexed treeId, bytes32 indexed key, bytes32 value);

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

    error UnknownTree(uint8 treeId);
    error LengthMismatch(uint256 keys, uint256 leaves);
    error BranchFull(uint8 treeId, uint8 branch);
    error UnknownBranch(uint8 branch);
    /// @notice A key already holds a slot in another branch of this tree.
    error BranchMismatch(uint8 treeId, bytes32 key, uint8 have, uint8 want);
    /// @notice Branch 0 is written by `setConfig` alone.
    error ConfigBranchReserved(uint8 treeId);
    error SlotKeySourceUnset();
    error EndpointSourceUnset();
    /// @notice Counters can be seeded only into a plane that has published nothing.
    error NotFresh();
    error VersionCountMismatch(uint256 given);
    error TreeNotConfigured(uint8 treeId);
    error NothingToPublish();
    error UnknownKey(uint8 treeId, bytes32 key);
    error NotAuthorized(address caller);
    error NoRounds();
    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).
    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.
    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.
    error InvalidChainAccount(bytes32 chainRef, bytes32 account);

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

    /**
     * @param registry_ The identity registry. Every signer, key and role is
     *        resolved through it.
     * @dev 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.
     */
    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

    /**
     * @dev The configuration gate: 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.
     */
    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.
     * @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.
     */
    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.
     */
    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.
    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.
    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 Take over the previous plane's counters — one `treeVersion` per
     *         tree (index = treeId, 0 unused) and the published `round` — so a
     *         redeploy is monotonic for every consumer that compares them
     *         (rings, explorers, the round feed). NO-WIPE redeploy, ruled
     *         2026-09-03. Past rounds' roots stay on the old plane:
     *         `roundRootAt` below the seed answers zero.
     * @dev Configuration authority (bootstrap admin before the seal, registrar
     *      quorum after), and only while this plane has published nothing.
     */
    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.
    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.
     *
     * @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 the round root — one source for
     * the fleet, the contracts and the explorer, where the fleet's environment
     * used to be a second one.
     */
    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 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.
     */
    function setLeaves(
        uint8 treeId,
        uint8 branch,
        bytes32[] calldata keys,
        bytes32[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _assertTree(treeId);
        _assertDataBranch(treeId, branch);
        if (treeId == TREE_PHI || treeId == TREE_VASSET || treeId == TREE_ORACLE) {
            revert TypedTreeOnly(treeId);
        }
        // Trees 7 and 8 have their own rulers and NO quorum path at all: an
        // intent's status is what `FinalIntentLog` verified, an identity is
        // what the registry or the ledger verified, and no set of service
        // signatures can make a different answer true.
        if (treeId == TREE_INTENTS || treeId == TREE_IDENTITY) revert WriterOnlyTree(treeId);
        if (keys.length != leaves.length) revert LengthMismatch(keys.length, leaves.length);
        uint256 k = threshold[treeId];
        if (k == 0) revert TreeNotConfigured(treeId);

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

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

        _bump(treeId, keys.length);
    }

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

    /// @notice `FinalWalletFactory.AccountStateLeaf`, field for field.
    struct AccountStateLeaf {
        address wallet;
        bytes32 liveAccess;
        bytes32 liveTransaction;
        bytes32 recoveryAccess;
        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;
        bytes32 recoveryKem;
        address owner;
        bool pqEnabled;
        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 what a zero beneficiary resolves through; it
        /// replaced a bitmask over registry slots that could only say "may
        /// exist", never "as what".
        ChainAccount[] deployedChains;
        /// @dev Per-chain dormancy verdict, one bit per asset-registry chain
        /// slot. Keeps the slot space the bitmask had.
        uint32 dormantChains;
        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.
     */
    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.
     */
    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 EOA.
     */
    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.
    /// Pinned against the factory by test. `deployedChains` rides through
    /// `abi.encode` like every other field — head offset, then length and
    /// rows — so the table is committed whole and in order.
    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
            )
        );
    }

    /// @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.
    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`).
    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.
     */
    function writeTyped(
        uint8 treeId,
        bytes32[] memory keys,
        bytes32[] memory hashes,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (msg.sender != typedWriter) revert NotAuthorized(msg.sender);
        uint256 k = threshold[treeId];
        if (k == 0) revert TreeNotConfigured(treeId);

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

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

        _bump(treeId, keys.length);
    }

    /**
     * @notice Snapshot every tree's root into a new round.
     * @dev Permissionless, deliberately. Every root being snapshotted was
     * already authorized by its tree's quorum, so this adds no authority — it
     * only fixes a moment. Requiring a signature would put a liveness
     * dependency in front of publication for no security gain.
     *
     * A round that would change nothing is refused, so the round number cannot
     * be advanced by anyone with gas to spend.
     */
    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.
    function rootsAt(uint64 which) external view returns (bytes32[TREE_COUNT + 1] memory) {
        return _rounds[which].roots;
    }

    /// @notice One tree's root at one round.
    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.
    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.
     */
    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.
    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.
    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.
    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`.
    function keyAt(uint8 treeId, uint256 slot) external view returns (bytes32) {
        return _keyAt[treeId][slot];
    }

    /// @notice Slots handed out in one branch.
    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.
    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.
    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.
    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).
    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.
    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.
    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)`.
    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.
    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.
     */
    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.
    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.
     */
    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 A view, so the backend fetches a proof with one `eth_call` instead of
     * rebuilding 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.
     */
    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.
    function emptyRoot(uint256 level) external view returns (bytes32) {
        return _zero[level];
    }

    /// @notice The tree-1 key a wallet occupies.
    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.
     */
    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.
    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.
     */
    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.
     */
    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

    /// @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.
    function _enabledChainRefs() private view returns (bytes32[] memory) {
        address source = chainSource;
        if (source == address(0)) return new bytes32[](0);
        return IChainSource(source).enabledChainRefs();
    }

    function _assertTree(uint8 treeId) private pure {
        if (treeId == 0 || treeId > TREE_COUNT) revert UnknownTree(treeId);
    }

    function _assertBranch(uint8 branch) private pure {
        if (branch >= BRANCH_COUNT) revert UnknownBranch(branch);
    }

    /// @dev A branch a quorum or a writer may write: any but the config branch.
    function _assertDataBranch(uint8 treeId, uint8 branch) private pure {
        _assertBranch(branch);
        if (branch == BRANCH_CONFIG) revert ConfigBranchReserved(treeId);
    }

    /// @dev Version + event, the tail of every write door.
    function _bump(uint8 treeId, uint256 count) private {
        uint64 v = treeVersion[treeId] + 1;
        treeVersion[treeId] = v;
        emit LeavesSet(treeId, count, liveRoot[treeId], v);
    }

    /// @dev `keccak256(0x01 ‖ lo ‖ hi)`, the pair sorted — the one node hash.
    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));
    }

    /// @dev The sibling path from a slot up `height` levels.
    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;
        }
    }

    /// @dev The forest's leaves: the tree roots at their positions, the
    ///      empty tree at the rest.
    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];
        }
    }

    /// @dev Fold a power-of-two level to its root, in place.
    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];
    }

    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;
    }

    /// @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.
    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;
    }
}

abi

[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "registry_",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      },
      {
        "name": "trees_",
        "type": "address",
        "internalType": "contract FinalStateTrees"
      },
      {
        "name": "admin_",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "ACTION_CONFIGURE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "CAIP_NAMESPACE_EIP155",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "CHAIN_ROLE_FULL",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "CHAIN_ROLE_OBSERVED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "FINALITY_CONFIRMATIONS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "FINALITY_L2_SETTLED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "FINALITY_TAG_FINALIZED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "GAS_MODEL_EIP1559",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "GAS_MODEL_L2_WITH_L1_FEE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "GAS_MODEL_LEGACY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "LEVERAGE_CAP_MAX_PCT",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "LEVERAGE_CAP_MIN_PCT",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PRICE_CADENCE_DEFAULT_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PRICE_CADENCE_FAST_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PRICE_MAX_AGE_DEFAULT_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PRICE_MAX_AGE_FAST_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_CURVE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_UNISWAP_V2",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_UNISWAP_V3",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_UNISWAP_V4",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "PROTOCOL_VELODROME",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "VM_EVM",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "VM_MOVE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "VM_SVM",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "admin",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "allowlistKeyForAsset",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForAssetOnChain",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForChain",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForChainTerms",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForPolicy",
    "inputs": [
      {
        "name": "paramId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForProtocol",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "protocolId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "allowlistKeyForSource",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "venueId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "assetAt",
    "inputs": [
      {
        "name": "i",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.AssetEntry",
        "components": [
          {
            "name": "originChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "originToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "decimals",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "name",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "symbol",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "priceCadenceMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "maxAgeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "assetCount",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "assetIdFor",
    "inputs": [
      {
        "name": "originChainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "originToken",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "assetLeafHash",
    "inputs": [
      {
        "name": "a",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.AssetEntry",
        "components": [
          {
            "name": "originChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "originToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "decimals",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "name",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "symbol",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "priceCadenceMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "maxAgeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "assetOf",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.AssetEntry",
        "components": [
          {
            "name": "originChainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "originToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "decimals",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "name",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "symbol",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "priceCadenceMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "maxAgeMs",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "assetOnChain",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.AssetChainEntry",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "uses",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "maxLeveragePct",
            "type": "uint16",
            "internalType": "uint16"
          }
        ]
      }
    ],
    "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"
          }
        ]
      }
    ],
    "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"
          }
        ]
      }
    ],
    "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"
          }
        ]
      }
    ],
    "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"
          }
        ]
      }
    ],
    "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": "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": "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": "policyAt",
    "inputs": [
      {
        "name": "i",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.PolicyEntry",
        "components": [
          {
            "name": "paramId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "value",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "policyCount",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "policyOf",
    "inputs": [
      {
        "name": "paramId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.PolicyEntry",
        "components": [
          {
            "name": "paramId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "value",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "priceSourceByKey",
    "inputs": [
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.PriceSourceEntry",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "venue",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "symbol",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "weight",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "baseToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "baseIsToken0",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "twapWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "twapMinWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "minLiquidity",
            "type": "uint128",
            "internalType": "uint128"
          },
          {
            "name": "maxSpotDeviationBps",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "priceSourceKeys",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "priceSourceKeysFor",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "priceSourceOf",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "venueId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAssetRegistry.PriceSourceEntry",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "venue",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "symbol",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "weight",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "baseToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "baseIsToken0",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "twapWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "twapMinWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "minLiquidity",
            "type": "uint128",
            "internalType": "uint128"
          },
          {
            "name": "maxSpotDeviationBps",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "publisherRole",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registry",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registryEpoch",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registryRootKey",
    "inputs": [
      {
        "name": "which",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "seal",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "seedEpoch",
    "inputs": [
      {
        "name": "epoch_",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "setChainTerms",
    "inputs": [
      {
        "name": "updates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.ChainTermsEntry[]",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "defaultLane",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "lanes",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "quoteWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "floatPremiumBps",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "admissionFloorUsdMicros",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "maxLegsPerIntent",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "setPolicy",
    "inputs": [
      {
        "name": "updates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.PolicyEntry[]",
        "components": [
          {
            "name": "paramId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "value",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "setSources",
    "inputs": [
      {
        "name": "protocolUpdates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.DexProtocolEntry[]",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "factory",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoter",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "router",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "positionManager",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "name": "sourceUpdates",
        "type": "tuple[]",
        "internalType": "struct FinalAssetRegistry.PriceSourceEntry[]",
        "components": [
          {
            "name": "assetId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "venueKind",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "venue",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "protocolId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "symbol",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteAsset",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "weight",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "baseToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "quoteToken",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "baseIsToken0",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "twapWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "twapMinWindowSeconds",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "minLiquidity",
            "type": "uint128",
            "internalType": "uint128"
          },
          {
            "name": "maxSpotDeviationBps",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "enabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "epoch",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "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": "ChainSet",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "ChainTermsSet",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "defaultLane",
        "type": "uint8",
        "indexed": false,
        "internalType": "uint8"
      },
      {
        "name": "lanes",
        "type": "uint16",
        "indexed": false,
        "internalType": "uint16"
      },
      {
        "name": "quoteWindowSeconds",
        "type": "uint32",
        "indexed": false,
        "internalType": "uint32"
      },
      {
        "name": "floatPremiumBps",
        "type": "uint16",
        "indexed": false,
        "internalType": "uint16"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "DexProtocolSet",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "protocolId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "EpochSeeded",
    "inputs": [
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "PolicySet",
    "inputs": [
      {
        "name": "paramId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "value",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "PriceSourceSet",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "venueId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "enabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "RegistryConfigured",
    "inputs": [
      {
        "name": "role",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "threshold",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "RootsPublished",
    "inputs": [
      {
        "name": "chainRoot",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      },
      {
        "name": "assetRoot",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "Sealed",
    "inputs": [],
    "anonymous": false
  },
  {
    "type": "error",
    "name": "AlreadySealed",
    "inputs": []
  },
  {
    "type": "error",
    "name": "AnchorAhead",
    "inputs": [
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "blockNumber",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "AnchorStale",
    "inputs": [
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "blockNumber",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "AssetNotRegistered",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSeal",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSignature",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "algorithm",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "ChainNotRegistered",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "ChainRefMismatch",
    "inputs": [
      {
        "name": "claimed",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "derived",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "EmptyBatch",
    "inputs": []
  },
  {
    "type": "error",
    "name": "IncompleteChainDescription",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidChainRole",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "role",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidDexProtocol",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "protocolId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidPriceSource",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "venueId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "LaneNotAvailable",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "defaultLane",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "lanes",
        "type": "uint16",
        "internalType": "uint16"
      }
    ]
  },
  {
    "type": "error",
    "name": "LeverageCapOutOfRange",
    "inputs": [
      {
        "name": "assetId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "maxLeveragePct",
        "type": "uint16",
        "internalType": "uint16"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAdmin",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "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": "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"
      }
    ]
  }
]

čtení kontraktu

bytecode · 27,628 bajtů

0x60a0806040526004361015610012575f80fd5b5f905f3560e01c9081630292709e14614de857508063036f9a4314614dc95780630489d03814614d37578063087a5cf414614cd357806308f0ce62146132975780630900767d14614cb95780631591c4a014614686578063178bcc93146146415780631945098e146105815780631a633473146104755780631e000aa51461454b578063200d43ad146138f557806320d4f69614610475578063210de79d146138a95780632b89089c1461388c5780633ec4cc34146138465780633fb27b85146137f057806342cde4e8146137d25780634760fd43146137675780634bcb06a91461374d5780634ec824ff146132975780635483a0b51461370a57806357d70648146136eb5780635b88f50e146132975780635eb320b8146136cd5780635f61b15c1461329757806360c29fac14610581578063622c3f21146136b057806365cea49c1461369157806368cf465e146105815780636c2c44e2146136775780636db817521461365b578063711f3db41461358c578063767c39d2146104755780637b103999146135475780637bb737bc1461352d5780637f49d69f146134f2578063831f33a1146132d75780638513e2d5146132b8578063852a52e81461329c57806388f06eaa146132975780639212a9c91461311d57806392a2e0b3146131015780639377c220146130da5780639493ba1d1461307657806394bc4e9614612d7257806395102c4214612d555780639648eca214612ac257806397b9857b14612a65578063a741113c14612a2a578063aa9239f5146129d3578063ac65bc70146128b0578063affed0e014612889578063b19f48051461284e578063b8da302a14612830578063b9a7f07614612743578063bbbcda4414612724578063bda1019e14612706578063c5dbda66146126e9578063c8cfbc17146108fd578063dc544a09146108db578063de54d429146108bd578063df46c71314610767578063e02e1bfd14610749578063e597e65914610586578063e70475a714610581578063e94a52af14610565578063eafe7a7414610547578063eb7773821461047a578063ee076a5014610475578063ee57e33c146103e8578063f3178f4314610393578063f4e885db146103755763f851a4401461034c575f80fd5b34610372578060031936011261037257546040516001600160a01b039091168152602090f35b80fd5b50346103725780600319360112610372576020600154604051908152f35b50346103725760403660031901126103725761ffff600260406020936103b76152a5565b6004358252600a86526103d56001600160401b038484209216615fa3565b8252855220015460501c16604051908152f35b503461037257806003193601126103725760405180916020601154928381520191601182527f31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68915b81811061045f5761045b8561044781870382615507565b604051918291602083526020830190615374565b0390f35b8254845260209093019260019283019201610430565b61540e565b5034610372578060031936011261037257600454610497816157ad565b8291835b8181106104eb5750506104ad826157ad565b925b8281106104cc576040516020808252819061045b90820187615374565b806104d96001928461587b565b516104e4828761587b565b52016104af565b806104f76001926157df565b90549060031b1c8652600560205260ff600c60408820015460081c1661051e575b0161049b565b610527816157df565b90549060031b1c61054161053a876159e0565b968661587b565b52610518565b50346103725780600319360112610372576020600754604051908152f35b5034610372578060031936011261037257602060405160058152f35b6153f3565b50346103725760403660031901126103725760243560043560ff82168203610745576007546105b48161609d565b928492855b83811061065257505050506105cd8161609d565b91835b828110610628578385604051918291602083016020845282518091526020604085019301915b818110610604575050500390f35b91935091602060c08261061a600194885161532b565b0194019101918493926105f6565b806106356001928461587b565b51610640828761587b565b5261064b818661587b565b50016105d0565b61065b8161580b565b90549060031b1c808852600860205260ff600560408a20015460481c161561073c578752600a60205260408720828852602052604087206002604051916106a18361547c565b8054835260018101546020840152015460ff811660408301528460ff8260081c161515918260608501526001600160401b038160101c16608085015261ffff8160501c1660a08501528261072d575b5050610702575b506001905b016105b9565b949061072682610714600194916159e0565b9761071f828b61587b565b528861587b565b50906106f7565b60ff925016161515845f6106f0565b506001906106fc565b8280fd5b50346103725780600319360112610372576020600454604051908152f35b503461037257602036600319011261037257610781615443565b6007549061078e8261604e565b918391845b828110610841575050506107a68161604e565b91835b8281106108175783856040519182916020830160208452825180915260408401602060408360051b870101940192905b8282106107e857505050500390f35b919360019193955060206108078192603f198a82030186528851615579565b96019201920185949391926107d9565b806108246001928461587b565b5161082f828761587b565b5261083a818661587b565b50016107a9565b61084a8161580b565b90549060031b1c8652600860205261086460408720615e2e565b6101008101511515806108ab575b610880575b50600101610793565b93906108a482610892600194916159e0565b9661089d828a61587b565b528761587b565b5090610877565b5060ff8360a083015116161515610872565b50346103725780600319360112610372576020600d54604051908152f35b50346103725760206108f56108ef36615315565b90616008565b604051908152f35b50346103725760a0366003190112610372576001600160401b036004351161037257366023600435011215610372576001600160401b036004356004013511610372573660246102a06004356004013502600435010111610372576024356001600160401b038111612393576109779036906004016152e5565b916001600160401b036044351161037257366023604435011215610372576001600160401b0360443560040135116103725736602460c0604435600401350260443501011161037257606435926001600160401b0384168403612393576084356001600160401b038111610745576109f39036906004016152e5565b9460025480156126da576004356004013515806126d2575b806126c4575b6126b557600354926040519760a089016001600160401b03861660208b0152608060408b015260043560040135905260c08901602460043501885b60043560040135811061252e575050601f198a82030160608b01528681526020810160208860051b830101908a928a915b8a83106124225750505060209150601f198b82030160808c01526044356004013581520198602460443501885b6044356004013581106123ab575050610ad381610b759798999a9b03601f198101835282615507565b6020815191012060405160208101915f516020616bac5f395f51905f5283524660408301523060608301527fe10634eb0bf7bd6ad00dc59a6c67fe9b09037b979405fa59137e7e530d60141460808301526001600160401b03871660a083015260c082015260c08152610b4760e082615507565b51902090600154927f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6616100565b506001600160401b03610b89818316615764565b16906001600160401b0319161760035560175491610baf6001600160401b038416615764565b67ffffffffffffffff199093166001600160401b03841617601755610bd9826004803501356159b8565b600460443501356001600160ff1b038116900361239757610c07610c18916044356004013560011b906159b8565b610c10816157ad565b6080526157ad565b90809281935b6004356004013585101561147857610c476004356102a087020160648101359060440135616008565b60246102a087026004350101350361143d57610c6e6102446102a0870260043501016159c5565b806113b0575b61139257600160ff610c916102a46102a0890260043501016159d2565b161161135957610cac6102446102a0870260043501016159c5565b8061133a575b80611326575b611308577f84e75c8576483b53128ed23c26158522388ddb0a5df8516c83887d522196f91f60446102a0870260043501013514806112e6575b6112ad576101e46102a08602600435010135151580611289575b6112695760246102a086026004350101358352600660205260ff604084205416156111e3575b6102a08502600435016024810135808552600560205260408520908155604482013560018201908155606483013560028301556084830135600383015591610d7b9060a4016159d2565b6004828101805460ff191660ff939093169290921790915560c490356102a089020190810135600583015560e48101356006830155610dbd90610104016159d2565b60ff1660ff196007830154161760078201556102a087026004350161012401610de590615f8f565b6007820190610e12919068ffffffffffffffff0082549160081b169068ffffffffffffffff001916179055565b610e276101446004356102a08a0201016159fd565b600782015461ffff60681b610e476101646004356102a08d0201016159ee565b60681b169061ffff60781b610e676101846004356102a08e0201016159ee565b60781b169260481b6cffffffff000000000000000000169067ffffffffffffffff60481b191617171760078201556102a08702600435016101a4013560088201556102a08702600435016101c401610ebe906159d2565b60098201805460ff191660ff929092169190911790556101e46004356102a089020190810135600a830155610204810135600b830155610f0190610224016159d2565b60ff1660ff19600c8301541617600c8201556102a087026004350161024401610f29906159c5565b600c8201805491151560081b61ff001669ffffffffffffffffff00199092169190911760108a901b69ffffffffffffffff000016179055610f756102846004356102a08a020101615f8f565b600c8201805467ffffffffffffffff60501b191660509290921b67ffffffffffffffff60501b16919091179055610fb76102a46004356102a08a0201016159d2565b600c8201805460ff60901b198116609084901b60ff60901b1617909155918690610fec60246004356102a08d020101356158c2565b8660805190610ffa9161587b565b528054945460028201546003830154600484015460058501546006860154600787015460088089015460098a0154600a8b0154600b909b0154604080515f516020616bcc5f395f51905f5260208201529081019d909d5260608d019f909f5260808c019990995260a08b019790975260c08a019590955260ff93841660e08a01526101008901929092526101208801528082166101408801526001600160401b0381841c811661016089015263ffffffff604883901c1661018089015261ffff606883901c81166101a08a015260789290921c9091166101c08801526101e087019390935292831661020086015261022085019390935261024084019690965284811661026084015260ff60901b19909416609093841b60ff60901b16179081901c84161515610280830152601081901c85166102a0830152605081901c9094166102c082015292901c166102e080830191909152815261115d61030082615507565b8051906020012061116e828661587b565b52611178906159e0565b938561118f6102446004356102a0850201016159c5565b6040805191151582526001600160401b039290921660208201526102a083026004350160240135917f523b72ac5fbe8982a57ea63c8ca4daf1602c22eda42d6cc14a884c0310698b8091a260010193610c1e565b600480356102a0870201602401358452600660205260408420805460ff1916600117905554600160401b8110156112555761122981600161124e93016004556004615823565b60246102a089949394026004350101359083549060031b91821b915f19901b19161790565b9055610d31565b634e487b7160e01b84526041600452602484fd5b633f4b99b160e21b8352600480356102a08702016101e401359052602483fd5b506101e46102a086026004350101358352600660205260ff60408420541615610d0b565b60448360ff8760246102a06112cb61022482850260043501016159d2565b92631e9a7d7560e01b86520260043501013560045216602452fd5b50600160ff6113006102246102a0890260043501016159d2565b161415610cf1565b602483816102a088632eb72ead60e11b845202600435010135600452fd5b5060846102a0860260043501013515610cb8565b5060ff6113526102a46102a0880260043501016159d2565b1615610cb2565b60448360ff8760246102a06113776102a482850260043501016159d2565b92631a7ce1dd60e01b86520260043501013560045216602452fd5b602483816102a088633dc2fc1160e01b845202600435010135600452fd5b5063ffffffff6113cb6101446102a0880260043501016159fd565b1615801561141e575b80156113ff575b80610c74575060ff6113f86102246102a0880260043501016159d2565b1615610c74565b5060ff6114176101c46102a0880260043501016159d2565b16156113db565b5060ff6114366101046102a0880260043501016159d2565b16156113d4565b6044838660246102a061145e60043582850201606481013590870135616008565b92631a35ac9960e01b855202600435010135600452602452fd5b919350939483915b85831015611a5e578260051b82013561013e1983360301811215611a5a576114ab9036908401615685565b9283518652600660205260ff60408720541615611a465760a084015160ec8116611a31575063ffffffff60c08501511615611a03575b63ffffffff60e085015116156119d4575b6001600160401b0388166101208501526115128451602086015190615bf0565b808752600960205260ff60408820541615611967575b80875260086020526040872092855184556020860151600185015560ff60408701511660ff60028601911660ff1982541617905560608601518051906001600160401b0382116118825781906115816003880154615d56565b601f8111611909575b50602090601f83116001146118a1578b92611896575b50508160011b915f199060031b1c19161760038501555b60808601518051906001600160401b0382116118825787929189916115df6004890154615d56565b601f8111611819575b506020908c601f8411600114611771579361172b86611725600560019d8860a099611731997fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f9e9d9960ff9d92611766575b5050600182901b915f199060031b1c19161760048201555b0198888089830151161689198b5416178a5561168f63ffffffff60c0830151168b9064ffffffff0082549160081b169064ffffffff001916179055565b60e08101518a5468ffffffff0000000000191660289190911b68ffffffff000000000016178a55610100810180518b5461012084015171ffffffffffffffffff0000000000000000001990911691151560481b69ff000000000000000000169190911760509190911b67ffffffffffffffff60501b1617909a556117128c615f5c565b61171e8460805161587b565b528b61656a565b9261587b565b526159e0565b9801519151604080519290931660ff168252151560208201526001600160401b038c1691810191909152606090a20191611480565b015190505f8061163a565b5090600489018d52808d20918d5b601f19851681106117fb57509361172b86611725600560019d8e89611731997fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f9e9d9960ff9d60a09d601f198116106117e3575b505050811b016004820155611652565b01515f1960f88460031b161c191690555f80806117d3565b8282015184558c97508d95506001909301926020928301920161177f565b828111156115e857919394509150600487018b5260208b20601f840160051c906020851061187a575b918b93918b9695938e805b83601f860160051c03821061186557505050506115e8565b8383018201558d98508e96506001018f61184d565b8c9150611842565b634e487b7160e01b8a52604160045260248afd5b015190505f806115a0565b600388018c52818c209250601f1984168c5b8181106118f157509084600195949392106118d9575b505050811b0160038501556115b7565b01515f1960f88460031b161c191690555f80806118c9565b929360206001819287860151815501950193016118b3565b8281111561158a57909150600387018b5260208b20601f840160051c60208510611960575b808594939201908d805b82601f860160051c038210611950575050505061158a565b818401558695506001018e611938565b508b61192e565b808752600960205260408720805460ff19166001179055600754600160401b8110156119c0576119b96119a38260018594016007556007615823565b819391549060031b91821b915f19901b19161790565b9055611528565b634e487b7160e01b88526041600452602488fd5b60a0840151600216156119f55763ffffffff610bb85b1660e08501526114f2565b63ffffffff6201d4c06119ea565b60a084015160021615611a245763ffffffff6103e85b1660c08501526114e1565b63ffffffff612710611a19565b6376cbaf6d60e01b875260ff16600452602486fd5b8351633f4b99b160e21b8752600452602486fd5b8580fd5b868585815b60443560040135811015611e965760c081026044350160c06023198236030112611e925760405190611a948261547c565b6024810135825260448101356020830197818952611ab460648401615453565b9260408501938452611ac860848201615548565b9260608601938452611aef60c4611ae160a485016152d1565b936080890194855201615539565b9060a087019182528952600660205260ff60408a20541615611e7e5785518952600960205260ff60408a20541615611e6a57845160f38116611e55575061ffff81511680151580611e40575b611e21575090611c8292916001600160401b038b16825286518a52600a60205260408a208c518b52602052611bf660408b208d60028a519182845551928360018201550160ff808b51161660ff19825416178155611baf89511515829061ff00825491151560081b169061ff001916179055565b8551815469ffffffffffffffff0000191660109190911b69ffffffffffffffff0000161781558451815461ffff60501b191660509190911b61ffff60501b16179055615c10565b611c028460805161587b565b528651918c519160ff8851169161ffff6001600160401b0389511515925116925116926040519460208601965f516020616bcc5f395f51905f528852600260408801526060870152608086015260a085015260c084015260e08301526101008201526101008152611c7561012082615507565b51902061172b828961587b565b83519851835183516040805160ff909316835290151560208301526001600160401b038b1690820152919990917ffdf8afb51a6b8e68fa9d9946922368e4a2e54a69963cf7bcfcdff38235916e7990606090a35115159081611e13575b50611cee575b50600101611a63565b80518452600860205260408420600581019081546103e863ffffffff8260081c1611908115611dfd575b50611d25575b5050611ce5565b815471ffffffffffffffff00ffffffffffffffff001916605088901b67ffffffffffffffff60501b1617660bb8000003e8001782559196600193927fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f918891611dbf9190611db590611d978d51615f5c565b611da38460805161587b565b52611daf8d5191615e2e565b9061656a565b61172b828a61587b565b985192546040805160ff808416825260489390931c909216151560208301526001600160401b039290921691810191909152606090a2908680611d1e565b610bb8915063ffffffff9060281c161189611d18565b600491505116151587611cdf565b86518c516368adc18560e01b8c52600491909152602452604452606489fd5b506064811080611b3b57506101f48111611b3b565b6376cbaf6d60e01b8a5260ff16600452602489fd5b85516326ef68cf60e01b8a52600452602489fd5b8a51633f4b99b160e21b8a52600452602489fd5b8380fd5b50608051849052928352917f000000000000000000000000617620cabf90b3ceabe00d8fb83626bdf55e3bd96001600160a01b0316908390823b156123935781611ef7916040518093819263abf1570d60e01b83526080516004840161588f565b038183875af180156121ae5761237e575b50600454611f15816157ad565b8291835b81811061221857505090611f2c91616985565b9182601555600754611f3d816157ad565b8391845b8181106121b957505090611f5491616985565b8060165560609360405191611f698684615507565b60028352601f198601918236602086013760405192611f888885615507565b6002845236602085013760405160208101907fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f825287604082015260408152611fd18982615507565b519020611fdd8561585e565b5260405160208101917f205e4a5b42cbb22c56a771766b971165a627b616bd23460c8e002faad102d0918352876040830152888201526001600160401b03891660808201526080815261203160a082615507565b51902061203d8361585e565b5260405160208101907fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f8252600160408201526040815261207e8882615507565b51902061208a8461586b565b5260405160208101917f205e4a5b42cbb22c56a771766b971165a627b616bd23460c8e002faad102d091835260016040830152878201526001600160401b0388166080820152608081526120df60a082615507565b5190206120eb8261586b565b52823b15611e9257612142926121308580946040519687958694859363abf1570d60e01b85526005600486015260016024860152608060448601526084850190615374565b83810360031901606485015290615374565b03925af180156121ae57612199575b50507ff95ac4707234b4ef5620ff790985a37679e4c7e0b0c603f110ae9bcc65b0cf5c916001600160401b03601554916016546040519384526020840152166040820152a180f35b816121a391615507565b610745578284612151565b6040513d84823e3d90fd5b60019192939495506121ca8161580b565b90549060031b1c895260086020526121e460408a20615e2e565b61010081015115612212576121f890615ec8565b61220461053a876159e0565b525b01908794939291611f41565b50612206565b60019192939450612228816157df565b90549060031b1c8852600560205260408820600c60405191612249836154b3565b80548352848101546020840152600281015460408401526003810154606084015260ff6004820154166080840152600581015460a0840152600681015460c084015261ffff600782015460ff811660e08601526001600160401b038160081c1661010086015263ffffffff8160481c16610120860152818160681c1661014086015260781c16610160840152600881015461018084015260ff6009820154166101a0840152600a8101546101c0840152600b8101546101e0840152015460ff811661020083015260ff808260081c16159182156102208501526001600160401b038160101c166102408501526001600160401b038160501c1661026085015260901c166102808301526123785761235f90615c4a565b61236b61053a876159e0565b525b019086939291611f19565b5061236d565b8161238891615507565b610745578284611f08565b5080fd5b634e487b7160e01b82526011600452602482fd5b909a60c08060019261ffff8f60a081612412923585526020810135602086015260ff6123d960408301615453565b1660408601526123eb60608201615548565b151560608601526001600160401b03612406608083016152d1565b16608086015201615539565b1660a0820152019c019101610aaa565b909192601f1983820301825261013e198d3603018535121561252a576020806001928f8835019081358152828201358382015260ff61246360408401615453565b1660408201526101206001600160401b03612517826124b961249e61248b6060890189615cba565b61014060608a0152610140890191615ceb565b6124ab6080890189615cba565b9088830360808a0152615ceb565b9560ff6124c860a08301615453565b1660a087015263ffffffff6124df60c08301615528565b1660c087015263ffffffff6124f660e08301615528565b1660e08701526125096101008201615548565b1515610100870152016152d1565b1691015296019201930191909392610a7d565b8b80fd5b90916102a0806001928535815260208601356020820152604086013560408201526060860135606082015260ff61256760808801615453565b16608082015260a086013560a082015260c086013560c082015260ff61258f60e08801615453565b1660e08201526001600160401b036125aa61010088016152d1565b1661010082015263ffffffff6125c36101208801615528565b1661012082015261ffff6125da6101408801615539565b1661014082015261ffff6125f16101608801615539565b1661016082015261018086013561018082015260ff6126136101a08801615453565b166101a08201526101c08601356101c08201526101e08601356101e082015260ff6126416102008801615453565b166102008201526126556102208701615548565b15156102208201526001600160401b0361267261024088016152d1565b166102408201526001600160401b0361268e61026088016152d1565b1661026082015260ff6126a46102808801615453565b166102808201520193019101610a4c565b63c2e5347d60e01b8552600485fd5b506044356004013515610a11565b508315610a0b565b63d311bc3960e01b8552600485fd5b503461037257806003193601126103725760206040516101f48152f35b50346103725780600319360112610372576020601554604051908152f35b50346103725760203660031901126103725760206108f5600435615f5c565b503461037257602036600319011261037257600435612760615b66565b50600d5481101561281c577fd7b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb50154600382901b1c8152600b602052604090819020905161045b916001600160401b03906002906127bc84615461565b8054845260018101546020850152015460ff81161515604084015260081c1660608201526040519182918291909160606001600160401b038160808401958051855260208101516020860152604081015115156040860152015116910152565b634e487b7160e01b82526032600452602482fd5b503461037257806003193601126103725760206040516201d4c08152f35b503461037257806003193601126103725760206040517f3d07e5a0f372bb3f92469df7a6dcb4a49539b8707a06a699d3727c07dbbb3a4b8152f35b503461037257806003193601126103725760206001600160401b0360035416604051908152f35b5034610372576020366003190112610372576128ca615975565b506004358152600e602052604090206040516128e581615497565b81549182825260010154906020810160ff83168152604082018360081c61ffff168152606083018460181c63ffffffff16815260808401908560381c61ffff16825260a08501928660481c6001600160401b0316845260c08601948760881c61ffff16865260e08701968860981c60ff1615158852610100019760a01c6001600160401b031688526040519889525160ff1660208901525161ffff1660408801525163ffffffff1660608701525161ffff166080860152516001600160401b031660a08501525161ffff1660c084015251151560e0830152516001600160401b031661010082015261012090f35b503461037257602036600319011261037257612a16604061045b926129f6615d0b565b50612a0260043561580b565b90549060031b1c8152600860205220615e2e565b604051918291602083526020830190615579565b503461037257602036600319011261037257600435906001600160401b0382116103725760206108f5612a603660048601615685565b615ec8565b50346103725760203660031901126103725760043590612a83615d0b565b50818152600960205260ff60408220541615612ab05760408161045b93612a169352600860205220615e2e565b60249163082cdf1f60e11b8252600452fd5b50346103725760203660031901126103725760043590612ae0615a0e565b50818152600660205260ff60408220541615612d43578061045b926040925260056020522060ff600c60405192612b16846154b3565b80548452600181015460208501526002810154604085015260038101546060850152826004820154166080850152600581015460a0850152600681015460c085015261ffff600782015484811660e08701526001600160401b038160081c1661010087015263ffffffff8160481c16610120870152818160681c1661014087015260781c166101608501526008810154610180850152826009820154166101a0850152600a8101546101c0850152600b8101546101e08501520154818116610200840152818160081c1615156102208401526001600160401b038160101c166102408401526001600160401b038160501c1661026084015260901c166102808201526040519182918291909161028060ff816102a08401958051855260208101516020860152604081015160408601526060810151606086015282608082015116608086015260a081015160a086015260c081015160c08601528260e08201511660e08601526001600160401b036101008201511661010086015263ffffffff6101208201511661012086015261ffff6101408201511661014086015261ffff61016082015116610160860152610180810151610180860152826101a0820151166101a08601526101c08101516101c08601526101e08101516101e0860152826102008201511661020086015261022081015115156102208601526001600160401b03610240820151166102408601526001600160401b0361026082015116610260860152015116910152565b602491632a80afbd60e21b8252600452fd5b50346103725780600319360112610372576020604051610bb88152f35b503461037257608036600319011261037257600435602435612d926152bb565b6064356001600160401b03811161307257612db19036906004016152e5565b85546001600160a01b03163303612ebc575b50505080612e07575b816040917f40bd951b186ab08230e53414aeaafc99b557bb646ef875822965f0e5a8c32ca5936001558060025582519182526020820152a180f35b60405163342f616360e01b8152600481018390526020816024817f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa66001600160a01b03165afa908115612eb1578491612e7f575b50818110612e695750612dcc565b633770da3360e11b845260045260245250604490fd5b90506020813d602011612ea9575b81612e9a60209383615507565b81010312611e9257515f612e5b565b3d9150612e8d565b6040513d86823e3d90fd5b60408051602081018781528183018790529181527f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa66001600160a01b0316939190612f08606082615507565b519020833b1561306e57908288959493926001600160401b03604051966322f3f44760e11b885260848801927f3d07e5a0f372bb3f92469df7a6dcb4a49539b8707a06a699d3727c07dbbb3a4b60048a01526024890152166044870152608060648701525260a4840160a48360051b86010192828790607e19813603015b838310612fc4575050505050508391838381809403925af180156121ae57612faf575b80612dc3565b81612fb991615507565b61074557825f612fa9565b9193959092949697985060a31989820301865286358281121561306a57830180359160018060a01b038316809303613066576130526020928260019585945260ff613010858401615453565b16848201526130446130396130286040850185615cba565b608060408601526080850191615ceb565b926060810190615cba565b916060818503910152615ceb565b98019601930190918b989796959492612f86565b8d80fd5b8c80fd5b8780fd5b8480fd5b5034610372576020366003190112610372576004358152601460205260408120604051918260208354918281520192825260208220915b8181106130c45761045b8561044781870382615507565b82548452602090930192600192830192016130ad565b503461037257806003193601126103725760206001600160401b0360175416604051908152f35b5034610372578060031936011261037257602060405160048152f35b5034610372576102a0366003190112610372576040519061313d826154b3565b600435825260243560208301526044356040830152606435606083015260843560ff8116810361239357608083015260a43560a083015260c43560c083015260e43560ff811681036123935760e0830152610104356001600160401b0381168103612393576101008301526101243563ffffffff81168103612393576101208301526101443561ffff81168103612393576101408301526101643561ffff8116810361239357610160830152610184356101808301526101a43560ff81168103612393576101a08301526101c4356101c08301526101e4356101e08301526102043560ff811681036123935761020083015261022435801515810361239357610220830152610244356001600160401b038116810361239357610240830152610264356001600160401b038116810361239357610260830152610284359060ff821682036103725760206108f58484610280820152615c4a565b6153a7565b5034610372578060031936011261037257602060405160648152f35b50346103725760203660031901126103725760206108f5600435615fa3565b503461037257602036600319011261037257604061045b916132f7615aa5565b506004358152600f602052206001600160401b03600c6040519261331a846154cf565b80548452600181015460208501526002810154604085015260ff600382015416606085015260048101546080850152600581015460a0850152600681015460c0850152600781015460e085015261ffff6008820154166101008501526009810154610120850152600a81015461014085015260ff600b820154818116151561016087015263ffffffff8160081c1661018087015263ffffffff8160281c166101a08701526001600160801b038160481c166101c087015261ffff8160c81c166101e087015260d81c161515610200850152015416610220820152604051918291829190916102206001600160401b038161024084019580518552602081015160208601526040810151604086015260ff60608201511660608601526080810151608086015260a081015160a086015260c081015160c086015260e081015160e086015261ffff61010082015116610100860152610120810151610120860152610140810151610140860152610160810151151561016086015263ffffffff6101808201511661018086015263ffffffff6101a0820151166101a08601526001600160801b036101c0820151166101c086015261ffff6101e0820151166101e08601526102008101511515610200860152015116910152565b503461037257806003193601126103725760206040517f84e75c8576483b53128ed23c26158522388ddb0a5df8516c83887d522196f91f8152f35b50346103725760206108f561354136615315565b90615c10565b50346103725780600319360112610372576040517f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa66001600160a01b03168152602090f35b5034610372576020366003190112610372576004356001600160401b0381168091036123935781546001600160a01b03163303613648576017546001600160401b0381161580159061363d575b8015613632575b6136235767ffffffffffffffff191681176017556040519081527f1b0319a7c1b77359b02ef03a86a6e1677b3d55bc74afbe201986efaef1e8b4b590602090a180f35b63dc63d81f60e01b8352600483fd5b5060075415156135e0565b5060045415156135d9565b630bd4212160e11b825233600452602482fd5b5034610372578060031936011261037257602090604051908152f35b50346103725760206108f561368b36615315565b90615bf0565b50346103725760203660031901126103725760206108f5600435615bbd565b503461037257806003193601126103725760206040516127108152f35b50346103725780600319360112610372576020601654604051908152f35b50346103725760203660031901126103725760206108f5600435615b8a565b503461037257602036600319011261037257604061045b9161372a615b66565b506004358152600b602052206001600160401b036002604051926127bc84615461565b50346103725760206108f561376136615429565b91615b27565b503461037257602036600319011261037257602090613784615443565b905060405160ff838201927fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f8452166040820152604081526137c7606082615507565b519020604051908152f35b50346103725780600319360112610372576020600254604051908152f35b503461037257806003193601126103725780546001600160a01b0381163303613648576001600160a01b03191681557f1b2d71eb44f882534bf4e86f940c56ccc869ffb927e2bab86561de93950c22168180a180f35b503461037257604061045b9161386d61385e36615429565b91613867615aa5565b50615b27565b8152600f602052206001600160401b03600c6040519261331a846154cf565b503461037257806003193601126103725760206040516103e88152f35b503461037257602036600319011261037257604061045b916138c9615a0e565b506138d56004356157df565b90549060031b1c815260056020522060ff600c60405192612b16846154b3565b5034610372576080366003190112610372576004356001600160401b038111612393576139269036906004016153c2565b6001600160401b0360243511610745573660236024350112156107455760243560040135916001600160401b038311611e925736602461024085028135010111611e92576139726152bb565b6064356001600160401b038111611a5a576139919036906004016152e5565b9091600254801561453c57851580614534575b61452557600354936001600160401b038516936040518860808201876020840152606060408401525260a0810188908c5b8b81106144975750828103601f190160608401528b8152602001905060248035018c5b8c811061434f57505092613a9b959282613a286001600160401b03999794613a959703601f198101835282615507565b6020815191012060405160208101915f516020616bac5f395f51905f5283524660408301523060608301527f5596fa6720176cbfa2a11ee0d5be50e2016480c70c477d3273492663856b2d0b60808301528a871660a083015260c082015260c08152610b4760e082615507565b50615764565b16906001600160401b0319161760035560175491613ac16001600160401b038416615764565b67ffffffffffffffff199093166001600160401b03841617601755613ae684826159b8565b91613af9613af3846157ad565b936157ad565b908692875b8181106140a35750505085915b8686841015614025575060246102408402813501019283358852600960205260ff6040892054161561401157908791613b438561637b565b613b60602435610240830201606481013590604401358735615b27565b91828452601060205260ff60408520541615613fb2575b828452600f6020526040842086358155602435610240840201604481013560018301556064810135600283015590919060ff90613bb6906084016159d2565b60038401805460ff19169290911691909117905560243561024084020160a4810135600484015560c4810135600584015560e48101356006840155610104810135600784015561ffff90613c0d90610124016159ee565b60088401805461ffff1916929091169190911790556024356102408402016101448101356009840155610164810135600a840155613c6790613c5290610184016159c5565b600b84019060ff801983541691151516179055565b613c9f613c7f6101a4610240860260243501016159fd565b600b84019064ffffffff0082549160081b169064ffffffff001916179055565b613cdf613cb76101c4610240860260243501016159fd565b600b84019068ffffffff000000000082549160281b169068ffffffff00000000001916179055565b6024356102408402016101e401356001600160801b03811690036130725760019382600b613f3f94015461ffff60c81b613d24610204610240890260243501016159ee565b60c81b1660ff60d81b613d426024356102408a0201610224016159c5565b7fffffffff00000000000000000000000000000000000000ffffffffffffffffff9093166024356102408a02016101e4013560481b78ffffffffffffffffffffffffffffffff00000000000000000016179190911791151560d81b1617600b820155600c8101805467ffffffffffffffff19166001600160401b038d1617815591613dcd848c61587b565b52613f325f516020616bcc5f395f51905f52602083549489850154600286015460ff600388015416600488015460058901549060068a01549260078b01549461ffff60088d0154169660806040519e8f908c82019d8e52600460408301526060820152015260a08d015260c08c015260e08b01526101008a01526101208901526101408801526101608701526101608652613e6a61018087615507565b60098501549060ff6001600160401b03600b600a890154980154925416916040519785890194855260408901528181161515606089015263ffffffff8160081c16608089015263ffffffff8160281c1660a08901526001600160801b038160481c1660c089015261ffff8160c81c1660e089015260d81c1615156101008701526101208601526101208552613f0161014086615507565b60405194859383850197518091895e840190838201908d8252519283915e010189815203601f198101835282615507565b51902061172b828861587b565b94613f55610224610240840260243501016159c5565b6040805191151582526001600160401b038a166020830152602435610240850201606481013593604490910135929035917f9738e55ce3503cada06dd3d3338d42623309a94afb5495119306f4a19a92350b91a401929050613b0b565b9091809352601060205260408920600160ff19825416179055601154600160401b811015611882579083613ff46119a38460018e989796016011556011615823565b905585358452601260205261400c8360408620615838565b613b77565b6326ef68cf60e01b88528335600452602488fd5b807f000000000000000000000000617620cabf90b3ceabe00d8fb83626bdf55e3bd96001600160a01b03168685823b1561409e5761407c9284928360405180968195829463abf1570d60e01b84526004840161588f565b03925af180156121ae5761408d5750f35b8161409791615507565b6103725780f35b505050fd5b6140ae818385615964565b9485358a52600660205260ff60408b2054161561433b576020860135158015614306575b6142ea579061426560019287358c52601360205260408c2060208901358d5260205260408c206001600160401b03600782015460081c16156142ca575b8835815560208901358582015560ff61412a60408b016159d2565b1660ff19600283015416176002820155606089013560038201556080890135600482015560a0890135600582015560c0890135600682015561418661417160e08b016159c5565b600783019060ff801983541691151516179055565b60078101805468ffffffffffffffff00191660088d901b68ffffffffffffffff00161790556141ba60208a01358a3561592a565b6141c4838c61587b565b528054906001600160401b03868201549160ff600282015416906003810154600482015460058301549160076006850154940154946040519760208901995f516020616bcc5f395f51905f528b52600560408b015260608a0152608089015260a088015260c087015260e086015261010085015261012084015260ff8116151561014084015260081c166101608201526101608152611c7561018082615507565b9561427260e082016159c5565b7f4b9c1fac36a2aeaf69e62fe75b58c9e69ec27b26ca89e6cde77296d160d452da604051806142c18d6020870135963595839092916001600160401b0360209160408401951515845216910152565b0390a301613afe565b88358d5260146020526142e58d604060208c01359120615838565b61410f565b638940a78560e01b8a528535600452602086013560245260448afd5b5061431360e087016159c5565b80156140d2575060ff614328604088016159d2565b1615806140d257506060860135156140d2565b633f4b99b160e21b8a52853560045260248afd5b909182358152602083013560208201526040830135604082015260ff61437760608501615453565b1660608201526080830135608082015260a083013560a082015260c083013560c082015260e083013560e082015261ffff6143b56101008501615539565b166101008201526101208301356101208201526101408301356101408201526143e16101608401615548565b151561016082015263ffffffff6143fb6101808501615528565b1661018082015263ffffffff6144146101a08501615528565b166101a08201526101c0830135906001600160801b03821680920361449357610240816001936101c08394015261ffff6144516101e08801615539565b166101e08201526144656102008701615548565b15156102008201526001600160401b0361448261022088016152d1565b1661022082015201930191016139f8565b8e80fd5b9061012080600192853581526020860135602082015260ff6144bb60408801615453565b166040820152606086013560608201526080860135608082015260a086013560a082015260c086013560c08201526144f560e08701615548565b151560e08201526001600160401b0361451161010088016152d1565b1661010082015201930191019190916139d5565b63c2e5347d60e01b8852600488fd5b5086156139a4565b63d311bc3960e01b8852600488fd5b50346103725760406101209161456036615315565b90614569615975565b5082526013602052828220908252602052206001600160401b036040519161459083615497565b8054928381526001820154916020820192835260ff600282015416604083019081526003820154606084019081526004830154906080850191825260ff60058501549360a08701948552600760068701549660c089019788520154978961010060e08a0199858c1615158b52019960081c1689526040519a8b525160208b0152511660408901525160608801525160808701525160a08601525160c085015251151560e08401525116610100820152f35b50346103725780600319360112610372576040517f000000000000000000000000617620cabf90b3ceabe00d8fb83626bdf55e3bd96001600160a01b03168152602090f35b5034610372576060366003190112610372576004356001600160401b038111612393576146b79036906004016153c2565b6146c29291926152a5565b926044356001600160401b038111611e92576146e29036906004016152e5565b9490600254918215614caa578415614c9b57600395949554926001600160401b038416926040519860208a01898b60408060608301928a865201525260808b019a888a5b8c8110614bd1575050926147c195926001600160401b03979592856147588f97613a959803601f198101835282615507565b51902060405160208101915f516020616bac5f395f51905f5283524660408301523060608301527f7d6f52ebac641da43fc11d5911cdb8aabf4931733ac493d1f5540628174e88d360808301528a871660a083015260c082015260c08152610b4760e082615507565b16906001600160401b03191617600355601754906001600160401b036147e8818416615764565b1680926001600160401b03191617601755614802846157ad565b9061480c856157ad565b9284955b80871061486f5785807f000000000000000000000000617620cabf90b3ceabe00d8fb83626bdf55e3bd96001600160a01b03168688823b1561409e5761407c9284928360405180968195829463abf1570d60e01b84526004840161588f565b61487a878285615964565b9661012088360312614bcd576040519761489389615497565b61491c610100823592838c526148ab60208201615453565b60208d01526148bc60408201615539565b60408d01526148cd60608201615528565b60608d01526148de60808201615539565b60808d01526148ef60a082016152d1565b60a08d015261490060c08201615539565b60c08d015261491160e08201615548565b60e08d0152016152d1565b6101008a01528752600660205260ff60408820541615614bb957604088015160ff60208a01511661ffff6001821b83161615614b9b57505060019088846101008a9b015280518952600e602052614a5760408a20848351918281550160ff806020860151161660ff19825416178155604084015181549066ffffffff000000606087015160181b169068ffff00000000000000608088015160381b169167ffffffffffffffff60481b60a089015160481b1661ffff60881b60c08a015160881b169260e08a0151151560981b946001600160401b0360a01b6101008c015160a01b16966001600160401b0360a01b199460ff60981b199361ffff60881b199262ffff0067ffffffffffffffff60481b199260081b169068ffffffffffffffff0019161716171617161716179060ff60981b1617179055615bbd565b614a61838961587b565b52805160ff60208301511661ffff60408401511663ffffffff60608501511661ffff6080860151166001600160401b0360a08701511661ffff60c0880151169160e08801511515936001600160401b036101008a015116956040519760208901995f516020616bcc5f395f51905f528b52600760408b015260608a0152608089015260a088015260c087015260e08601526101008501526101208401526101408301526101608201526101608152614b1b61018082615507565b519020614b28838a61587b565b527ffb325cc55af6e4500b502905b39f0679ffc3ea5f77a1534709444fdb64a99f0a60a082519260ff6020820151169061ffff6040820151169061ffff608063ffffffff6060840151169201511691604051938452602084015260408301526060820152876080820152a2019650614810565b8951633ecb481360e21b8a5260045260245261ffff16604452606487fd5b8751633f4b99b160e21b8852600452602487fd5b8680fd5b909c610120808f929d9e9d6001600160401b03614c876101008660019735855260ff614bff60208301615453565b16602086015261ffff614c1460408301615539565b16604086015263ffffffff614c2b60608301615528565b16606086015261ffff614c4060808301615539565b16608086015283614c5360a083016152d1565b1660a086015261ffff614c6860c08301615539565b1660c0860152614c7a60e08201615548565b151560e0860152016152d1565b16610100820152019e0191019b9a9b614726565b63c2e5347d60e01b8652600486fd5b63d311bc3960e01b8652600486fd5b50346103725760206108f5614ccd36615315565b9061592a565b5034610372576020366003190112610372576004358152601260205260408120604051918260208354918281520192825260208220915b818110614d215761045b8561044781870382615507565b8254845260209093019260019283019201614d0a565b503461037257604060c091614d4b36615315565b90614d546158fa565b508252600a6020528282209082526020522061ffff600260405192614d788461547c565b8054845260018101546020850152015460ff8116604084015260ff8160081c16151560608401526001600160401b038160101c16608084015260501c1660a0820152614dc7604051809261532b565bf35b50346103725760203660031901126103725760206108f56004356158c2565b9050346151b45760603660031901126151b4576004356001600160401b0381116151b457366023820112156151b4578060040135916001600160401b0383116151b4573660248460071b840101116151b457614e426152a5565b906044356001600160401b0381116151b457614e629036906004016152e5565b929060025490811561529657861561528757600354946001600160401b0386169460208101908960608201888452604080840152526080810160248a015f5b8c811061523a575050926001600160401b0397959282614ed2613a959794614f3b9a9703601f198101835282615507565b51902060405160208101915f516020616bac5f395f51905f5283524660408301523060608301527fe3b31cc24d29add9661dc3a1f58e781f69070e12dc384bcd05cb5622a2f1c57b60808301528a871660a083015260c082015260c08152610b4760e082615507565b16906001600160401b031916176003556017546001600160401b03614f61818316615764565b1680916001600160401b03191617601755614f7b836157ad565b91614f85846157ad565b945f5b858110156151b8578060071b830190608060231983360301126151b457604051614fb181615461565b602483013592838252602082019160448201358352614fe56084614fd760648501615548565b9360408401948552016152d1565b5060608101948886525f52600c6020528a8960ff60405f20541615615120575b917fed4d5e005c9676e1dcabfeb18ab560394b1c30a4409c368d6b8e5b760951353593916151008760019960609685515f52600b60205261509e836117258c615099600260405f208c5193848255516001820155016150738d511515829060ff801983541691151516179055565b8751815468ffffffffffffffff00191660089190911b68ffffffffffffffff0016179055565b615b8a565b528451908951906001600160401b0388511515915116906040519260208401945f516020616bcc5f395f51905f528652600360408601528b850152608084015260a083015260c082015260c081526150f760e082615507565b5190209261587b565b52519351905115156040519182526020820152886040820152a201614f88565b50509081515f52600c60205260405f20600160ff19825416179055815190600d5492600160401b8410156151a0578c615100876001998e60609761518f6119a38b8f7fed4d5e005c9676e1dcabfeb18ab560394b1c30a4409c368d6b8e5b76095135359d01600d55600d615823565b905594965099505050919350615005565b634e487b7160e01b5f52604160045260245ffd5b5f80fd5b507f000000000000000000000000617620cabf90b3ceabe00d8fb83626bdf55e3bd96001600160a01b03168487823b156151b45761520f925f928360405180968195829463abf1570d60e01b84526004840161588f565b03925af1801561522f57615221575080f35b61522d91505f90615507565b005b6040513d5f823e3d90fd5b9091608080600192853581526020860135602082015261525c60408701615548565b151560408201526001600160401b03615277606088016152d1565b1660608201520193019101614ea1565b63c2e5347d60e01b5f5260045ffd5b63d311bc3960e01b5f5260045ffd5b602435906001600160401b03821682036151b457565b604435906001600160401b03821682036151b457565b35906001600160401b03821682036151b457565b9181601f840112156151b4578235916001600160401b0383116151b4576020808501948460051b0101116151b457565b60409060031901126151b4576004359060243590565b61ffff60a08092805185526020810151602086015260ff60408201511660408601526060810151151560608601526001600160401b036080820151166080860152015116910152565b90602080835192838152019201905f5b8181106153915750505090565b8251845260209384019390920191600101615384565b346151b4575f3660031901126151b457602060405160018152f35b9181601f840112156151b4578235916001600160401b0383116151b45760208085019461012085020101116151b457565b346151b4575f3660031901126151b457602060405160028152f35b346151b4575f3660031901126151b457602060405160038152f35b60609060031901126151b457600435906024359060443590565b6004359060ff821682036151b457565b359060ff821682036151b457565b608081019081106001600160401b038211176151a057604052565b60c081019081106001600160401b038211176151a057604052565b61012081019081106001600160401b038211176151a057604052565b6102a081019081106001600160401b038211176151a057604052565b61024081019081106001600160401b038211176151a057604052565b61014081019081106001600160401b038211176151a057604052565b90601f801991011681019081106001600160401b038211176151a057604052565b359063ffffffff821682036151b457565b359061ffff821682036151b457565b359081151582036151b457565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90815181526020820151602082015260ff60408301511660408201526101206001600160401b03816155d16155bf60608701516101406060880152610140870190615555565b60808701518682036080880152615555565b9460ff60a08201511660a086015263ffffffff60c08201511660c086015263ffffffff60e08201511660e0860152610100810151151561010086015201511691015290565b6001600160401b0381116151a057601f01601f191660200190565b92919261563d82615616565b9161564b6040519384615507565b8294818452818301116151b4578281602093845f960137010152565b9080601f830112156151b45781602061568293359101615631565b90565b919091610140818403126151b457604051906156a0826154eb565b819381358352602082013560208401526156bc60408301615453565b604084015260608201356001600160401b0381116151b457816156e0918401615667565b60608401526080820135906001600160401b0382116151b4578261570e610120949261575f94869401615667565b608086015261571f60a08201615453565b60a086015261573060c08201615528565b60c086015261574160e08201615528565b60e08601526157536101008201615548565b610100860152016152d1565b910152565b6001600160401b036001911601906001600160401b03821161578257565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116151a05760051b60200190565b906157b782615796565b6157c46040519182615507565b82815280926157d5601f1991615796565b0190602036910137565b6004548110156157f75760045f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b6007548110156157f75760075f5260205f2001905f90565b80548210156157f7575f5260205f2001905f90565b805490600160401b8210156151a057816119a391600161585a94018155615823565b9055565b8051156157f75760200190565b8051600110156157f75760400190565b80518210156157f75760209160051b010190565b90916158b4615682936006845260016020850152608060408501526080840190615374565b916060818403910152615374565b60405160208101915f516020616b8c5f395f51905f5283525f60408301526060820152606081526158f4608082615507565b51902090565b604051906159078261547c565b5f60a0838281528260208201528260408201528260608201528260808201520152565b906040519060208201925f516020616b8c5f395f51905f5284526005604084015260608301526080820152608081526158f460a082615507565b91908110156157f757610120020190565b6040519061598282615497565b5f610100838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201520152565b9190820180921161578257565b3580151581036151b45790565b3560ff811681036151b45790565b5f1981146157825760010190565b3561ffff811681036151b45790565b3563ffffffff811681036151b45790565b60405190615a1b826154b3565b5f610280838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e0820152826102008201528261022082015282610240820152826102608201520152565b60405190615ab2826154cf565b5f610220838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e0820152826102008201520152565b916040519160208301935f516020616b8c5f395f51905f528552600460408501526060840152608083015260a082015260a081526158f460c082615507565b60405190615b7382615461565b5f6060838281528260208201528260408201520152565b60405160208101915f516020616b8c5f395f51905f528352600360408301526060820152606081526158f4608082615507565b60405160208101915f516020616b8c5f395f51905f528352600760408301526060820152606081526158f4608082615507565b9060405190602082019283526040820152604081526158f4606082615507565b906040519060208201925f516020616b8c5f395f51905f5284526002604084015260608301526080820152608081526158f460a082615507565b8051906060810151906001600160401b0361024060ff60808401511692015116906040519260208401947f2d9686edff91aeaad85c9917be9d5ee284127481a229d9857b748073fba5b1c0865260408501526060840152608083015260a082015260a081526158f460c082615507565b9035601e19823603018112156151b45701602081359101916001600160401b0382116151b45781360383136151b457565b908060209392818452848401375f828201840152601f01601f1916010190565b60405190615d18826154eb565b5f61012083828152826020820152826040820152606080820152606060808201528260a08201528260c08201528260e0820152826101008201520152565b90600182811c92168015615d84575b6020831014615d7057565b634e487b7160e01b5f52602260045260245ffd5b91607f1691615d65565b9060405191825f825492615da184615d56565b8084529360018116908115615e0c5750600114615dc8575b50615dc692500383615507565b565b90505f9291925260205f20905f915b818310615df0575050906020615dc6928201015f615db9565b6020919350806001915483858901015201910190918492615dd7565b905060209250615dc694915060ff191682840152151560051b8201015f615db9565b90604051615e3b816154eb565b6101206001600160401b0360058395805485526001810154602086015260ff6002820154166040860152615e7160038201615d8e565b6060860152615e8260048201615d8e565b6080860152015460ff811660a085015263ffffffff8160081c1660c085015263ffffffff8160281c1660e085015260ff8160481c16151561010085015260501c16910152565b80519060208101519060ff604082015116906060810151602081519101206001600160401b0361012060808401516020815191012093015116926040519460208601967f8db2525bc769e56adb6fd2402771f48c08c4503c9dc0bc507a776eaca65d700f885260408701526060860152608085015260a084015260c083015260e082015260e081526158f461010082615507565b60405160208101915f516020616b8c5f395f51905f528352600160408301526060820152606081526158f4608082615507565b356001600160401b03811681036151b45790565b60405160208101917faf7a3b9335082a3b4f22ddf585b52ecc40819755789412bb4253e79b45db1ab383527f84e75c8576483b53128ed23c26158522388ddb0a5df8516c83887d522196f91f60408301526060820152606081526158f4608082615507565b906040519060208201927faf7a3b9335082a3b4f22ddf585b52ecc40819755789412bb4253e79b45db1ab3845260408301526060820152606081526158f4608082615507565b9061605882615796565b6160656040519182615507565b8281528092616076601f1991615796565b01905f5b82811061608657505050565b602090616091615d0b565b8282850101520161607a565b906160a782615796565b6160b46040519182615507565b82815280926160c5601f1991615796565b01905f5b8281106160d557505050565b6020906160e06158fa565b828285010152016160c9565b356001600160a01b03811681036151b45790565b93919594965f95881561636c576001600160401b0316438111616356578043034381116157825761025810616340575060405193602085015260208452616148604085615507565b5f975f965b88881015616316578760051b840135607e19853603018112156151b4578401996161768b6160ec565b6001600160a01b0391821691168110156162ea57506161948a6160ec565b9961619e816160ec565b604051632e4bfa5160e11b81526001600160a01b0391821660048201526024810188905290602090829060449082908d165afa90811561522f575f916162b0575b5015616289576020810190600460ff6161f7846159d2565b16036162565761620888828b6166cf565b1561622257505061621a6001916159e0565b97019661614d565b9061623761623160ff936160ec565b916159d2565b9063bbf82ba360e01b5f5260018060a01b03166004521660245260445ffd5b9061626561623160ff936160ec565b9063587548c360e11b5f5260018060a01b031660045216602452600460445260645ffd5b61629386916160ec565b63ae8bb03960e01b5f5260018060a01b031660045260245260445ffd5b90506020813d82116162e2575b816162ca60209383615507565b810103126151b4575180151581036151b4575f6161df565b3d91506162bd565b6162f38b6160ec565b6311641feb60e21b5f9081526004929092526001600160a01b0316602452604490fd5b9850955095505050505080831061632a5750565b826305bc216760e51b5f5260045260245260445ffd5b630ed38fd160e41b5f526004524360245260445ffd5b637b51505560e01b5f526004524360245260445ffd5b631fc460bf60e11b5f5260045ffd5b60408101359081156164fc5761639461020082016159c5565b156165665761ffff6163a961010083016159ee565b1615801561655a575b6164fc5760608101600260ff6163c7836159d2565b161461653357600160ff6163da836159d2565b161415908161651b575b506164fc57602081013590815f52600660205260ff60405f205416156164e957815f52601360205260405f2060a082013590815f5260205260405f2090159081156164d9575b5061646f576101208101351580156164cc575b61646f57610180810163ffffffff616454826159fd565b16159081156164b0575b8115616488575b5061646f57505050565b638307aca560e01b5f523560045260245260445260645ffd5b905063ffffffff806164a66164a06101a086016159fd565b936159fd565b169116115f616465565b905063ffffffff6164c46101a084016159fd565b16159061645e565b506101408101351561643d565b60ff91506007015416155f61642a565b50633f4b99b160e21b5f5260045260245ffd5b602090638307aca560e01b5f528035600452013560245260445260645ffd5b6003915061652a60ff916159d2565b1614155f6163e4565b50602081013590811580159061654d575b61646f57505050565b5060a08101351515616544565b506080810135156163b2565b5050565b9080519060208101519060ff60408201511660608201516020815191012060808301516020815191012060ff60a0850151169063ffffffff60c0860151169263ffffffff60e087015116946001600160401b03610120610100890151151598015116976040519960208b019b5f516020616bcc5f395f51905f528d52600160408d015260608c015260808b015260a08a015260c089015260e08801526101008701526101208601526101408501526101608401526101808301526101a08201526101a081526158f46101c082615507565b6020818303126151b4578051906001600160401b0382116151b4570181601f820112156151b45780519061666e82615616565b9261667c6040519485615507565b828452602083830101116151b457815f9260208093018386015e8301015290565b903590601e19813603018212156151b457018035906001600160401b0382116151b4576020019181360383136151b457565b9160208201600460ff6166e1836159d2565b16146167905760ff6166f46005926159d2565b1614616701575050505f90565b5f61670b836160ec565b604051639e5adaeb60e01b81526001600160a01b0391821660048201529485916024918391165afa91821561522f57615682935f93616764575b5061675781604061675e93019061669d565b3691615631565b91616901565b61675e919350616788616757913d805f833e6167808183615507565b81019061663b565b939150616745565b505f61679b836160ec565b60405163b7af85d760e01b81526001600160a01b0391821660048201529485916024918391165afa91821561522f57615682935f936167ed575b506167578160406167e793019061669d565b91616811565b6167e7919350616809616757913d805f833e6167808183615507565b9391506167d5565b610a208151148015906168f4575b6168ed5760206168725f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282615507565b51906102045afa3d156168e6573d61688981615616565b906168976040519283615507565b81523d5f602083013e5b816168da575b816168b0575090565b90506020815191015190602081106168c9575b50151590565b5f199060200360031b1b165f6168c3565b805160201491506168a7565b60606168a1565b5050505f90565b506112138351141561681f565b6040815114801590616978575b6168ed5760206169615f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282615507565b51906102055afa3d156168e6573d61688981615616565b506174608351141561690e565b908015616b855760015b818110616af357505f5b818110616ab257505b600181116169b857506169b49061585e565b5190565b5f905f5b8181106169ca5750506169a2565b6001810180821161578257828114616a94576169e6828661587b565b51905f916169f4828861587b565b511115616a7857616a05838761587b565b519161578257616a15908661587b565b515b604051906020820192600160f81b84526021830152604182015260418152616a40606182615507565b519020616a56616a4f856159e0565b948661587b565b525b60028101809111156169bc57634e487b7160e01b5f52601160045260245ffd5b616a8391508561587b565b51616a8e828661587b565b51616a17565b50616a9f818561587b565b51616aac616a4f856159e0565b52616a58565b80616abf6001928561587b565b5160405160208101915f8352602182015260218152616adf604182615507565b519020616aec828661587b565b5201616999565b92616b008484939461587b565b5192845b8015801580616b67575b15616b4e575f1982019180831161578257616b34616b2c848861587b565b51918761587b565b5215616b0457634e487b7160e01b5f52601160045260245ffd5b5093616b60600193969294958661587b565b520161698f565b505f19820182811161578257616b7e87918761587b565b5111616b0e565b50505f9056febe98de78ab1a89101b9b77b1b3013a49647e63c349ee09cc55e0a33f97e351a9d850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267553bfca378ab6d3ea8c58df6b962c50de5515a3d2878713fa6dd8d84f220809ff
Žádný ocas metadat CBOR — tento bytecode byl sestaven s vypnutým cbor_metadata, nastavením, které naše kontrakty fixují kvůli neměnnosti adres CREATE2.

disassemblace (prvních 4,000 op)

pcopoperand
0000PUSH10xa0
0002DUP1
0003PUSH10x40
0005MSTORE
0006PUSH10x04
0008CALLDATASIZE
0009LT
000aISZERO
000bPUSH20x0012
000eJUMPI
000fPUSH0
0010DUP1
0011REVERT
0012JUMPDEST
0013PUSH0
0014SWAP1
0015PUSH0
0016CALLDATALOAD
0017PUSH10xe0
0019SHR
001aSWAP1
001bDUP2
001cPUSH40x0292709e
0021EQ
0022PUSH20x4de8
0025JUMPI
0026POP
0027DUP1
0028PUSH40x036f9a43
002dEQ
002ePUSH20x4dc9
0031JUMPI
0032DUP1
0033PUSH40x0489d038
0038EQ
0039PUSH20x4d37
003cJUMPI
003dDUP1
003ePUSH40x087a5cf4
0043EQ
0044PUSH20x4cd3
0047JUMPI
0048DUP1
0049PUSH40x08f0ce62
004eEQ
004fPUSH20x3297
0052JUMPI
0053DUP1
0054PUSH40x0900767d
0059EQ
005aPUSH20x4cb9
005dJUMPI
005eDUP1
005fPUSH40x1591c4a0
0064EQ
0065PUSH20x4686
0068JUMPI
0069DUP1
006aPUSH40x178bcc93
006fEQ
0070PUSH20x4641
0073JUMPI
0074DUP1
0075PUSH40x1945098e
007aEQ
007bPUSH20x0581
007eJUMPI
007fDUP1
0080PUSH40x1a633473
0085EQ
0086PUSH20x0475
0089JUMPI
008aDUP1
008bPUSH40x1e000aa5
0090EQ
0091PUSH20x454b
0094JUMPI
0095DUP1
0096PUSH40x200d43ad
009bEQ
009cPUSH20x38f5
009fJUMPI
00a0DUP1
00a1PUSH40x20d4f696
00a6EQ
00a7PUSH20x0475
00aaJUMPI
00abDUP1
00acPUSH40x210de79d
00b1EQ
00b2PUSH20x38a9
00b5JUMPI
00b6DUP1
00b7PUSH40x2b89089c
00bcEQ
00bdPUSH20x388c
00c0JUMPI
00c1DUP1
00c2PUSH40x3ec4cc34
00c7EQ
00c8PUSH20x3846
00cbJUMPI
00ccDUP1
00cdPUSH40x3fb27b85
00d2EQ
00d3PUSH20x37f0
00d6JUMPI
00d7DUP1
00d8PUSH40x42cde4e8
00ddEQ
00dePUSH20x37d2
00e1JUMPI
00e2DUP1
00e3PUSH40x4760fd43
00e8EQ
00e9PUSH20x3767
00ecJUMPI
00edDUP1
00eePUSH40x4bcb06a9
00f3EQ
00f4PUSH20x374d
00f7JUMPI
00f8DUP1
00f9PUSH40x4ec824ff
00feEQ
00ffPUSH20x3297
0102JUMPI
0103DUP1
0104PUSH40x5483a0b5
0109EQ
010aPUSH20x370a
010dJUMPI
010eDUP1
010fPUSH40x57d70648
0114EQ
0115PUSH20x36eb
0118JUMPI
0119DUP1
011aPUSH40x5b88f50e
011fEQ
0120PUSH20x3297
0123JUMPI
0124DUP1
0125PUSH40x5eb320b8
012aEQ
012bPUSH20x36cd
012eJUMPI
012fDUP1
0130PUSH40x5f61b15c
0135EQ
0136PUSH20x3297
0139JUMPI
013aDUP1
013bPUSH40x60c29fac
0140EQ
0141PUSH20x0581
0144JUMPI
0145DUP1
0146PUSH40x622c3f21
014bEQ
014cPUSH20x36b0
014fJUMPI
0150DUP1
0151PUSH40x65cea49c
0156EQ
0157PUSH20x3691
015aJUMPI
015bDUP1
015cPUSH40x68cf465e
0161EQ
0162PUSH20x0581
0165JUMPI
0166DUP1
0167PUSH40x6c2c44e2
016cEQ
016dPUSH20x3677
0170JUMPI
0171DUP1
0172PUSH40x6db81752
0177EQ
0178PUSH20x365b
017bJUMPI
017cDUP1
017dPUSH40x711f3db4
0182EQ
0183PUSH20x358c
0186JUMPI
0187DUP1
0188PUSH40x767c39d2
018dEQ
018ePUSH20x0475
0191JUMPI
0192DUP1
0193PUSH40x7b103999
0198EQ
0199PUSH20x3547
019cJUMPI
019dDUP1
019ePUSH40x7bb737bc
01a3EQ
01a4PUSH20x352d
01a7JUMPI
01a8DUP1
01a9PUSH40x7f49d69f
01aeEQ
01afPUSH20x34f2
01b2JUMPI
01b3DUP1
01b4PUSH40x831f33a1
01b9EQ
01baPUSH20x32d7
01bdJUMPI
01beDUP1
01bfPUSH40x8513e2d5
01c4EQ
01c5PUSH20x32b8
01c8JUMPI
01c9DUP1
01caPUSH40x852a52e8
01cfEQ
01d0PUSH20x329c
01d3JUMPI
01d4DUP1
01d5PUSH40x88f06eaa
01daEQ
01dbPUSH20x3297
01deJUMPI
01dfDUP1
01e0PUSH40x9212a9c9
01e5EQ
01e6PUSH20x311d
01e9JUMPI
01eaDUP1
01ebPUSH40x92a2e0b3
01f0EQ
01f1PUSH20x3101
01f4JUMPI
01f5DUP1
01f6PUSH40x9377c220
01fbEQ
01fcPUSH20x30da
01ffJUMPI
0200DUP1
0201PUSH40x9493ba1d
0206EQ
0207PUSH20x3076
020aJUMPI
020bDUP1
020cPUSH40x94bc4e96
0211EQ
0212PUSH20x2d72
0215JUMPI
0216DUP1
0217PUSH40x95102c42
021cEQ
021dPUSH20x2d55
0220JUMPI
0221DUP1
0222PUSH40x9648eca2
0227EQ
0228PUSH20x2ac2
022bJUMPI
022cDUP1
022dPUSH40x97b9857b
0232EQ
0233PUSH20x2a65
0236JUMPI
0237DUP1
0238PUSH40xa741113c
023dEQ
023ePUSH20x2a2a
0241JUMPI
0242DUP1
0243PUSH40xaa9239f5
0248EQ
0249PUSH20x29d3
024cJUMPI
024dDUP1
024ePUSH40xac65bc70
0253EQ
0254PUSH20x28b0
0257JUMPI
0258DUP1
0259PUSH40xaffed0e0
025eEQ
025fPUSH20x2889
0262JUMPI
0263DUP1
0264PUSH40xb19f4805
0269EQ
026aPUSH20x284e
026dJUMPI
026eDUP1
026fPUSH40xb8da302a
0274EQ
0275PUSH20x2830
0278JUMPI
0279DUP1
027aPUSH40xb9a7f076
027fEQ
0280PUSH20x2743
0283JUMPI
0284DUP1
0285PUSH40xbbbcda44
028aEQ
028bPUSH20x2724
028eJUMPI
028fDUP1
0290PUSH40xbda1019e
0295EQ
0296PUSH20x2706
0299JUMPI
029aDUP1
029bPUSH40xc5dbda66
02a0EQ
02a1PUSH20x26e9
02a4JUMPI
02a5DUP1
02a6PUSH40xc8cfbc17
02abEQ
02acPUSH20x08fd
02afJUMPI
02b0DUP1
02b1PUSH40xdc544a09
02b6EQ
02b7PUSH20x08db
02baJUMPI
02bbDUP1
02bcPUSH40xde54d429
02c1EQ
02c2PUSH20x08bd
02c5JUMPI
02c6DUP1
02c7PUSH40xdf46c713
02ccEQ
02cdPUSH20x0767
02d0JUMPI
02d1DUP1
02d2PUSH40xe02e1bfd
02d7EQ
02d8PUSH20x0749
02dbJUMPI
02dcDUP1
02ddPUSH40xe597e659
02e2EQ
02e3PUSH20x0586
02e6JUMPI
02e7DUP1
02e8PUSH40xe70475a7
02edEQ
02eePUSH20x0581
02f1JUMPI
02f2DUP1
02f3PUSH40xe94a52af
02f8EQ
02f9PUSH20x0565
02fcJUMPI
02fdDUP1
02fePUSH40xeafe7a74
0303EQ
0304PUSH20x0547
0307JUMPI
0308DUP1
0309PUSH40xeb777382
030eEQ
030fPUSH20x047a
0312JUMPI
0313DUP1
0314PUSH40xee076a50
0319EQ
031aPUSH20x0475
031dJUMPI
031eDUP1
031fPUSH40xee57e33c
0324EQ
0325PUSH20x03e8
0328JUMPI
0329DUP1
032aPUSH40xf3178f43
032fEQ
0330PUSH20x0393
0333JUMPI
0334DUP1
0335PUSH40xf4e885db
033aEQ
033bPUSH20x0375
033eJUMPI
033fPUSH40xf851a440
0344EQ
0345PUSH20x034c
0348JUMPI
0349PUSH0
034aDUP1
034bREVERT
034cJUMPDEST
034dCALLVALUE
034ePUSH20x0372
0351JUMPI
0352DUP1
0353PUSH10x03
0355NOT
0356CALLDATASIZE
0357ADD
0358SLT
0359PUSH20x0372
035cJUMPI
035dSLOAD
035ePUSH10x40
0360MLOAD
0361PUSH10x01
0363PUSH10x01
0365PUSH10xa0
0367SHL
0368SUB
0369SWAP1
036aSWAP2
036bAND
036cDUP2
036dMSTORE
036ePUSH10x20
0370SWAP1
0371RETURN
0372JUMPDEST
0373DUP1
0374REVERT
0375JUMPDEST
0376POP
0377CALLVALUE
0378PUSH20x0372
037bJUMPI
037cDUP1
037dPUSH10x03
037fNOT
0380CALLDATASIZE
0381ADD
0382SLT
0383PUSH20x0372
0386JUMPI
0387PUSH10x20
0389PUSH10x01
038bSLOAD
038cPUSH10x40
038eMLOAD
038fSWAP1
0390DUP2
0391MSTORE
0392RETURN
0393JUMPDEST
0394POP
0395CALLVALUE
0396PUSH20x0372
0399JUMPI
039aPUSH10x40
039cCALLDATASIZE
039dPUSH10x03
039fNOT
03a0ADD
03a1SLT
03a2PUSH20x0372
03a5JUMPI
03a6PUSH20xffff
03a9PUSH10x02
03abPUSH10x40
03adPUSH10x20
03afSWAP4
03b0PUSH20x03b7
03b3PUSH20x52a5
03b6JUMP
03b7JUMPDEST
03b8PUSH10x04
03baCALLDATALOAD
03bbDUP3
03bcMSTORE
03bdPUSH10x0a
03bfDUP7
03c0MSTORE
03c1PUSH20x03d5
03c4PUSH10x01
03c6PUSH10x01
03c8PUSH10x40
03caSHL
03cbSUB
03ccDUP5
03cdDUP5
03ceKECCAK256
03cfSWAP3
03d0AND
03d1PUSH20x5fa3
03d4JUMP
03d5JUMPDEST
03d6DUP3
03d7MSTORE
03d8DUP6
03d9MSTORE
03daKECCAK256
03dbADD
03dcSLOAD
03ddPUSH10x50
03dfSHR
03e0AND
03e1PUSH10x40
03e3MLOAD
03e4SWAP1
03e5DUP2
03e6MSTORE
03e7RETURN
03e8JUMPDEST
03e9POP
03eaCALLVALUE
03ebPUSH20x0372
03eeJUMPI
03efDUP1
03f0PUSH10x03
03f2NOT
03f3CALLDATASIZE
03f4ADD
03f5SLT
03f6PUSH20x0372
03f9JUMPI
03faPUSH10x40
03fcMLOAD
03fdDUP1
03feSWAP2
03ffPUSH10x20
0401PUSH10x11
0403SLOAD
0404SWAP3
0405DUP4
0406DUP2
0407MSTORE
0408ADD
0409SWAP2
040aPUSH10x11
040cDUP3
040dMSTORE
040ePUSH320x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68
042fSWAP2
0430JUMPDEST
0431DUP2
0432DUP2
0433LT
0434PUSH20x045f
0437JUMPI
0438PUSH20x045b
043bDUP6
043cPUSH20x0447
043fDUP2
0440DUP8
0441SUB
0442DUP3
0443PUSH20x5507
0446JUMP
0447JUMPDEST
0448PUSH10x40
044aMLOAD
044bSWAP2
044cDUP3
044dSWAP2
044ePUSH10x20
0450DUP4
0451MSTORE
0452PUSH10x20
0454DUP4
0455ADD
0456SWAP1
0457PUSH20x5374
045aJUMP
045bJUMPDEST
045cSUB
045dSWAP1
045eRETURN
045fJUMPDEST
0460DUP3
0461SLOAD
0462DUP5
0463MSTORE
0464PUSH10x20
0466SWAP1
0467SWAP4
0468ADD
0469SWAP3
046aPUSH10x01
046cSWAP3
046dDUP4
046eADD
046fSWAP3
0470ADD
0471PUSH20x0430
0474JUMP
0475JUMPDEST
0476PUSH20x540e
0479JUMP
047aJUMPDEST
047bPOP
047cCALLVALUE
047dPUSH20x0372
0480JUMPI
0481DUP1
0482PUSH10x03
0484NOT
0485CALLDATASIZE
0486ADD
0487SLT
0488PUSH20x0372
048bJUMPI
048cPUSH10x04
048eSLOAD
048fPUSH20x0497
0492DUP2
0493PUSH20x57ad
0496JUMP
0497JUMPDEST
0498DUP3
0499SWAP2
049aDUP4
049bJUMPDEST
049cDUP2
049dDUP2
049eLT
049fPUSH20x04eb
04a2JUMPI
04a3POP
04a4POP
04a5PUSH20x04ad
04a8DUP3
04a9PUSH20x57ad
04acJUMP
04adJUMPDEST
04aeSWAP3
04afJUMPDEST
04b0DUP3
04b1DUP2
04b2LT
04b3PUSH20x04cc
04b6JUMPI
04b7PUSH10x40
04b9MLOAD
04baPUSH10x20
04bcDUP1
04bdDUP3
04beMSTORE
04bfDUP2
04c0SWAP1
04c1PUSH20x045b
04c4SWAP1
04c5DUP3
04c6ADD
04c7DUP8
04c8PUSH20x5374
04cbJUMP
04ccJUMPDEST
04cdDUP1
04cePUSH20x04d9
04d1PUSH10x01
04d3SWAP3
04d4DUP5
04d5PUSH20x587b
04d8JUMP
04d9JUMPDEST
04daMLOAD
04dbPUSH20x04e4
04deDUP3
04dfDUP8
04e0PUSH20x587b
04e3JUMP
04e4JUMPDEST
04e5MSTORE
04e6ADD
04e7PUSH20x04af
04eaJUMP
04ebJUMPDEST
04ecDUP1
04edPUSH20x04f7
04f0PUSH10x01
04f2SWAP3
04f3PUSH20x57df
04f6JUMP
04f7JUMPDEST
04f8SWAP1
04f9SLOAD
04faSWAP1
04fbPUSH10x03
04fdSHL
04feSHR
04ffDUP7
0500MSTORE
0501PUSH10x05
0503PUSH10x20
0505MSTORE
0506PUSH10xff
0508PUSH10x0c
050aPUSH10x40
050cDUP9
050dKECCAK256
050eADD
050fSLOAD
0510PUSH10x08
0512SHR
0513AND
0514PUSH20x051e
0517JUMPI
0518JUMPDEST
0519ADD
051aPUSH20x049b
051dJUMP
051eJUMPDEST
051fPUSH20x0527
0522DUP2
0523PUSH20x57df
0526JUMP
0527JUMPDEST
0528SWAP1
0529SLOAD
052aSWAP1
052bPUSH10x03
052dSHL
052eSHR
052fPUSH20x0541
0532PUSH20x053a
0535DUP8
0536PUSH20x59e0
0539JUMP
053aJUMPDEST
053bSWAP7
053cDUP7
053dPUSH20x587b
0540JUMP
0541JUMPDEST
0542MSTORE
0543PUSH20x0518
0546JUMP
0547JUMPDEST
0548POP
0549CALLVALUE
054aPUSH20x0372
054dJUMPI
054eDUP1
054fPUSH10x03
0551NOT
0552CALLDATASIZE
0553ADD
0554SLT
0555PUSH20x0372
0558JUMPI
0559PUSH10x20
055bPUSH10x07
055dSLOAD
055ePUSH10x40
0560MLOAD
0561SWAP1
0562DUP2
0563MSTORE
0564RETURN
0565JUMPDEST
0566POP
0567CALLVALUE
0568PUSH20x0372
056bJUMPI
056cDUP1
056dPUSH10x03
056fNOT
0570CALLDATASIZE
0571ADD
0572SLT
0573PUSH20x0372
0576JUMPI
0577PUSH10x20
0579PUSH10x40
057bMLOAD
057cPUSH10x05
057eDUP2
057fMSTORE
0580RETURN
0581JUMPDEST
0582PUSH20x53f3
0585JUMP
0586JUMPDEST
0587POP
0588CALLVALUE
0589PUSH20x0372
058cJUMPI
058dPUSH10x40
058fCALLDATASIZE
0590PUSH10x03
0592NOT
0593ADD
0594SLT
0595PUSH20x0372
0598JUMPI
0599PUSH10x24
059bCALLDATALOAD
059cPUSH10x04
059eCALLDATALOAD
059fPUSH10xff
05a1DUP3
05a2AND
05a3DUP3
05a4SUB
05a5PUSH20x0745
05a8JUMPI
05a9PUSH10x07
05abSLOAD
05acPUSH20x05b4
05afDUP2
05b0PUSH20x609d
05b3JUMP
05b4JUMPDEST
05b5SWAP3
05b6DUP5
05b7SWAP3
05b8DUP6
05b9JUMPDEST
05baDUP4
05bbDUP2
05bcLT
05bdPUSH20x0652
05c0JUMPI
05c1POP
05c2POP
05c3POP
05c4POP
05c5PUSH20x05cd
05c8DUP2
05c9PUSH20x609d
05ccJUMP
05cdJUMPDEST
05ceSWAP2
05cfDUP4
05d0JUMPDEST
05d1DUP3
05d2DUP2
05d3LT
05d4PUSH20x0628
05d7JUMPI
05d8DUP4
05d9DUP6
05daPUSH10x40
05dcMLOAD
05ddSWAP2
05deDUP3
05dfSWAP2
05e0PUSH10x20
05e2DUP4
05e3ADD
05e4PUSH10x20
05e6DUP5
05e7MSTORE
05e8DUP3
05e9MLOAD
05eaDUP1
05ebSWAP2
05ecMSTORE
05edPUSH10x20
05efPUSH10x40
05f1DUP6
05f2ADD
05f3SWAP4
05f4ADD
05f5SWAP2
05f6JUMPDEST
05f7DUP2
05f8DUP2
05f9LT
05faPUSH20x0604
05fdJUMPI
05fePOP
05ffPOP
0600POP
0601SUB
0602SWAP1
0603RETURN
0604JUMPDEST
0605SWAP2
0606SWAP4
0607POP
0608SWAP2
0609PUSH10x20
060bPUSH10xc0
060dDUP3
060ePUSH20x061a
0611PUSH10x01
0613SWAP5
0614DUP9
0615MLOAD
0616PUSH20x532b
0619JUMP
061aJUMPDEST
061bADD
061cSWAP5
061dADD
061eSWAP2
061fADD
0620SWAP2
0621DUP5
0622SWAP4
0623SWAP3
0624PUSH20x05f6
0627JUMP
0628JUMPDEST
0629DUP1
062aPUSH20x0635
062dPUSH10x01
062fSWAP3
0630DUP5
0631PUSH20x587b
0634JUMP
0635JUMPDEST
0636MLOAD
0637PUSH20x0640
063aDUP3
063bDUP8
063cPUSH20x587b
063fJUMP
0640JUMPDEST
0641MSTORE
0642PUSH20x064b
0645DUP2
0646DUP7
0647PUSH20x587b
064aJUMP
064bJUMPDEST
064cPOP
064dADD
064ePUSH20x05d0
0651JUMP
0652JUMPDEST
0653PUSH20x065b
0656DUP2
0657PUSH20x580b
065aJUMP
065bJUMPDEST
065cSWAP1
065dSLOAD
065eSWAP1
065fPUSH10x03
0661SHL
0662SHR
0663DUP1
0664DUP9
0665MSTORE
0666PUSH10x08
0668PUSH10x20
066aMSTORE
066bPUSH10xff
066dPUSH10x05
066fPUSH10x40
0671DUP11
0672KECCAK256
0673ADD
0674SLOAD
0675PUSH10x48
0677SHR
0678AND
0679ISZERO
067aPUSH20x073c
067dJUMPI
067eDUP8
067fMSTORE
0680PUSH10x0a
0682PUSH10x20
0684MSTORE
0685PUSH10x40
0687DUP8
0688KECCAK256
0689DUP3
068aDUP9
068bMSTORE
068cPUSH10x20
068eMSTORE
068fPUSH10x40
0691DUP8
0692KECCAK256
0693PUSH10x02
0695PUSH10x40
0697MLOAD
0698SWAP2
0699PUSH20x06a1
069cDUP4
069dPUSH20x547c
06a0JUMP
06a1JUMPDEST
06a2DUP1
06a3SLOAD
06a4DUP4
06a5MSTORE
06a6PUSH10x01
06a8DUP2
06a9ADD
06aaSLOAD
06abPUSH10x20
06adDUP5
06aeADD
06afMSTORE
06b0ADD
06b1SLOAD
06b2PUSH10xff
06b4DUP2
06b5AND
06b6PUSH10x40
06b8DUP4
06b9ADD
06baMSTORE
06bbDUP5
06bcPUSH10xff
06beDUP3
06bfPUSH10x08
06c1SHR
06c2AND
06c3ISZERO
06c4ISZERO
06c5SWAP2
06c6DUP3
06c7PUSH10x60
06c9DUP6
06caADD
06cbMSTORE
06ccPUSH10x01
06cePUSH10x01
06d0PUSH10x40
06d2SHL
06d3SUB
06d4DUP2
06d5PUSH10x10
06d7SHR
06d8AND
06d9PUSH10x80
06dbDUP6
06dcADD
06ddMSTORE
06dePUSH20xffff
06e1DUP2
06e2PUSH10x50
06e4SHR
06e5AND
06e6PUSH10xa0
06e8DUP6
06e9ADD
06eaMSTORE
06ebDUP3
06ecPUSH20x072d
06efJUMPI
06f0JUMPDEST
06f1POP
06f2POP
06f3PUSH20x0702
06f6JUMPI
06f7JUMPDEST
06f8POP
06f9PUSH10x01
06fbSWAP1
06fcJUMPDEST
06fdADD
06fePUSH20x05b9
0701JUMP
0702JUMPDEST
0703SWAP5
0704SWAP1
0705PUSH20x0726
0708DUP3
0709PUSH20x0714
070cPUSH10x01
070eSWAP5
070fSWAP2
0710PUSH20x59e0
0713JUMP
0714JUMPDEST
0715SWAP8
0716PUSH20x071f
0719DUP3
071aDUP12
071bPUSH20x587b
071eJUMP
071fJUMPDEST
0720MSTORE
0721DUP9
0722PUSH20x587b
0725JUMP
0726JUMPDEST
0727POP
0728SWAP1
0729PUSH20x06f7
072cJUMP
072dJUMPDEST
072ePUSH10xff
0730SWAP3
0731POP
0732AND
0733AND
0734ISZERO
0735ISZERO
0736DUP5
0737PUSH0
0738PUSH20x06f0
073bJUMP
073cJUMPDEST
073dPOP
073ePUSH10x01
0740SWAP1
0741PUSH20x06fc
0744JUMP
0745JUMPDEST
0746DUP3
0747DUP1
0748REVERT
0749JUMPDEST
074aPOP
074bCALLVALUE
074cPUSH20x0372
074fJUMPI
0750DUP1
0751PUSH10x03
0753NOT
0754CALLDATASIZE
0755ADD
0756SLT
0757PUSH20x0372
075aJUMPI
075bPUSH10x20
075dPUSH10x04
075fSLOAD
0760PUSH10x40
0762MLOAD
0763SWAP1
0764DUP2
0765MSTORE
0766RETURN
0767JUMPDEST
0768POP
0769CALLVALUE
076aPUSH20x0372
076dJUMPI
076ePUSH10x20
0770CALLDATASIZE
0771PUSH10x03
0773NOT
0774ADD
0775SLT
0776PUSH20x0372
0779JUMPI
077aPUSH20x0781
077dPUSH20x5443
0780JUMP
0781JUMPDEST
0782PUSH10x07
0784SLOAD
0785SWAP1
0786PUSH20x078e
0789DUP3
078aPUSH20x604e
078dJUMP
078eJUMPDEST
078fSWAP2
0790DUP4
0791SWAP2
0792DUP5
0793JUMPDEST
0794DUP3
0795DUP2
0796LT
0797PUSH20x0841
079aJUMPI
079bPOP
079cPOP
079dPOP
079ePUSH20x07a6
07a1DUP2
07a2PUSH20x604e
07a5JUMP
07a6JUMPDEST
07a7SWAP2
07a8DUP4
07a9JUMPDEST
07aaDUP3
07abDUP2
07acLT
07adPUSH20x0817
07b0JUMPI
07b1DUP4
07b2DUP6
07b3PUSH10x40
07b5MLOAD
07b6SWAP2
07b7DUP3
07b8SWAP2
07b9PUSH10x20
07bbDUP4
07bcADD
07bdPUSH10x20
07bfDUP5
07c0MSTORE
07c1DUP3
07c2MLOAD
07c3DUP1
07c4SWAP2
07c5MSTORE
07c6PUSH10x40
07c8DUP5
07c9ADD
07caPUSH10x20
07ccPUSH10x40
07ceDUP4
07cfPUSH10x05
07d1SHL
07d2DUP8
07d3ADD
07d4ADD
07d5SWAP5
07d6ADD
07d7SWAP3
07d8SWAP1
07d9JUMPDEST
07daDUP3
07dbDUP3
07dcLT
07ddPUSH20x07e8
07e0JUMPI
07e1POP
07e2POP
07e3POP
07e4POP
07e5SUB
07e6SWAP1
07e7RETURN
07e8JUMPDEST
07e9SWAP2
07eaSWAP4
07ebPUSH10x01
07edSWAP2
07eeSWAP4
07efSWAP6
07f0POP
07f1PUSH10x20
07f3PUSH20x0807
07f6DUP2
07f7SWAP3
07f8PUSH10x3f
07faNOT
07fbDUP11
07fcDUP3
07fdSUB
07feADD
07ffDUP7
0800MSTORE
0801DUP9
0802MLOAD
0803PUSH20x5579
0806JUMP
0807JUMPDEST
0808SWAP7
0809ADD
080aSWAP3
080bADD
080cSWAP3
080dADD
080eDUP6
080fSWAP5
0810SWAP4
0811SWAP2
0812SWAP3
0813PUSH20x07d9
0816JUMP
0817JUMPDEST
0818DUP1
0819PUSH20x0824
081cPUSH10x01
081eSWAP3
081fDUP5
0820PUSH20x587b
0823JUMP
0824JUMPDEST
0825MLOAD
0826PUSH20x082f
0829DUP3
082aDUP8
082bPUSH20x587b
082eJUMP
082fJUMPDEST
0830MSTORE
0831PUSH20x083a
0834DUP2
0835DUP7
0836PUSH20x587b
0839JUMP
083aJUMPDEST
083bPOP
083cADD
083dPUSH20x07a9
0840JUMP
0841JUMPDEST
0842PUSH20x084a
0845DUP2
0846PUSH20x580b
0849JUMP
084aJUMPDEST
084bSWAP1
084cSLOAD
084dSWAP1
084ePUSH10x03
0850SHL
0851SHR
0852DUP7
0853MSTORE
0854PUSH10x08
0856PUSH10x20
0858MSTORE
0859PUSH20x0864
085cPUSH10x40
085eDUP8
085fKECCAK256
0860PUSH20x5e2e
0863JUMP
0864JUMPDEST
0865PUSH20x0100
0868DUP2
0869ADD
086aMLOAD
086bISZERO
086cISZERO
086dDUP1
086ePUSH20x08ab
0871JUMPI
0872JUMPDEST
0873PUSH20x0880
0876JUMPI
0877JUMPDEST
0878POP
0879PUSH10x01
087bADD
087cPUSH20x0793
087fJUMP
0880JUMPDEST
0881SWAP4
0882SWAP1
0883PUSH20x08a4
0886DUP3
0887PUSH20x0892
088aPUSH10x01
088cSWAP5
088dSWAP2
088ePUSH20x59e0
0891JUMP
0892JUMPDEST
0893SWAP7
0894PUSH20x089d
0897DUP3
0898DUP11
0899PUSH20x587b
089cJUMP
089dJUMPDEST
089eMSTORE
089fDUP8
08a0PUSH20x587b
08a3JUMP
08a4JUMPDEST
08a5POP
08a6SWAP1
08a7PUSH20x0877
08aaJUMP
08abJUMPDEST
08acPOP
08adPUSH10xff
08afDUP4
08b0PUSH10xa0
08b2DUP4
08b3ADD
08b4MLOAD
08b5AND
08b6AND
08b7ISZERO
08b8ISZERO
08b9PUSH20x0872
08bcJUMP
08bdJUMPDEST
08bePOP
08bfCALLVALUE
08c0PUSH20x0372
08c3JUMPI
08c4DUP1
08c5PUSH10x03
08c7NOT
08c8CALLDATASIZE
08c9ADD
08caSLT
08cbPUSH20x0372
08ceJUMPI
08cfPUSH10x20
08d1PUSH10x0d
08d3SLOAD
08d4PUSH10x40
08d6MLOAD
08d7SWAP1
08d8DUP2
08d9MSTORE
08daRETURN
08dbJUMPDEST
08dcPOP
08ddCALLVALUE
08dePUSH20x0372
08e1JUMPI
08e2PUSH10x20
08e4PUSH20x08f5
08e7PUSH20x08ef
08eaCALLDATASIZE
08ebPUSH20x5315
08eeJUMP
08efJUMPDEST
08f0SWAP1
08f1PUSH20x6008
08f4JUMP
08f5JUMPDEST
08f6PUSH10x40
08f8MLOAD
08f9SWAP1
08faDUP2
08fbMSTORE
08fcRETURN
08fdJUMPDEST
08fePOP
08ffCALLVALUE
0900PUSH20x0372
0903JUMPI
0904PUSH10xa0
0906CALLDATASIZE
0907PUSH10x03
0909NOT
090aADD
090bSLT
090cPUSH20x0372
090fJUMPI
0910PUSH10x01
0912PUSH10x01
0914PUSH10x40
0916SHL
0917SUB
0918PUSH10x04
091aCALLDATALOAD
091bGT
091cPUSH20x0372
091fJUMPI
0920CALLDATASIZE
0921PUSH10x23
0923PUSH10x04
0925CALLDATALOAD
0926ADD
0927SLT
0928ISZERO
0929PUSH20x0372
092cJUMPI
092dPUSH10x01
092fPUSH10x01
0931PUSH10x40
0933SHL
0934SUB
0935PUSH10x04
0937CALLDATALOAD
0938PUSH10x04
093aADD
093bCALLDATALOAD
093cGT
093dPUSH20x0372
0940JUMPI
0941CALLDATASIZE
0942PUSH10x24
0944PUSH20x02a0
0947PUSH10x04
0949CALLDATALOAD
094aPUSH10x04
094cADD
094dCALLDATALOAD
094eMUL
094fPUSH10x04
0951CALLDATALOAD
0952ADD
0953ADD
0954GT
0955PUSH20x0372
0958JUMPI
0959PUSH10x24
095bCALLDATALOAD
095cPUSH10x01
095ePUSH10x01
0960PUSH10x40
0962SHL
0963SUB
0964DUP2
0965GT
0966PUSH20x2393
0969JUMPI
096aPUSH20x0977
096dSWAP1
096eCALLDATASIZE
096fSWAP1
0970PUSH10x04
0972ADD
0973PUSH20x52e5
0976JUMP
0977JUMPDEST
0978SWAP2
0979PUSH10x01
097bPUSH10x01
097dPUSH10x40
097fSHL
0980SUB
0981PUSH10x44
0983CALLDATALOAD
0984GT
0985PUSH20x0372
0988JUMPI
0989CALLDATASIZE
098aPUSH10x23
098cPUSH10x44
098eCALLDATALOAD
098fADD
0990SLT
0991ISZERO
0992PUSH20x0372
0995JUMPI
0996PUSH10x01
0998PUSH10x01
099aPUSH10x40
099cSHL
099dSUB
099ePUSH10x44
09a0CALLDATALOAD
09a1PUSH10x04
09a3ADD
09a4CALLDATALOAD
09a5GT
09a6PUSH20x0372
09a9JUMPI
09aaCALLDATASIZE
09abPUSH10x24
09adPUSH10xc0
09afPUSH10x44
09b1CALLDATALOAD
09b2PUSH10x04
09b4ADD
09b5CALLDATALOAD
09b6MUL
09b7PUSH10x44
09b9CALLDATALOAD
09baADD
09bbADD
09bcGT
09bdPUSH20x0372
09c0JUMPI
09c1PUSH10x64
09c3CALLDATALOAD
09c4SWAP3
09c5PUSH10x01
09c7PUSH10x01
09c9PUSH10x40
09cbSHL
09ccSUB
09cdDUP5
09ceAND
09cfDUP5
09d0SUB
09d1PUSH20x2393
09d4JUMPI
09d5PUSH10x84
09d7CALLDATALOAD
09d8PUSH10x01
09daPUSH10x01
09dcPUSH10x40
09deSHL
09dfSUB
09e0DUP2
09e1GT
09e2PUSH20x0745
09e5JUMPI
09e6PUSH20x09f3
09e9SWAP1
09eaCALLDATASIZE
09ebSWAP1
09ecPUSH10x04
09eeADD
09efPUSH20x52e5
09f2JUMP
09f3JUMPDEST
09f4SWAP5
09f5PUSH10x02
09f7SLOAD
09f8DUP1
09f9ISZERO
09faPUSH20x26da
09fdJUMPI
09fePUSH10x04
0a00CALLDATALOAD
0a01PUSH10x04
0a03ADD
0a04CALLDATALOAD
0a05ISZERO
0a06DUP1
0a07PUSH20x26d2
0a0aJUMPI
0a0bJUMPDEST
0a0cDUP1
0a0dPUSH20x26c4
0a10JUMPI
0a11JUMPDEST
0a12PUSH20x26b5
0a15JUMPI
0a16PUSH10x03
0a18SLOAD
0a19SWAP3
0a1aPUSH10x40
0a1cMLOAD
0a1dSWAP8
0a1ePUSH10xa0
0a20DUP10
0a21ADD
0a22PUSH10x01
0a24PUSH10x01
0a26PUSH10x40
0a28SHL
0a29SUB
0a2aDUP7
0a2bAND
0a2cPUSH10x20
0a2eDUP12
0a2fADD
0a30MSTORE
0a31PUSH10x80
0a33PUSH10x40
0a35DUP12
0a36ADD
0a37MSTORE
0a38PUSH10x04
0a3aCALLDATALOAD
0a3bPUSH10x04
0a3dADD
0a3eCALLDATALOAD
0a3fSWAP1
0a40MSTORE
0a41PUSH10xc0
0a43DUP10
0a44ADD
0a45PUSH10x24
0a47PUSH10x04
0a49CALLDATALOAD
0a4aADD
0a4bDUP9
0a4cJUMPDEST
0a4dPUSH10x04
0a4fCALLDATALOAD
0a50PUSH10x04
0a52ADD
0a53CALLDATALOAD
0a54DUP2
0a55LT
0a56PUSH20x252e
0a59JUMPI
0a5aPOP
0a5bPOP
0a5cPUSH10x1f
0a5eNOT
0a5fDUP11
0a60DUP3
0a61SUB
0a62ADD
0a63PUSH10x60
0a65DUP12
0a66ADD
0a67MSTORE
0a68DUP7
0a69DUP2
0a6aMSTORE
0a6bPUSH10x20
0a6dDUP2
0a6eADD
0a6fPUSH10x20
0a71DUP9
0a72PUSH10x05
0a74SHL
0a75DUP4
0a76ADD
0a77ADD
0a78SWAP1
0a79DUP11
0a7aSWAP3
0a7bDUP11
0a7cSWAP2
0a7dJUMPDEST
0a7eDUP11
0a7fDUP4
0a80LT
0a81PUSH20x2422
0a84JUMPI
0a85POP
0a86POP
0a87POP
0a88PUSH10x20
0a8aSWAP2
0a8bPOP
0a8cPUSH10x1f
0a8eNOT
0a8fDUP12
0a90DUP3
0a91SUB
0a92ADD
0a93PUSH10x80
0a95DUP13
0a96ADD
0a97MSTORE
0a98PUSH10x44
0a9aCALLDATALOAD
0a9bPUSH10x04
0a9dADD
0a9eCALLDATALOAD
0a9fDUP2
0aa0MSTORE
0aa1ADD
0aa2SWAP9
0aa3PUSH10x24
0aa5PUSH10x44
0aa7CALLDATALOAD
0aa8ADD
0aa9DUP9
0aaaJUMPDEST
0aabPUSH10x44
0aadCALLDATALOAD
0aaePUSH10x04
0ab0ADD
0ab1CALLDATALOAD
0ab2DUP2
0ab3LT
0ab4PUSH20x23ab
0ab7JUMPI
0ab8POP
0ab9POP
0abaPUSH20x0ad3
0abdDUP2
0abePUSH20x0b75
0ac1SWAP8
0ac2SWAP9
0ac3SWAP10
0ac4SWAP11
0ac5SWAP12
0ac6SUB
0ac7PUSH10x1f
0ac9NOT
0acaDUP2
0acbADD
0accDUP4
0acdMSTORE
0aceDUP3
0acfPUSH20x5507
0ad2JUMP
0ad3JUMPDEST
0ad4PUSH10x20
0ad6DUP2
0ad7MLOAD
0ad8SWAP2
0ad9ADD
0adaKECCAK256
0adbPUSH10x40
0addMLOAD
0adePUSH10x20
0ae0DUP2
0ae1ADD
0ae2SWAP2
0ae3PUSH0
0ae4MLOAD
0ae5PUSH10x20
0ae7PUSH20x6bac
0aeaPUSH0
0aebCODECOPY
0aecPUSH0
0aedMLOAD
0aeeSWAP1
0aefPUSH0
0af0MSTORE
0af1DUP4
0af2MSTORE
0af3CHAINID
0af4PUSH10x40
0af6DUP4
0af7ADD
0af8MSTORE
0af9ADDRESS
0afaPUSH10x60
0afcDUP4
0afdADD
0afeMSTORE
0affPUSH320xe10634eb0bf7bd6ad00dc59a6c67fe9b09037b979405fa59137e7e530d601414
0b20PUSH10x80
0b22DUP4
0b23ADD
0b24MSTORE
0b25PUSH10x01
0b27PUSH10x01
0b29PUSH10x40
0b2bSHL
0b2cSUB
0b2dDUP8
0b2eAND
0b2fPUSH10xa0
0b31DUP4
0b32ADD
0b33MSTORE
0b34PUSH10xc0
0b36DUP3
0b37ADD
0b38MSTORE
0b39PUSH10xc0
0b3bDUP2
0b3cMSTORE
0b3dPUSH20x0b47
0b40PUSH10xe0
0b42DUP3
0b43PUSH20x5507
0b46JUMP
0b47JUMPDEST
0b48MLOAD
0b49SWAP1
0b4aKECCAK256
0b4bSWAP1
0b4cPUSH10x01
0b4eSLOAD
0b4fSWAP3
0b50PUSH320x000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6
0b71PUSH20x6100
0b74JUMP
0b75JUMPDEST
0b76POP
0b77PUSH10x01
0b79PUSH10x01
0b7bPUSH10x40
0b7dSHL
0b7eSUB
0b7fPUSH20x0b89
0b82DUP2
0b83DUP4
0b84AND
0b85PUSH20x5764
0b88JUMP
0b89JUMPDEST
0b8aAND
0b8bSWAP1
0b8cPUSH10x01
0b8ePUSH10x01
0b90PUSH10x40
0b92SHL
0b93SUB
0b94NOT
0b95AND
0b96OR
0b97PUSH10x03
0b99SSTORE
0b9aPUSH10x17
0b9cSLOAD
0b9dSWAP2
0b9ePUSH20x0baf
0ba1PUSH10x01
0ba3PUSH10x01
0ba5PUSH10x40
0ba7SHL
0ba8SUB
0ba9DUP5
0baaAND
0babPUSH20x5764
0baeJUMP
0bafJUMPDEST
0bb0PUSH80xffffffffffffffff
0bb9NOT
0bbaSWAP1
0bbbSWAP4
0bbcAND
0bbdPUSH10x01
0bbfPUSH10x01
0bc1PUSH10x40
0bc3SHL
0bc4SUB
0bc5DUP5
0bc6AND
0bc7OR
0bc8PUSH10x17
0bcaSSTORE
0bcbPUSH20x0bd9
0bceDUP3
0bcfPUSH10x04
0bd1DUP1
0bd2CALLDATALOAD
0bd3ADD
0bd4CALLDATALOAD
0bd5PUSH20x59b8
0bd8JUMP
0bd9JUMPDEST
0bdaPUSH10x04
0bdcPUSH10x44
0bdeCALLDATALOAD
0bdfADD
0be0CALLDATALOAD
0be1PUSH10x01
0be3PUSH10x01
0be5PUSH10xff
0be7SHL
0be8SUB
0be9DUP2
0beaAND
0bebSWAP1
0becSUB
0bedPUSH20x2397
0bf0JUMPI
0bf1PUSH20x0c07
0bf4PUSH20x0c18
0bf7SWAP2
0bf8PUSH10x44
0bfaCALLDATALOAD
0bfbPUSH10x04
0bfdADD
0bfeCALLDATALOAD
0bffPUSH10x01
0c01SHL
0c02SWAP1
0c03PUSH20x59b8
0c06JUMP
0c07JUMPDEST
0c08PUSH20x0c10
0c0bDUP2
0c0cPUSH20x57ad
0c0fJUMP
0c10JUMPDEST
0c11PUSH10x80
0c13MSTORE
0c14PUSH20x57ad
0c17JUMP
0c18JUMPDEST
0c19SWAP1
0c1aDUP1
0c1bSWAP3
0c1cDUP2
0c1dSWAP4
0c1eJUMPDEST
0c1fPUSH10x04
0c21CALLDATALOAD
0c22PUSH10x04
0c24ADD
0c25CALLDATALOAD
0c26DUP6
0c27LT
0c28ISZERO
0c29PUSH20x1478
0c2cJUMPI
0c2dPUSH20x0c47
0c30PUSH10x04
0c32CALLDATALOAD
0c33PUSH20x02a0
0c36DUP8
0c37MUL
0c38ADD
0c39PUSH10x64
0c3bDUP2
0c3cADD
0c3dCALLDATALOAD
0c3eSWAP1
0c3fPUSH10x44
0c41ADD
0c42CALLDATALOAD
0c43PUSH20x6008
0c46JUMP
0c47JUMPDEST
0c48PUSH10x24
0c4aPUSH20x02a0
0c4dDUP8
0c4eMUL
0c4fPUSH10x04
0c51CALLDATALOAD
0c52ADD
0c53ADD
0c54CALLDATALOAD
0c55SUB
0c56PUSH20x143d
0c59JUMPI
0c5aPUSH20x0c6e
0c5dPUSH20x0244
0c60PUSH20x02a0
0c63DUP8
0c64MUL
0c65PUSH10x04
0c67CALLDATALOAD
0c68ADD
0c69ADD
0c6aPUSH20x59c5
0c6dJUMP
0c6eJUMPDEST
0c6fDUP1
0c70PUSH20x13b0
0c73JUMPI
0c74JUMPDEST
0c75PUSH20x1392
0c78JUMPI
0c79PUSH10x01
0c7bPUSH10xff
0c7dPUSH20x0c91
0c80PUSH20x02a4
0c83PUSH20x02a0
0c86DUP10
0c87MUL
0c88PUSH10x04
0c8aCALLDATALOAD
0c8bADD
0c8cADD
0c8dPUSH20x59d2
0c90JUMP
0c91JUMPDEST
0c92AND
0c93GT
0c94PUSH20x1359
0c97JUMPI
0c98PUSH20x0cac
0c9bPUSH20x0244
0c9ePUSH20x02a0
0ca1DUP8
0ca2MUL
0ca3PUSH10x04
0ca5CALLDATALOAD
0ca6ADD
0ca7ADD
0ca8PUSH20x59c5
0cabJUMP
0cacJUMPDEST
0cadDUP1
0caePUSH20x133a
0cb1JUMPI
0cb2JUMPDEST
0cb3DUP1
0cb4PUSH20x1326
0cb7JUMPI
0cb8JUMPDEST
0cb9PUSH20x1308
0cbcJUMPI
0cbdPUSH320x84e75c8576483b53128ed23c26158522388ddb0a5df8516c83887d522196f91f
0cdePUSH10x44
0ce0PUSH20x02a0
0ce3DUP8
0ce4MUL
0ce5PUSH10x04
0ce7CALLDATALOAD
0ce8ADD
0ce9ADD
0ceaCALLDATALOAD
0cebEQ
0cecDUP1
0cedPUSH20x12e6
0cf0JUMPI
0cf1JUMPDEST
0cf2PUSH20x12ad
0cf5JUMPI
0cf6PUSH20x01e4
0cf9PUSH20x02a0
0cfcDUP7
0cfdMUL
0cfePUSH10x04
0d00CALLDATALOAD
0d01ADD
0d02ADD
0d03CALLDATALOAD
0d04ISZERO
0d05ISZERO
0d06DUP1
0d07PUSH20x1289
0d0aJUMPI
0d0bJUMPDEST
0d0cPUSH20x1269
0d0fJUMPI
0d10PUSH10x24
0d12PUSH20x02a0
0d15DUP7
0d16MUL
0d17PUSH10x04
0d19CALLDATALOAD
0d1aADD
0d1bADD
0d1cCALLDATALOAD
0d1dDUP4
0d1eMSTORE
0d1fPUSH10x06
0d21PUSH10x20
0d23MSTORE
0d24PUSH10xff
0d26PUSH10x40
0d28DUP5
0d29KECCAK256
0d2aSLOAD
0d2bAND
0d2cISZERO
0d2dPUSH20x11e3
0d30JUMPI
0d31JUMPDEST
0d32PUSH20x02a0
0d35DUP6
0d36MUL
0d37PUSH10x04
0d39CALLDATALOAD
0d3aADD
0d3bPUSH10x24
0d3dDUP2
0d3eADD
0d3fCALLDATALOAD
0d40DUP1
0d41DUP6
0d42MSTORE
0d43PUSH10x05
0d45PUSH10x20
0d47MSTORE
0d48PUSH10x40
0d4aDUP6
0d4bKECCAK256
0d4cSWAP1
0d4dDUP2
0d4eSSTORE
0d4fPUSH10x44
0d51DUP3
0d52ADD
0d53CALLDATALOAD
0d54PUSH10x01
0d56DUP3
0d57ADD
0d58SWAP1
0d59DUP2
0d5aSSTORE
0d5bPUSH10x64
0d5dDUP4
0d5eADD
0d5fCALLDATALOAD
0d60PUSH10x02
0d62DUP4
0d63ADD
0d64SSTORE
0d65PUSH10x84
0d67DUP4
0d68ADD
0d69CALLDATALOAD
0d6aPUSH10x03
0d6cDUP4
0d6dADD
0d6eSSTORE
0d6fSWAP2
0d70PUSH20x0d7b
0d73SWAP1
0d74PUSH10xa4
0d76ADD
0d77PUSH20x59d2
0d7aJUMP
0d7bJUMPDEST
0d7cPUSH10x04
0d7eDUP3
0d7fDUP2
0d80ADD
0d81DUP1
0d82SLOAD
0d83PUSH10xff
0d85NOT
0d86AND
0d87PUSH10xff
0d89SWAP4
0d8aSWAP1
0d8bSWAP4
0d8cAND
0d8dSWAP3
0d8eSWAP1
0d8fSWAP3
0d90OR
0d91SWAP1
0d92SWAP2
0d93SSTORE
0d94PUSH10xc4
0d96SWAP1
0d97CALLDATALOAD
0d98PUSH20x02a0
0d9bDUP10
0d9cMUL
0d9dADD
0d9eSWAP1
0d9fDUP2
0da0ADD
0da1CALLDATALOAD
0da2PUSH10x05
0da4DUP4
0da5ADD
0da6SSTORE
0da7PUSH10xe4
0da9DUP2
0daaADD
0dabCALLDATALOAD
0dacPUSH10x06
0daeDUP4
0dafADD
0db0SSTORE
0db1PUSH20x0dbd
0db4SWAP1
0db5PUSH20x0104
0db8ADD
0db9PUSH20x59d2
0dbcJUMP
0dbdJUMPDEST
0dbePUSH10xff
0dc0AND
0dc1PUSH10xff
0dc3NOT
0dc4PUSH10x07
0dc6DUP4
0dc7ADD
0dc8SLOAD
0dc9AND
0dcaOR
0dcbPUSH10x07
0dcdDUP3
0dceADD
0dcfSSTORE
0dd0PUSH20x02a0
0dd3DUP8
0dd4MUL
0dd5PUSH10x04
0dd7CALLDATALOAD
0dd8ADD
0dd9PUSH20x0124
0ddcADD
0dddPUSH20x0de5
0de0SWAP1
0de1PUSH20x5f8f
0de4JUMP
0de5JUMPDEST
0de6PUSH10x07
0de8DUP3
0de9ADD
0deaSWAP1
0debPUSH20x0e12
0deeSWAP2
0defSWAP1
0df0PUSH90xffffffffffffffff00
0dfaDUP3
0dfbSLOAD
0dfcSWAP2
0dfdPUSH10x08
0dffSHL
0e00AND
0e01SWAP1
0e02PUSH90xffffffffffffffff00
0e0cNOT
0e0dAND
0e0eOR
0e0fSWAP1
0e10SSTORE
0e11JUMP
0e12JUMPDEST
0e13PUSH20x0e27
0e16PUSH20x0144
0e19PUSH10x04
0e1bCALLDATALOAD
0e1cPUSH20x02a0
0e1fDUP11
0e20MUL
0e21ADD
0e22ADD
0e23PUSH20x59fd
0e26JUMP
0e27JUMPDEST
0e28PUSH10x07
0e2aDUP3
0e2bADD
0e2cSLOAD
0e2dPUSH20xffff
0e30PUSH10x68
0e32SHL
0e33PUSH20x0e47
0e36PUSH20x0164
0e39PUSH10x04
0e3bCALLDATALOAD
0e3cPUSH20x02a0
0e3fDUP14
0e40MUL
0e41ADD
0e42ADD
0e43PUSH20x59ee
0e46JUMP
0e47JUMPDEST
0e48PUSH10x68
0e4aSHL
0e4bAND
0e4cSWAP1
0e4dPUSH20xffff
0e50PUSH10x78
0e52SHL
0e53PUSH20x0e67
0e56PUSH20x0184
0e59PUSH10x04
0e5bCALLDATALOAD
0e5cPUSH20x02a0
0e5fDUP15
0e60MUL
0e61ADD
0e62ADD
0e63PUSH20x59ee
0e66JUMP
0e67JUMPDEST
0e68PUSH10x78
0e6aSHL
0e6bAND
0e6cSWAP3
0e6dPUSH10x48
0e6fSHL
0e70PUSH130xffffffff000000000000000000
0e7eAND
0e7fSWAP1
0e80PUSH80xffffffffffffffff
0e89PUSH10x48
0e8bSHL
0e8cNOT
0e8dAND
0e8eOR
0e8fOR
0e90OR
0e91PUSH10x07
0e93DUP3
0e94ADD
0e95SSTORE
0e96PUSH20x02a0
0e99DUP8
0e9aMUL
0e9bPUSH10x04
0e9dCALLDATALOAD
0e9eADD
0e9fPUSH20x01a4
0ea2ADD
0ea3CALLDATALOAD
0ea4PUSH10x08
0ea6DUP3
0ea7ADD
0ea8SSTORE
0ea9PUSH20x02a0
0eacDUP8
0eadMUL
0eaePUSH10x04
0eb0CALLDATALOAD
0eb1ADD
0eb2PUSH20x01c4
0eb5ADD
0eb6PUSH20x0ebe
0eb9SWAP1
0ebaPUSH20x59d2
0ebdJUMP
0ebeJUMPDEST
0ebfPUSH10x09
0ec1DUP3
0ec2ADD
0ec3DUP1
0ec4SLOAD
0ec5PUSH10xff
0ec7NOT
0ec8AND
0ec9PUSH10xff
0ecbSWAP3
0eccSWAP1
0ecdSWAP3
0eceAND
0ecfSWAP2
0ed0SWAP1
0ed1SWAP2
0ed2OR
0ed3SWAP1
0ed4SSTORE
0ed5PUSH20x01e4
0ed8PUSH10x04
0edaCALLDATALOAD
0edbPUSH20x02a0
0edeDUP10
0edfMUL
0ee0ADD
0ee1SWAP1
0ee2DUP2
0ee3ADD
0ee4CALLDATALOAD
0ee5PUSH10x0a
0ee7DUP4
0ee8ADD
0ee9SSTORE
0eeaPUSH20x0204
0eedDUP2
0eeeADD
0eefCALLDATALOAD
0ef0PUSH10x0b
0ef2DUP4
0ef3ADD
0ef4SSTORE
0ef5PUSH20x0f01
0ef8SWAP1
0ef9PUSH20x0224
0efcADD
0efdPUSH20x59d2
0f00JUMP
0f01JUMPDEST
0f02PUSH10xff
0f04AND
0f05PUSH10xff
0f07NOT
0f08PUSH10x0c
0f0aDUP4
0f0bADD
0f0cSLOAD
0f0dAND
0f0eOR
0f0fPUSH10x0c
0f11DUP3
0f12ADD
0f13SSTORE
0f14PUSH20x02a0
0f17DUP8
0f18MUL
0f19PUSH10x04
0f1bCALLDATALOAD
0f1cADD
0f1dPUSH20x0244
0f20ADD
0f21PUSH20x0f29
0f24SWAP1
0f25PUSH20x59c5
0f28JUMP
0f29JUMPDEST
0f2aPUSH10x0c
0f2cDUP3
0f2dADD
0f2eDUP1
0f2fSLOAD
0f30SWAP2
0f31ISZERO
0f32ISZERO
0f33PUSH10x08
0f35SHL
0f36PUSH20xff00
0f39AND
0f3aPUSH100xffffffffffffffffff00
0f45NOT
0f46SWAP1
0f47SWAP3
0f48AND
0f49SWAP2
0f4aSWAP1
0f4bSWAP2
0f4cOR
0f4dPUSH10x10
0f4fDUP11
0f50SWAP1
0f51SHL
0f52PUSH100xffffffffffffffff0000
0f5dAND
0f5eOR
0f5fSWAP1
0f60SSTORE
0f61PUSH20x0f75
0f64PUSH20x0284
0f67PUSH10x04
0f69CALLDATALOAD
0f6aPUSH20x02a0
0f6dDUP11
0f6eMUL
0f6fADD
0f70ADD
0f71PUSH20x5f8f
0f74JUMP
0f75JUMPDEST
0f76PUSH10x0c
0f78DUP3
0f79ADD
0f7aDUP1
0f7bSLOAD
0f7cPUSH80xffffffffffffffff
0f85PUSH10x50
0f87SHL
0f88NOT
0f89AND
0f8aPUSH10x50
0f8cSWAP3
0f8dSWAP1
0f8eSWAP3
0f8fSHL
0f90PUSH80xffffffffffffffff
0f99PUSH10x50
0f9bSHL
0f9cAND
0f9dSWAP2
0f9eSWAP1
0f9fSWAP2
0fa0OR
0fa1SWAP1
0fa2SSTORE
0fa3PUSH20x0fb7
0fa6PUSH20x02a4
0fa9PUSH10x04
0fabCALLDATALOAD
0facPUSH20x02a0
0fafDUP11
0fb0MUL
0fb1ADD
0fb2ADD
0fb3PUSH20x59d2
0fb6JUMP
0fb7JUMPDEST
0fb8PUSH10x0c
0fbaDUP3
0fbbADD
0fbcDUP1
0fbdSLOAD
0fbePUSH10xff
0fc0PUSH10x90
0fc2SHL
0fc3NOT
0fc4DUP2
0fc5AND
0fc6PUSH10x90
0fc8DUP5
0fc9SWAP1
0fcaSHL
0fcbPUSH10xff
0fcdPUSH10x90
0fcfSHL
0fd0AND
0fd1OR
0fd2SWAP1
0fd3SWAP2
0fd4SSTORE
0fd5SWAP2
0fd6DUP7
0fd7SWAP1
0fd8PUSH20x0fec
0fdbPUSH10x24
0fddPUSH10x04
0fdfCALLDATALOAD
0fe0PUSH20x02a0
0fe3DUP14
0fe4MUL
0fe5ADD
0fe6ADD
0fe7CALLDATALOAD
0fe8PUSH20x58c2
0febJUMP
0fecJUMPDEST
0fedDUP7
0feePUSH10x80
0ff0MLOAD
0ff1SWAP1
0ff2PUSH20x0ffa
0ff5SWAP2
0ff6PUSH20x587b
0ff9JUMP
0ffaJUMPDEST
0ffbMSTORE
0ffcDUP1
0ffdSLOAD
0ffeSWAP5
0fffSLOAD
1000PUSH10x02
1002DUP3
1003ADD
1004SLOAD
1005PUSH10x03
1007DUP4
1008ADD
1009SLOAD
100aPUSH10x04
100cDUP5
100dADD
100eSLOAD
100fPUSH10x05
1011DUP6
1012ADD
1013SLOAD
1014PUSH10x06
1016DUP7
1017ADD
1018SLOAD
1019PUSH10x07
101bDUP8
101cADD
101dSLOAD
101ePUSH10x08
1020DUP1
1021DUP10
1022ADD
1023SLOAD
1024PUSH10x09
1026DUP11
1027ADD
1028SLOAD
1029PUSH10x0a
102bDUP12
102cADD
102dSLOAD
102ePUSH10x0b
1030SWAP1
1031SWAP12
1032ADD
1033SLOAD
1034PUSH10x40
1036DUP1
1037MLOAD
1038PUSH0
1039MLOAD
103aPUSH10x20
103cPUSH20x6bcc
103fPUSH0
1040CODECOPY
1041PUSH0
1042MLOAD
1043SWAP1
1044PUSH0
1045MSTORE
1046PUSH10x20
1048DUP3
1049ADD
104aMSTORE
104bSWAP1
104cDUP2
104dADD
104eSWAP14
104fSWAP1
1050SWAP14
1051MSTORE
1052PUSH10x60
1054DUP14
1055ADD
1056SWAP16
1057SWAP1
1058SWAP16
1059MSTORE
105aPUSH10x80
105cDUP13
105dADD
105eSWAP10
105fSWAP1
1060SWAP10
1061MSTORE
1062PUSH10xa0
1064DUP12
1065ADD
1066SWAP8
1067SWAP1
1068SWAP8
1069MSTORE
106aPUSH10xc0
106cDUP11
106dADD
106eSWAP6
106fSWAP1
1070SWAP6
1071MSTORE
1072PUSH10xff
1074SWAP4
1075DUP5
1076AND
1077PUSH10xe0
1079DUP11
107aADD
107bMSTORE
107cPUSH20x0100
107fDUP10
1080ADD
1081SWAP3
1082SWAP1
1083SWAP3
1084MSTORE
1085PUSH20x0120
1088DUP9
1089ADD
108aMSTORE
108bDUP1
108cDUP3
108dAND
108ePUSH20x0140
1091DUP9
1092ADD
1093MSTORE
1094PUSH10x01
1096PUSH10x01
1098PUSH10x40
109aSHL
109bSUB
109cDUP2
109dDUP5
109eSHR
109fDUP2
10a0AND
10a1PUSH20x0160
10a4DUP10
10a5ADD
10a6MSTORE
10a7PUSH40xffffffff
10acPUSH10x48
10aeDUP4
10afSWAP1
10b0SHR
10b1AND
10b2PUSH20x0180
10b5DUP10
10b6ADD
10b7MSTORE
10b8PUSH20xffff
10bbPUSH10x68
10bdDUP4
10beSWAP1
10bfSHR
10c0DUP2
10c1AND
10c2PUSH20x01a0
10c5DUP11
10c6ADD
10c7MSTORE
10c8PUSH10x78
10caSWAP3
10cbSWAP1
10ccSWAP3
10cdSHR
10ceSWAP1
10cfSWAP2
10d0AND
10d1PUSH20x01c0
10d4DUP9
10d5ADD
10d6MSTORE
10d7PUSH20x01e0
10daDUP8
10dbADD
10dcSWAP4
10ddSWAP1
10deSWAP4
10dfMSTORE
10e0SWAP3
10e1DUP4
10e2AND
10e3PUSH20x0200
10e6DUP7
10e7ADD
10e8MSTORE
10e9PUSH20x0220
10ecDUP6
10edADD
10eeSWAP4
10efSWAP1
10f0SWAP4
10f1MSTORE
10f2PUSH20x0240
10f5DUP5
10f6ADD
10f7SWAP7
10f8SWAP1
10f9SWAP7
10faMSTORE
10fbDUP5
10fcDUP2
10fdAND
10fePUSH20x0260
1101DUP5
1102ADD
1103MSTORE
1104PUSH10xff
1106PUSH10x90
1108SHL
1109NOT
110aSWAP1
110bSWAP5
110cAND
110dPUSH10x90
110fSWAP4
1110DUP5
1111SHL
1112PUSH10xff
1114PUSH10x90
1116SHL
1117AND
1118OR
1119SWAP1
111aDUP2
111bSWAP1
111cSHR
111dDUP5
111eAND
111fISZERO
1120ISZERO
1121PUSH20x0280
1124DUP4
1125ADD
1126MSTORE
1127PUSH10x10
1129DUP2
112aSWAP1
112bSHR
112cDUP6
112dAND
112ePUSH20x02a0
1131DUP4
1132ADD
1133MSTORE
1134PUSH10x50
1136DUP2
1137SWAP1
1138SHR
1139SWAP1
113aSWAP5
113bAND
113cPUSH20x02c0
113fDUP3
1140ADD
1141MSTORE
1142SWAP3
1143SWAP1
1144SHR
1145AND
1146PUSH20x02e0
1149DUP1
114aDUP4
114bADD
114cSWAP2
114dSWAP1
114eSWAP2
114fMSTORE
1150DUP2
1151MSTORE
1152PUSH20x115d
1155PUSH20x0300
1158DUP3
1159PUSH20x5507
115cJUMP
115dJUMPDEST
115eDUP1
115fMLOAD
1160SWAP1
1161PUSH10x20
1163ADD
1164KECCAK256
1165PUSH20x116e
1168DUP3
1169DUP7
116aPUSH20x587b
116dJUMP
116eJUMPDEST
116fMSTORE
1170PUSH20x1178
1173SWAP1
1174PUSH20x59e0
1177JUMP
1178JUMPDEST
1179SWAP4
117aDUP6
117bPUSH20x118f
117ePUSH20x0244
1181PUSH10x04
1183CALLDATALOAD
1184PUSH20x02a0
1187DUP6
1188MUL
1189ADD
118aADD
118bPUSH20x59c5
118eJUMP
118fJUMPDEST
1190PUSH10x40
1192DUP1
1193MLOAD
1194SWAP2
1195ISZERO
1196ISZERO
1197DUP3
1198MSTORE
1199PUSH10x01
119bPUSH10x01
119dPUSH10x40
119fSHL
11a0SUB
11a1SWAP3
11a2SWAP1
11a3SWAP3
11a4AND
11a5PUSH10x20
11a7DUP3
11a8ADD
11a9MSTORE
11aaPUSH20x02a0
11adDUP4
11aeMUL
11afPUSH10x04
11b1CALLDATALOAD
11b2ADD
11b3PUSH10x24
11b5ADD
11b6CALLDATALOAD
11b7SWAP2
11b8PUSH320x523b72ac5fbe8982a57ea63c8ca4daf1602c22eda42d6cc14a884c0310698b80
11d9SWAP2
11daLOG2
11dbPUSH10x01
11ddADD
11deSWAP4
11dfPUSH20x0c1e
11e2JUMP
11e3JUMPDEST
11e4PUSH10x04
11e6DUP1
11e7CALLDATALOAD
11e8PUSH20x02a0
11ebDUP8
11ecMUL
11edADD
11eePUSH10x24
11f0ADD
11f1CALLDATALOAD
11f2DUP5
11f3MSTORE
11f4PUSH10x06
11f6PUSH10x20
11f8MSTORE
11f9PUSH10x40
11fbDUP5
11fcKECCAK256
11fdDUP1
11feSLOAD
11ffPUSH10xff
1201NOT
1202AND
1203PUSH10x01
1205OR
1206SWAP1
1207SSTORE
1208SLOAD
1209PUSH10x01
120bPUSH10x40
120dSHL
120eDUP2
120fLT
1210ISZERO
1211PUSH20x1255
1214JUMPI
1215PUSH20x1229
1218DUP2
1219PUSH10x01
121bPUSH20x124e
121eSWAP4
121fADD
1220PUSH10x04
1222SSTORE
1223PUSH10x04
1225PUSH20x5823
1228JUMP
1229JUMPDEST
122aPUSH10x24
122cPUSH20x02a0
122fDUP10
1230SWAP5
1231SWAP4
1232SWAP5
1233MUL
1234PUSH10x04
1236CALLDATALOAD
1237ADD
1238ADD
1239CALLDATALOAD
123aSWAP1
123bDUP4
123cSLOAD
123dSWAP1
123ePUSH10x03
1240SHL
1241SWAP2
1242DUP3
1243SHL
1244SWAP2
1245PUSH0
1246NOT
1247SWAP1
1248SHL
1249NOT
124aAND
124bOR
124cSWAP1
124dJUMP
124eJUMPDEST
124fSWAP1
1250SSTORE
1251PUSH20x0d31
1254JUMP
1255JUMPDEST
1256PUSH40x4e487b71
125bPUSH10xe0
125dSHL
125eDUP5
125fMSTORE
1260PUSH10x41
1262PUSH10x04
1264MSTORE
1265PUSH10x24
1267DUP5
1268REVERT
1269JUMPDEST
126aPUSH40x3f4b99b1
126fPUSH10xe2
1271SHL
1272DUP4
1273MSTORE
1274PUSH10x04
1276DUP1
1277CALLDATALOAD
1278PUSH20x02a0
127bDUP8
127cMUL
127dADD
127ePUSH20x01e4
1281ADD
1282CALLDATALOAD
1283SWAP1
1284MSTORE
1285PUSH10x24
1287DUP4
1288REVERT
1289JUMPDEST
128aPOP
128bPUSH20x01e4
128ePUSH20x02a0
1291DUP7
1292MUL
1293PUSH10x04
1295CALLDATALOAD
1296ADD
1297ADD
1298CALLDATALOAD
1299DUP4
129aMSTORE
129bPUSH10x06
129dPUSH10x20
129fMSTORE
12a0PUSH10xff
12a2PUSH10x40
12a4DUP5
12a5KECCAK256
12a6SLOAD
12a7AND
12a8ISZERO
12a9PUSH20x0d0b
12acJUMP
12adJUMPDEST
12aePUSH10x44
12b0DUP4
12b1PUSH10xff
12b3DUP8
12b4PUSH10x24
12b6PUSH20x02a0
12b9PUSH20x12cb
12bcPUSH20x0224
12bfDUP3
12c0DUP6
12c1MUL
12c2PUSH10x04
12c4CALLDATALOAD
12c5ADD
12c6ADD
12c7PUSH20x59d2
12caJUMP
12cbJUMPDEST
12ccSWAP3
12cdPUSH40x1e9a7d75
12d2PUSH10xe0
12d4SHL
12d5DUP7
12d6MSTORE
12d7MUL
12d8PUSH10x04
12daCALLDATALOAD
12dbADD
12dcADD
12ddCALLDATALOAD
12dePUSH10x04
12e0MSTORE
12e1AND
12e2PUSH10x24
12e4MSTORE
12e5REVERT
12e6JUMPDEST
12e7POP
12e8PUSH10x01
12eaPUSH10xff
12ecPUSH20x1300
12efPUSH20x0224
12f2PUSH20x02a0
12f5DUP10
12f6MUL
12f7PUSH10x04
12f9CALLDATALOAD
12faADD
12fbADD
12fcPUSH20x59d2
12ffJUMP
1300JUMPDEST
1301AND
1302EQ
1303ISZERO
1304PUSH20x0cf1
1307JUMP
1308JUMPDEST
1309PUSH10x24
130bDUP4
130cDUP2
130dPUSH20x02a0
1310DUP9
1311PUSH40x2eb72ead
1316PUSH10xe1
1318SHL
1319DUP5
131aMSTORE
131bMUL
131cPUSH10x04
131eCALLDATALOAD
131fADD
1320ADD
1321CALLDATALOAD
1322PUSH10x04
1324MSTORE
1325REVERT
1326JUMPDEST
1327POP
1328PUSH10x84
132aPUSH20x02a0
132dDUP7
132eMUL
132fPUSH10x04
1331CALLDATALOAD
1332ADD
1333ADD
1334CALLDATALOAD
1335ISZERO
1336PUSH20x0cb8
1339JUMP
133aJUMPDEST
133bPOP
133cPUSH10xff
133ePUSH20x1352
1341PUSH20x02a4
1344PUSH20x02a0
1347DUP9
1348MUL
1349PUSH10x04
134bCALLDATALOAD
134cADD
134dADD
134ePUSH20x59d2
1351JUMP
1352JUMPDEST
1353AND
1354ISZERO
1355PUSH20x0cb2
1358JUMP
1359JUMPDEST
135aPUSH10x44
135cDUP4
135dPUSH10xff
135fDUP8
1360PUSH10x24
1362PUSH20x02a0
1365PUSH20x1377
1368PUSH20x02a4
136bDUP3
136cDUP6
136dMUL
136ePUSH10x04
1370CALLDATALOAD
1371ADD
1372ADD
1373PUSH20x59d2
1376JUMP
1377JUMPDEST
1378SWAP3
1379PUSH40x1a7ce1dd
137ePUSH10xe0
1380SHL
1381DUP7
1382MSTORE
1383MUL
1384PUSH10x04
1386CALLDATALOAD
1387ADD
1388ADD
1389CALLDATALOAD
138aPUSH10x04
138cMSTORE
138dAND
138ePUSH10x24
1390MSTORE
1391REVERT
1392JUMPDEST
1393PUSH10x24
1395DUP4
1396DUP2
1397PUSH20x02a0
139aDUP9
139bPUSH40x3dc2fc11
13a0PUSH10xe0
13a2SHL
13a3DUP5
13a4MSTORE
13a5MUL
13a6PUSH10x04
13a8CALLDATALOAD
13a9ADD
13aaADD
13abCALLDATALOAD
13acPUSH10x04
13aeMSTORE
13afREVERT
13b0JUMPDEST
13b1POP
13b2PUSH40xffffffff
13b7PUSH20x13cb
13baPUSH20x0144
13bdPUSH20x02a0
13c0DUP9
13c1MUL
13c2PUSH10x04
13c4CALLDATALOAD
13c5ADD
13c6ADD
13c7PUSH20x59fd
13caJUMP
13cbJUMPDEST
13ccAND
13cdISZERO
13ceDUP1
13cfISZERO
13d0PUSH20x141e
13d3JUMPI
13d4JUMPDEST
13d5DUP1
13d6ISZERO
13d7PUSH20x13ff
13daJUMPI
13dbJUMPDEST
13dcDUP1
13ddPUSH20x0c74
13e0JUMPI
13e1POP
13e2PUSH10xff
13e4PUSH20x13f8
13e7PUSH20x0224
13eaPUSH20x02a0
13edDUP9
13eeMUL
13efPUSH10x04
13f1CALLDATALOAD
13f2ADD
13f3ADD
13f4PUSH20x59d2
13f7JUMP
13f8JUMPDEST
13f9AND
13faISZERO
13fbPUSH20x0c74
13feJUMP
13ffJUMPDEST
1400POP
1401PUSH10xff
1403PUSH20x1417
1406PUSH20x01c4
1409PUSH20x02a0
140cDUP9
140dMUL
140ePUSH10x04
1410CALLDATALOAD
1411ADD
1412ADD
1413PUSH20x59d2
1416JUMP
1417JUMPDEST
1418AND
1419ISZERO
141aPUSH20x13db
141dJUMP
141eJUMPDEST
141fPOP
1420PUSH10xff
1422PUSH20x1436
1425PUSH20x0104
1428PUSH20x02a0
142bDUP9
142cMUL
142dPUSH10x04
142fCALLDATALOAD
1430ADD
1431ADD
1432PUSH20x59d2
1435JUMP
1436JUMPDEST
1437AND
1438ISZERO
1439PUSH20x13d4
143cJUMP
143dJUMPDEST
143ePUSH10x44
1440DUP4
1441DUP7
1442PUSH10x24
1444PUSH20x02a0
1447PUSH20x145e
144aPUSH10x04
144cCALLDATALOAD
144dDUP3
144eDUP6
144fMUL
1450ADD
1451PUSH10x64
1453DUP2
1454ADD
1455CALLDATALOAD
1456SWAP1
1457DUP8
1458ADD
1459CALLDATALOAD
145aPUSH20x6008
145dJUMP
145eJUMPDEST
145fSWAP3
1460PUSH40x1a35ac99
1465PUSH10xe0
1467SHL
1468DUP6
1469MSTORE
146aMUL
146bPUSH10x04
146dCALLDATALOAD
146eADD
146fADD
1470CALLDATALOAD
1471PUSH10x04
1473MSTORE
1474PUSH10x24
1476MSTORE
1477REVERT
1478JUMPDEST
1479SWAP2
147aSWAP4
147bPOP
147cSWAP4
147dSWAP5
147eDUP4
147fSWAP2
1480JUMPDEST
1481DUP6
1482DUP4
1483LT
1484ISZERO
1485PUSH20x1a5e
1488JUMPI
1489DUP3
148aPUSH10x05
148cSHL
148dDUP3
148eADD
148fCALLDATALOAD
1490PUSH20x013e
1493NOT
1494DUP4
1495CALLDATASIZE
1496SUB
1497ADD
1498DUP2
1499SLT
149aISZERO
149bPUSH20x1a5a
149eJUMPI
149fPUSH20x14ab
14a2SWAP1
14a3CALLDATASIZE
14a4SWAP1
14a5DUP5
14a6ADD
14a7PUSH20x5685
14aaJUMP
14abJUMPDEST
14acSWAP3
14adDUP4
14aeMLOAD
14afDUP7
14b0MSTORE
14b1PUSH10x06
14b3PUSH10x20
14b5MSTORE
14b6PUSH10xff
14b8PUSH10x40
14baDUP8
14bbKECCAK256
14bcSLOAD
14bdAND
14beISZERO
14bfPUSH20x1a46
14c2JUMPI
14c3PUSH10xa0
14c5DUP5
14c6ADD
14c7MLOAD
14c8PUSH10xec
14caDUP2
14cbAND
14ccPUSH20x1a31
14cfJUMPI
14d0POP
14d1PUSH40xffffffff
14d6PUSH10xc0
14d8DUP6
14d9ADD
14daMLOAD
14dbAND
14dcISZERO
14ddPUSH20x1a03
14e0JUMPI
14e1JUMPDEST
14e2PUSH40xffffffff
14e7PUSH10xe0
14e9DUP6
14eaADD
14ebMLOAD
14ecAND
14edISZERO
14eePUSH20x19d4
14f1JUMPI
14f2JUMPDEST
14f3PUSH10x01
14f5PUSH10x01
14f7PUSH10x40
14f9SHL
14faSUB
14fbDUP9
14fcAND
14fdPUSH20x0120
1500DUP6
1501ADD
1502MSTORE
1503PUSH20x1512
1506DUP5
1507MLOAD
1508PUSH10x20
150aDUP7
150bADD
150cMLOAD
150dSWAP1
150ePUSH20x5bf0
1511JUMP
1512JUMPDEST
1513DUP1
1514DUP8
1515MSTORE
1516PUSH10x09
1518PUSH10x20
151aMSTORE
151bPUSH10xff
151dPUSH10x40
151fDUP9
1520KECCAK256
1521SLOAD
1522AND
1523ISZERO
1524PUSH20x1967
1527JUMPI
1528JUMPDEST
1529DUP1
152aDUP8
152bMSTORE
152cPUSH10x08
152ePUSH10x20
1530MSTORE
1531PUSH10x40
1533DUP8
1534KECCAK256
1535SWAP3
1536DUP6
1537MLOAD
1538DUP5
1539SSTORE
153aPUSH10x20
153cDUP7
153dADD
153eMLOAD
153fPUSH10x01
1541DUP6
1542ADD
1543SSTORE
1544PUSH10xff
1546PUSH10x40
1548DUP8
1549ADD
154aMLOAD
154bAND
154cPUSH10xff
154ePUSH10x02
1550DUP7
1551ADD
1552SWAP2
1553AND
1554PUSH10xff
1556NOT
1557DUP3
1558SLOAD
1559AND
155aOR
155bSWAP1
155cSSTORE
155dPUSH10x60
155fDUP7
1560ADD
1561MLOAD
1562DUP1
1563MLOAD
1564SWAP1
1565PUSH10x01
1567PUSH10x01
1569PUSH10x40
156bSHL
156cSUB
156dDUP3
156eGT
156fPUSH20x1882
1572JUMPI
1573DUP2
1574SWAP1
1575PUSH20x1581
1578PUSH10x03
157aDUP9
157bADD
157cSLOAD
157dPUSH20x5d56
1580JUMP
1581JUMPDEST
1582PUSH10x1f
1584DUP2
1585GT
1586PUSH20x1909
1589JUMPI
158aJUMPDEST
158bPOP
158cPUSH10x20
158eSWAP1
158fPUSH10x1f
1591DUP4
1592GT
1593PUSH10x01
1595EQ
1596PUSH20x18a1
1599JUMPI
159aDUP12
159bSWAP3
159cPUSH20x1896
159fJUMPI
15a0JUMPDEST
15a1POP
15a2POP
15a3DUP2
15a4PUSH10x01
15a6SHL
15a7SWAP2
15a8PUSH0
15a9NOT
15aaSWAP1
15abPUSH10x03
15adSHL
15aeSHR
15afNOT
15b0AND
15b1OR
15b2PUSH10x03
15b4DUP6
15b5ADD
15b6SSTORE
15b7JUMPDEST
15b8PUSH10x80
15baDUP7
15bbADD
15bcMLOAD
15bdDUP1
15beMLOAD
15bfSWAP1
15c0PUSH10x01
15c2PUSH10x01
15c4PUSH10x40
15c6SHL
15c7SUB
15c8DUP3
15c9GT
15caPUSH20x1882
15cdJUMPI
15ceDUP8
15cfSWAP3
15d0SWAP2
15d1DUP10
15d2SWAP2
15d3PUSH20x15df
15d6PUSH10x04
15d8DUP10
15d9ADD
15daSLOAD
15dbPUSH20x5d56
15deJUMP
15dfJUMPDEST
15e0PUSH10x1f
15e2DUP2
15e3GT
15e4PUSH20x1819
15e7JUMPI
15e8JUMPDEST
15e9POP
15eaPUSH10x20
15ecSWAP1
15edDUP13
15eePUSH10x1f
15f0DUP5
15f1GT
15f2PUSH10x01
15f4EQ
15f5PUSH20x1771
15f8JUMPI
15f9SWAP4
15faPUSH20x172b
15fdDUP7
15fePUSH20x1725
1601PUSH10x05
1603PUSH10x01
1605SWAP14
1606DUP9
1607PUSH10xa0
1609SWAP10
160aPUSH20x1731
160dSWAP10
160ePUSH320xed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f
162fSWAP15
1630SWAP14
1631SWAP10
1632PUSH10xff
1634SWAP14
1635SWAP3
1636PUSH20x1766
1639JUMPI
163aJUMPDEST
163bPOP
163cPOP
163dPUSH10x01
163fDUP3
1640SWAP1
1641SHL
1642SWAP2
1643PUSH0
1644NOT
1645SWAP1
1646PUSH10x03
1648SHL
1649SHR
164aNOT
164bAND
164cOR
164dPUSH10x04
164fDUP3
1650ADD
1651SSTORE
1652JUMPDEST
1653ADD
1654SWAP9
1655DUP9
1656DUP1
1657DUP10
1658DUP4
1659ADD
165aMLOAD
165bAND
165cAND
165dDUP10
165eNOT
165fDUP12
1660SLOAD
1661AND
1662OR
1663DUP11
1664SSTORE
1665PUSH20x168f
1668PUSH40xffffffff
166dPUSH10xc0
166fDUP4
1670ADD
1671MLOAD
1672AND
1673DUP12
1674SWAP1
1675PUSH50xffffffff00
167bDUP3
167cSLOAD
167dSWAP2
167ePUSH10x08
1680SHL
1681AND
1682SWAP1
1683PUSH50xffffffff00
1689NOT
168aAND
168bOR
168cSWAP1
168dSSTORE
168eJUMP
168fJUMPDEST
1690PUSH10xe0
1692DUP2
1693ADD
1694MLOAD
1695DUP11
1696SLOAD
1697PUSH90xffffffff0000000000
16a1NOT
16a2AND
16a3PUSH10x28
16a5SWAP2
16a6SWAP1
16a7SWAP2
16a8SHL
16a9PUSH90xffffffff0000000000
16b3AND
16b4OR
16b5DUP11
16b6SSTORE
16b7PUSH20x0100
16baDUP2
16bbADD
16bcDUP1
16bdMLOAD
16beDUP12
16bfSLOAD
16c0PUSH20x0120
16c3DUP5
16c4ADD
16c5MLOAD
16c6PUSH180xffffffffffffffffff000000000000000000
16d9NOT
16daSWAP1
16dbSWAP2
16dcAND
16ddSWAP2
16deISZERO
16dfISZERO
16e0PUSH10x48
16e2SHL
16e3PUSH100xff000000000000000000
16eeAND
16efSWAP2
16f0SWAP1
16f1SWAP2
16f2OR
16f3PUSH10x50
16f5SWAP2
16f6SWAP1
16f7SWAP2
16f8SHL
16f9PUSH80xffffffffffffffff
1702PUSH10x50
1704SHL
1705AND
1706OR
1707SWAP1
1708SWAP11
1709SSTORE
170aPUSH20x1712
170dDUP13
170ePUSH20x5f5c
1711JUMP
1712JUMPDEST
1713PUSH20x171e
1716DUP5
1717PUSH10x80
1719MLOAD
171aPUSH20x587b
171dJUMP
171eJUMPDEST
171fMSTORE
1720DUP12
1721PUSH20x656a
1724JUMP
1725JUMPDEST
1726SWAP3
1727PUSH20x587b
172aJUMP
172bJUMPDEST
172cMSTORE
172dPUSH20x59e0
1730JUMP
1731JUMPDEST
1732SWAP9
1733ADD
1734MLOAD
1735SWAP2
1736MLOAD
1737PUSH10x40
1739DUP1
173aMLOAD
173bSWAP3
173cSWAP1
173dSWAP4
173eAND
173fPUSH10xff
1741AND
1742DUP3
1743MSTORE
1744ISZERO
1745ISZERO
1746PUSH10x20
1748DUP3
1749ADD
174aMSTORE
174bPUSH10x01
174dPUSH10x01
174fPUSH10x40
1751SHL
1752SUB
1753DUP13
1754AND
1755SWAP2
1756DUP2
1757ADD
1758SWAP2
1759SWAP1
175aSWAP2
175bMSTORE
175cPUSH10x60
175eSWAP1
175fLOG2
1760ADD
1761SWAP2
1762PUSH20x1480
1765JUMP
1766JUMPDEST
1767ADD
1768MLOAD
1769SWAP1
176aPOP
176bPUSH0
176cDUP1
176dPUSH20x163a
1770JUMP
1771JUMPDEST
1772POP
1773SWAP1
1774PUSH10x04
1776DUP10
1777ADD
1778DUP14
1779MSTORE
177aDUP1
177bDUP14
177cKECCAK256
177dSWAP2
177eDUP14
177fJUMPDEST
1780PUSH10x1f
1782NOT
1783DUP6
1784AND
1785DUP2
1786LT
1787PUSH20x17fb
178aJUMPI
178bPOP
178cSWAP4
178dPUSH20x172b
1790DUP7
1791PUSH20x1725
1794PUSH10x05
1796PUSH10x01
1798SWAP14
1799DUP15
179aDUP10
179bPUSH20x1731
179eSWAP10
179fPUSH320xed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f
17c0SWAP15
17c1SWAP14
17c2SWAP10
17c3PUSH10xff
17c5SWAP14
17c6PUSH10xa0
17c8SWAP14
17c9PUSH10x1f
17cbNOT
17ccDUP2
17cdAND
17ceLT
17cfPUSH20x17e3
17d2JUMPI
17d3JUMPDEST
17d4POP
17d5POP
17d6POP
17d7DUP2
17d8SHL
17d9ADD
17daPUSH10x04
17dcDUP3
17ddADD
17deSSTORE
17dfPUSH20x1652
17e2JUMP
17e3JUMPDEST
17e4ADD
17e5MLOAD
17e6PUSH0
17e7NOT
17e8PUSH10xf8
17eaDUP5
17ebPUSH10x03
17edSHL
17eeAND
17efSHR
17f0NOT
17f1AND
17f2SWAP1
17f3SSTORE
17f4PUSH0
17f5DUP1
17f6DUP1
17f7PUSH20x17d3
17faJUMP
17fbJUMPDEST
17fcDUP3
17fdDUP3
17feADD
17ffMLOAD
1800DUP5
1801SSTORE
1802DUP13
1803SWAP8
1804POP
1805DUP14
1806SWAP6
1807POP
1808PUSH10x01
180aSWAP1
180bSWAP4
180cADD
180dSWAP3
180ePUSH10x20
1810SWAP3
1811DUP4
1812ADD
1813SWAP3
1814ADD
1815PUSH20x177f
1818JUMP
1819JUMPDEST
181aDUP3
181bDUP2
181cGT
181dISZERO
181ePUSH20x15e8
1821JUMPI
1822SWAP2
1823SWAP4
1824SWAP5
1825POP
1826SWAP2
1827POP
1828PUSH10x04
182aDUP8
182bADD
182cDUP12
182dMSTORE
182ePUSH10x20
1830DUP12
1831KECCAK256
1832PUSH10x1f
1834DUP5
1835ADD
1836PUSH10x05
1838SHR
1839SWAP1
183aPUSH10x20
183cDUP6
183dLT
183ePUSH20x187a
1841JUMPI
1842JUMPDEST
1843SWAP2
1844DUP12
1845SWAP4
1846SWAP2
1847DUP12
1848SWAP7
1849SWAP6
184aSWAP4
184bDUP15
184cDUP1
184dJUMPDEST
184eDUP4
184fPUSH10x1f
1851DUP7
1852ADD
1853PUSH10x05
1855SHR
1856SUB
1857DUP3
1858LT
1859PUSH20x1865
185cJUMPI