Final Testnetexplorer K_J · Final Testnet · 48359
en

Contract

0x11697064ea282a8cc37a3200601f1d23b51a60ff

Address
0x11697064ea282a8cc37a3200601f1d23b51a60ff
Kind
verified contract FinalAccountLedger
Balance
0 vETH
Nonce
1
Code
28,017 bytes codehash 0x082715619e61892026a162a5237efa2f75dc30d6f375f8437d3e6afa8f40b80d

account tree

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

source verified

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

contracts/finalchain/FinalAccountLedger.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy this account ledger as the account plane of
//    a Final DeFi Protocol state-plane chain, and may submit authorization
//    requests to it on behalf of any account holder — submission carries no
//    authority, so this permission grants none.
// 2. Holders, guardians, integrators, indexers and operators may read every
//    record, nonce, and account-state leaf this ledger publishes, and may build
//    clients that produce the credentials it verifies.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this account ledger or a competing account-state
//    authority for Final Wallets without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalChainTime} from "./FinalChainTime.sol";
import {FinalIdentityRegistry, DOMAIN_IDENTITY_LEAF} from "./FinalIdentityRegistry.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalStateTrees} from "./FinalStateTrees.sol";
import {FinalPlaneSweep} from "./FinalPlaneSweep.sol";

/**
 * @title Final Account Ledger
 * @notice The account plane of the Final DeFi Protocol: who owns each Final Wallet, which post-quantum key
 *         commitments authorize it, whether it is frozen, and on which chains it exists.
 * @dev This is the single decision point for every account transition — freeze, unfreeze, key rotation, rotation
 *      cancel, guardian change, owner transfer and post-quantum migration. It deploys ONLY to the protocol's own
 *      reth-based chains, because it verifies signatures inside precompiles that exist nowhere else: SHA3-256 at
 *      `0x0202`, ML-DSA-87 at `0x0204` and SLH-DSA-SHAKE-256s at `0x0205`, each addressed by its FIPS number. The
 *      constructor probes for them and refuses to deploy where they are absent. Gas is deliberately NOT a design
 *      constraint on that chain, and this contract is not written as though it were: a verdict re-derivable from
 *      public state is worth more here than a cheaper one that is only a claim by whichever process ran a library.
 *      It participates in no CREATE2 derivation, and nothing outside this state plane imports it.
 *
 *      **The authorization model.** Every transition after genesis is authorized by the HOLDER's own
 *      SLH-DSA-SHAKE-256s ACCESS credential. A request carries a public key and a signature; this contract hashes
 *      the key, matches that digest against a commitment the chain ALREADY holds for the account, and verifies the
 *      signature in the precompile. Which stored commitment matched is what NAMES the actor, so a caller can never
 *      select its own authority: only the recovery commitment resolves to `RECOVERY_KEY`, and the strongest thing
 *      the live key can assert is `LIVE_KEY`. Only the ACCESS class is accepted — the ML-DSA-87 transaction key of
 *      the same stage verifies perfectly well under its own algorithm and is still refused, because splitting the
 *      two classes buys nothing if a key that signs spends can also rotate the credential set.
 *
 *      **Submission is permissionless, and that separation is the point.** `submitRequest` may be called by
 *      anyone. Whoever submits pays the gas and carries no authority whatsoever: they cannot choose the actor,
 *      cannot alter the payload the digest commits to, and cannot make a refused transition succeed. There is
 *      therefore no privileged writer to compromise, and no operator whose silence can withhold a holder's remedy
 *      — a holder rotating a stolen key needs nobody's approval, only their own recovery credential.
 *
 *      **Verify, apply and publish happen in ONE transaction.** The same call that accepts a credential writes the
 *      account's leaf into tree 1 of `FinalStateTrees`, which is the root every execution chain projects authority
 *      from. There is no window in which the record and the published tree disagree, and no separate publication
 *      step that could describe a different history. A genesis additionally writes the account's admission leaf
 *      into the identity tree and the owner index beside it, so an account that exists here is creatable on an
 *      execution chain and one that does not exist here is creatable nowhere.
 *
 *      **Genesis is the single exception, and it takes a quorum.** `openAccount` is authorized by a K-of-N
 *      ML-DSA-87 quorum over the configured opener role, because issuance happens at a trust boundary this chain
 *      cannot observe: the certificate that fixed an account's commitments was produced elsewhere, and somebody
 *      has to attest that it existed. The grant is bounded to exactly that. After genesis no set of protocol keys
 *      can move an account, and the quorum cannot rewrite one it has already opened.
 *
 *      **There are no local authority writers anywhere else in the system.** Owner transfer, key rotation and
 *      post-quantum migration are ledger actions here — `TRANSFER_OWNER`, the rotation pair, `ENABLE_PQ` — and
 *      their selectors are deliberately off every execution-chain allowlist. An execution chain verifies a proven
 *      leaf against a published root; it never writes one, and it holds no copy that could drift from this record.
 *
 *      **Guardians delay a rotation; they do not veto it.** A guardian set can freeze an account and can cancel a
 *      pending rotation, each at its own threshold, and the cancel budget is bounded by `MAX_ROTATION_CANCELS` so
 *      that a captured guardian set cannot become a permanent lockout. Guardians cannot unfreeze, cannot rotate
 *      and cannot transfer ownership: they hold the fail-closed half only, and every power they do hold is
 *      self-limiting in the direction that protects the holder.
 *
 *      **What this deliberately does NOT do.** There is no owner, no pause, and no admin setter that rewrites an
 *      account — a ledger whose operator can rewrite an owner is not a record of ownership. It does not delegate
 *      to `FinalIdentityRegistry`: service wallets are projected into tree 1 by `FinalStateTrees.syncIdentities`
 *      and are not accounts here, because they have no holder to sign for them and giving them one would make the
 *      fleet's own credentials movable by the fleet. It holds no assets, meters nothing per request, and exposes
 *      no upgrade path.
 *
 *      **Immutable, with no proxy in front of it.** `registry` and `trees` are immutable constructor arguments, so
 *      the genesis quorum can never be pointed at a registry supplied in calldata. Any change to this contract's
 *      surface is therefore a redeploy at a new address: the trees' writer for tree 1 is re-pointed at the
 *      successor and every registration is replayed through `restoreAccount`, which is why that lane exists and
 *      why `sealRestore` closes it once the replay is verified.
 */
contract FinalAccountLedger is FinalPlaneSweep {
    // ------------------------------------------------------------ constants

    /// @notice EIP-712 domain salt for every authorization request this ledger accepts.
    /// @dev Distinct from the wallet-side `GuardianFreeze_v01` salt on purpose. That signature freezes ONE chain's
    ///      copy of an account as a liveness backstop; this one freezes the account globally, everywhere at once.
    ///      A guardian asked for the local freeze must not thereby have authorized the global one, and a distinct
    ///      salt is what makes the two signatures non-interchangeable rather than merely differently intended.
    bytes32 public constant DOMAIN_ACCOUNT_STATE_REQUEST =
        keccak256("FINAL_ACCOUNT_STATE_REQUEST_v01");

    /// @notice EIP-712 domain typehash: `EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)`.
    /// @dev Carries no `verifyingContract` field. The domain binds the chain and the salt, not this address, so a
    ///      credential a holder has already produced stays verifiable across a redeploy of the ledger — which is
    ///      what makes `restoreAccount` able to carry per-action nonces forward instead of resetting them.
    bytes32 private constant EIP712_DOMAIN_TYPEHASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)");
    /// @notice The EIP-712 domain `name` field, pre-hashed.
    /// @dev A client that derives a different name derives a different digest and produces signatures this
    ///      contract rejects, so the string is fixed here and mirrored by every credential producer.
    bytes32 private constant DOMAIN_NAME = keccak256("FinalChainAccountState");
    /// @notice The EIP-712 domain `version` field, pre-hashed.
    /// @dev Moving it invalidates every outstanding authorization at once, which is the only reason to move it.
    bytes32 private constant DOMAIN_VERSION = keccak256("01");

    /// @notice The EIP-712 struct typehash a request is signed under.
    /// @dev The action travels as a STRING and the arguments as a hash of their ABI encoding, so the digest binds
    ///      what the holder meant and what they meant it about. Adding, removing or reordering a field changes the
    ///      typehash and therefore every digest, so this string and `_actionName` move together or not at all.
    bytes32 private constant REQUEST_TYPEHASH = keccak256(
        "AccountStateRequest(address wallet,string action,bytes32 payloadHash,uint64 nonce,uint64 expiresAt)"
    );

    /// @notice What a co-signer's approval authorizes at genesis.
    /// @dev Per-action, so an approval to open cannot be replayed as an approval for any other quorum here.
    bytes32 private constant ACTION_OPEN = keccak256("FinalAccountLedger.open.v01");
    /// @notice The registrar-quorum action that authorizes `configure`.
    /// @dev Verified by the registry with this ledger named as the verifying contract, so an approval collected
    ///      for one deployment cannot configure another.
    bytes32 public constant ACTION_CONFIGURE = keccak256("FINAL_ACCOUNT_LEDGER_CONFIGURE_v01");
    /// @notice The identity tree this ledger writes admission leaves into.
    /// @dev Restated as a constant rather than read from `FinalStateTrees` on every batch: the pair is fixed for
    ///      the life of the plane, and a value that cannot change should not cost an external call to learn.
    uint8 internal constant TREE_IDENTITY_ID = 8;
    /// @notice The identity tree's main branch — one admission leaf per account.
    /// @dev Must equal the trees' own branch id; a mismatch writes a leaf no gateway would ever look for.
    uint8 internal constant BRANCH_MAIN_ID = 1;
    /// @notice The identity tree's second branch — the owner → wallets index.
    uint8 internal constant BRANCH_OWNER_INDEX_ID = 2;
    /// @notice Key domain for an owner-index leaf.
    /// @dev Restated from the trees so the write costs two hashes rather than two external calls. It must stay
    ///      byte-identical to the trees' own key derivation, or the leaf lands at a key nothing reads.
    bytes32 private constant DOMAIN_OWNER_INDEX_KEY = keccak256("FinalStateTrees.key.ownerIndex.v01");
    /// @notice Leaf domain for an owner-index leaf.
    /// @dev Byte-identical to the trees' own leaf derivation for the same reason as `DOMAIN_OWNER_INDEX_KEY`: a
    ///      reader proving against this branch folds the preimage itself and would prove nothing on a mismatch.
    bytes32 private constant DOMAIN_OWNER_INDEX_LEAF = keccak256("FINAL_OWNER_INDEX_LEAF_v01");
    /// @notice The restore lane's quorum action.
    /// @dev The opener quorum authorizes it, but under its own action and its own nonce, so an approval to open a
    ///      fresh account can never be replayed as an approval to re-create an existing record.
    bytes32 private constant ACTION_RESTORE = keccak256("FinalAccountLedger.restore.v01");
    /// @notice The configuration authority's action that closes the restore lane for good.
    bytes32 public constant ACTION_SEAL_RESTORE = keccak256("FINAL_ACCOUNT_LEDGER_SEAL_RESTORE_v01");

    /// @notice Shortest delay an account may set between initiating a rotation or guardian change and finalizing
    ///         it. A zero delay would remove the cancel window entirely, and with it the only defence a guardian
    ///         set has against a compromised recovery key.
    /// @dev MILLISECONDS, like every timestamp on this chain. `block.timestamp` here counts milliseconds, so a
    ///      second-denominated delay compared against it is a thousand times too short — a nominal day would
    ///      elapse in under a minute-and-a-half. Written as `hours * MS_PER_SECOND` so the unit is explicit in
    ///      the source rather than carried in a reader's head.
    uint64 public constant MIN_DELAY_MS = 1 hours * FinalChainTime.MS_PER_SECOND;
    /// @notice Longest delay an account may set. An unbounded one would let a hostile guardian configuration
    ///         strand a legitimate rotation forever, which is the same lockout the cancel budget exists to bound.
    /// @dev Milliseconds, as `MIN_DELAY_MS`.
    uint64 public constant MAX_DELAY_MS = 30 days * FinalChainTime.MS_PER_SECOND;
    /// @notice The delay an account gets when its genesis names none.
    /// @dev Milliseconds, as `MIN_DELAY_MS`. Chosen so the default is a full day of standing to object rather than
    ///      the minimum the bounds allow.
    uint64 public constant DEFAULT_DELAY_MS = 24 hours * FinalChainTime.MS_PER_SECOND;

    /// @notice How many times a guardian set may cancel one rotation before it proceeds anyway.
    /// @dev Guardians DELAY a rotation; they do not veto it. An unbounded cancel would make a captured guardian
    ///      set a permanent lockout — strictly worse than having no guardians at all, because the holder
    ///      configured the set believing it helped. Each cancel restarts the clock rather than dropping the
    ///      request, so the budget buys the guardians time to react and never the holder's remedy itself.
    uint8 public constant MAX_ROTATION_CANCELS = 2;

    /// @notice The longest a request may stay valid, measured from the block that applies it.
    /// @dev A nonce alone does not bound an UNUSED authorization: a guardian's freeze signature at some nonce
    ///      stays spendable for as long as nothing else freezes, which turns a one-off approval into a standing
    ///      power held by whoever has the bytes. An explicit ceiling on the expiry span closes that.
    uint64 public constant MAX_REQUEST_TTL_MS = 7 days * FinalChainTime.MS_PER_SECOND;

    /// @notice The post-quantum algorithm id of the ACCESS class: SLH-DSA-SHAKE-256s, FIPS 205.
    /// @dev Must equal the quorum library's own id for the same algorithm and the id every credential producer
    ///      stamps into its blob. The transaction class (ML-DSA-87, FIPS 204) is refused here on purpose: a key
    ///      that signs spends does not get to rotate a credential set, its own or anyone else's.
    uint8 public constant ALG_SLH_DSA_SHAKE_256S = 5;

    /// @notice The owner value every post-quantum account carries, on this chain and on every execution chain.
    /// @dev Must equal the wallet-side constant of the same name. It is declared here rather than imported
    ///      because the contracts in this directory deploy only to the protocol's own chains, and importing a
    ///      wallet-side compilation unit would couple two deploy targets that share nothing else; the two
    ///      declarations are held equal by a parity check, since a mismatch is an owner field no execution chain
    ///      agrees with and nothing on either side would point at the cause.
    ///
    ///      Unspendable by construction: recovering an ECDSA signature to a chosen twenty-byte value is a ~2^160
    ///      search, and the address holds no code, so the ERC-1271 branch is unreachable as well. A migrated
    ///      account's owner field is therefore a settlement destination and a marker, never an authority.
    address public constant FINAL_PQ_NATIVE_OWNER = 0x00000000000000000000000000000046494e414c;

    // ------------------------------------------------------------- vocabulary

    /// @notice Every account transition this ledger can be asked to apply.
    /// @dev The ORDER is load-bearing twice over: an action's ordinal indexes its per-account nonce in `nonceOf`,
    ///      and `_actionName` maps that ordinal to the exact string the EIP-712 digest hashes. Inserting one in
    ///      the middle renumbers every stored nonce and re-points every authorization a holder has already
    ///      signed, so APPENDING is the only safe direction and `ACTION_COUNT` moves with it.
    enum Action {
        /// @dev Stop the account everywhere. Guardian-authorized at `threshold`, because a freeze is
        ///      fail-closed: its worst misuse is denial of service against an account its own guardians chose.
        FREEZE,
        /// @dev Lift a freeze. Recovery-key only — guardians deliberately cannot reach it, since lifting a
        ///      freeze un-protects an account that may be mid-theft, which is not bounded at all.
        UNFREEZE,
        /// @dev Stage the committed recovery pair for promotion. Recovery-key only; starts the delay clock.
        INITIATE_ROTATION,
        /// @dev Object to a pending rotation, at `cancelThreshold` and against `MAX_ROTATION_CANCELS`.
        CANCEL_ROTATION,
        /// @dev Promote the staged pair once the delay has run. Permissionless: the outcome is already decided.
        FINALIZE_ROTATION,
        /// @dev Stage a replacement guardian set, thresholds included. Recovery-key only.
        INITIATE_GUARDIAN_CHANGE,
        /// @dev Objection by the OUTGOING guardian set — the members being removed — at `cancelThreshold`.
        CANCEL_GUARDIAN_CHANGE,
        /// @dev Install the staged guardian set once the delay has run. Permissionless, as the rotation is.
        FINALIZE_GUARDIAN_CHANGE,
        /// @dev Hand the account to a new owner. Live-key only, and refused while the account is frozen.
        TRANSFER_OWNER,
        /// @dev Migrate the account to post-quantum authorization. Live-key only, one-way, and takes no key
        ///      material: the commitments were fixed at issuance and are already in this record.
        ENABLE_PQ,
        /// @dev RETIRED 2026-09-08 — `submitRequest` refuses it with `ActionRetired` for every credential. It set or
        ///      replaced the account's row for one chain in the published `deployedChains` table, which creation
        ///      used to read for admission; creation now admits on the proven leaf alone (included under an accepted account
        ///      root, not frozen), so the holder declares nothing per chain. The ordinal stays: the enum is appended to,
        ///      never reordered, because ordinals index the per-action nonces every signed request commits to and
        ///      `Restored.nonces` carries `ACTION_COUNT` words.
        SET_CHAIN_ACCOUNT
    }

    /// @notice How many actions `Action` declares.
    /// @dev Sizes the fixed-length arrays `noncesOf` and `Restored.nonces` return and consume. Must equal the
    ///      number of `Action` variants: a stale value truncates a restore or reads past the actions in use.
    uint8 public constant ACTION_COUNT = 11;

    /// @notice Who a verified credential establishes, as this ledger resolved it.
    /// @dev Never claimed by the caller and never carried in the request — it is the OUTPUT of matching a
    ///      presented key against a stored commitment. `NONE` is the permissionless outcome used by the two
    ///      finalize actions, where the delay has already decided the result.
    enum Actor { NONE, RECOVERY_KEY, LIVE_KEY, GUARDIANS }

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

    /// @notice One authorization request: what should happen, to which account, and under which nonce.
    /// @dev Every field is committed to by `requestDigest`, so none of them can be altered by a submitter
    ///      without invalidating the credential that authorizes them.
    struct Request {
        /// @dev The account the transition applies to. Must already be open.
        address wallet;
        /// @dev The transition being asked for. Selects both the nonce domain and the actor the ledger demands.
        Action action;
        /// @dev The account's current nonce FOR THIS ACTION. Burned only when the transition is accepted, so a
        ///      refused request leaves its authorization spendable.
        uint64 nonce;
        /// @dev Absolute expiry in milliseconds. Must be in the future and no further out than
        ///      `MAX_REQUEST_TTL_MS`, so an unused approval cannot become a standing power.
        uint64 expiresAt;
        /// @dev The action's arguments, ABI-encoded exactly as the digest commits to them. Empty for the actions
        ///      that take none.
        bytes payload;
    }

    /// @notice The holder's credential. Exactly one field is populated; supplying both is refused.
    /// @dev This is what NAMES the actor. The ledger does not read a claim from the request — it matches the key
    ///      inside the credential against a commitment it already holds and reports which one matched.
    struct Credential {
        /// @dev `abi.encode(uint8 algorithmId, bytes publicKey, bytes signature)` for the ACCESS class. The
        ///      algorithm id must be `ALG_SLH_DSA_SHAKE_256S`; a transaction-class key is refused even though it
        ///      would verify under its own algorithm.
        bytes pqBlob;
        /// @dev The pre-quantum path: a 65-byte ECDSA signature from the account's `owner`, accepted only while
        ///      the account has not migrated. It resolves to `LIVE_KEY` and never to `RECOVERY_KEY`.
        bytes ownerSignature;
    }

    /// @notice One guardian's authorization over a request digest.
    /// @dev Three forms, and which one applies is decided from ledger state alone rather than from anything the
    ///      caller labels. A guardian of a post-quantum wallet must itself be a post-quantum Final Wallet, so most
    ///      guardians are CONTRACTS and cannot ECDSA-recover to their own address; on an execution chain that is
    ///      an ERC-1271 fallback, and here it is a lookup, because a guardian that is a Final Wallet is itself an
    ///      account in this ledger and its commitments are already known.
    ///
    ///        1. `guardian == 0`         — an EOA guardian. Recover, and the recovered address IS the guardian.
    ///        2. `guardian`, `pqBlob`    — a post-quantum Final Wallet, signing with its own LIVE access key.
    ///        3. `guardian`, `signature` — a pre-quantum Final Wallet, whose ECDSA owner signs.
    struct GuardianAuth {
        /// @dev The guardian being spoken for, or zero to mean "recover it from the signature".
        address guardian;
        /// @dev ACCESS-class blob signed by the named guardian's own live key. Form 2.
        bytes pqBlob;
        /// @dev ECDSA signature — the guardian itself in form 1, that guardian wallet's owner in form 3.
        bytes signature;
    }

    /// @notice Everything the ledger holds about one account except its guardian lists.
    /// @dev Packed into `ACCOUNT_WORDS` storage words, and the packing is load-bearing: `accountWords` exports
    ///      the record as raw words and `restoreAccount` writes them straight back, so a field added in the middle
    ///      or a type widened without a matching `ACCOUNT_WORDS` bump silently truncates a restore. Nothing here
    ///      is writable except through an authorized transition.
    struct Account {
        /// @dev Set at genesis and never cleared. Every entrypoint that touches an account checks it first, so
        ///      an unopened address reads as absent rather than as an account with default values.
        bool opened;
        /// @dev Commitment to the LIVE SLH-DSA-SHAKE-256s access key: what authorizes ordinary account actions.
        bytes32 liveAccess;
        /// @dev Commitment to the LIVE ML-DSA-87 transaction key. Published for execution chains; never accepted
        ///      as authorization here.
        bytes32 liveTransaction;
        /// @dev Commitment to the RECOVERY access key: what authorizes an unfreeze, a rotation, or a guardian
        ///      change. Matching it is the only way to become `RECOVERY_KEY`.
        bytes32 recoveryAccess;
        /// @dev Commitment to the RECOVERY transaction key, pre-committed so a promotion needs no key argument.
        bytes32 recoveryTransaction;
        /// @dev The genesis certificate serial — the raw half of the admission leaf's preimage. Kept so this
        ///      record alone can re-derive admission on a successor plane, since the leaf hash is not invertible.
        ///      Never mutated: the certificate is what determined the account's address.
        bytes32 serial;
        /// @dev Commitment to the LIVE encapsulation key — what a sender seals an envelope to.
        bytes32 liveKem;
        /// @dev Commitment to the pre-committed successor `liveKem` rotates into. Present for the same reason
        ///      `recoveryTransaction` is: a rotation supplies no key ARGUMENT and so cannot be handed a key
        ///      nobody vouched for.
        bytes32 recoveryKem;
        /// @dev Which generation of `liveKem` this is: one at genesis, incremented on every promotion.
        ///
        ///      This is the value an encrypted intent header names, and it is a KEM-rotation counter rather than
        ///      the account `version` below. `version` moves on every transition — a freeze, a guardian change, a
        ///      dormancy refresh — so pinning a sealed envelope to it would expire that envelope for reasons that
        ///      have nothing to do with the key it was sealed to.
        ///
        ///      It exists because an intent can sit encrypted for a long time and a rotation inside that window
        ///      would strand it: the sender sealed to a key the account has since disowned, and the intent then
        ///      simply never decrypts — no revert, no error, nothing to look at. The version lets a recipient say
        ///      WHICH key an envelope was sealed to and a reader say whether that key is still current.
        ///
        ///      `uint16` is honest here in a way it would not be for `version`, because it advances only when the
        ///      encapsulation key actually rotates.
        uint16 kemVersion;
        /// @dev Per-chain dormancy verdict, one bit per asset-registry slot. Derived here from `lastActivityAt`
        ///      and the per-chain threshold; the execution chains hold neither input and read only the bit. Which
        ///      chains the account exists on — and as what — is the separate `(chainRef, account)` table, because
        ///      a set of identities is not a set of verdicts and the two move on different cadences.
        uint32 dormantChains;
        /// @dev Newest evidence of the holder acting, on ANY chain. Monotone FORWARD and permissionless: anyone
        ///      may push it later, nobody may push it back. Understating liveness is the only dangerous
        ///      direction, because it manufactures dormancy against a holder who is still there, and the monotone
        ///      rule puts that out of reach of everyone — the holder included, who can always stamp it directly.
        ///      Overstating merely delays a legitimate recovery, which fails safe.
        uint64 lastActivityAt;
        /// @dev The account's owner. `FINAL_PQ_NATIVE_OWNER` once migrated, at which point it is a settlement
        ///      destination and a marker rather than an authority.
        address owner;
        /// @dev Whether the account has migrated to post-quantum authorization. One-way.
        bool pqEnabled;
        /// @dev Whether the account is frozen. Published in the leaf, so every chain refuses on the PROVEN value
        ///      rather than on a local copy that is only as fresh as the last fan-out.
        bool frozen;
        /// @dev Incremented on every accepted transition and on every published dormancy change. It orders the
        ///      record's history; it is deliberately NOT what an authorization binds to, since anything that can
        ///      move the state could otherwise invalidate everyone else's in-flight signatures.
        uint64 version;
        /// @dev This account's delay for a rotation or a guardian change, in milliseconds, within
        ///      `MIN_DELAY_MS`..`MAX_DELAY_MS`.
        uint64 delayMs;
        /// @dev How many distinct guardians must authorize a freeze. Zero means the account configured no
        ///      guardian set, and every guardian action is then refused outright.
        uint16 threshold;
        /// @dev How many must authorize a cancel. Always strictly greater than `threshold`: freezing is
        ///      fail-closed, cancelling can block the holder's own remedy, so one hostile guardian must not
        ///      manage it alone.
        uint16 cancelThreshold;
        /// @dev Whether a key rotation is staged and waiting out its delay.
        bool rotationPending;
        /// @dev How much of the cancel budget this rotation has already spent, against `MAX_ROTATION_CANCELS`.
        uint8 rotationCancels;
        /// @dev When the pending rotation's clock last started, in milliseconds. Reset by each cancel.
        uint64 rotationInitiatedAt;
        /// @dev Staged successor to `recoveryAccess`, installed at finalize.
        bytes32 pendingRecoveryAccess;
        /// @dev Staged successor to `recoveryTransaction`, installed at finalize.
        bytes32 pendingRecoveryTransaction;
        /// @dev Staged successor to `recoveryKem`, required alongside the other two. A rotation that promoted the
        ///      signing pair without the encapsulation key would leave the account with new keys and an old KEM,
        ///      and nothing would fail loudly: intents addressed to it would simply never decrypt.
        bytes32 pendingRecoveryKem;
        /// @dev Whether a guardian change is staged and waiting out its delay.
        bool guardianChangePending;
        /// @dev The freeze threshold the staged guardian set would install.
        uint16 pendingThreshold;
        /// @dev The cancel threshold the staged guardian set would install.
        uint16 pendingCancelThreshold;
        /// @dev When the pending guardian change's clock started, in milliseconds.
        uint64 guardianChangeInitiatedAt;
    }

    /// @notice What `openAccount` installs: an account at genesis, and the only shape a protocol quorum may ever
    ///         write into this ledger.
    /// @dev Every commitment here is attested rather than proven, which is why the quorum is bounded to this one
    ///      call. Nothing in this struct can be revised afterwards by the same authority — the holder's own
    ///      credentials are the only thing that moves an account once it is open.
    struct Genesis {
        /// @dev The account being opened. Refused if this ledger already knows it.
        address wallet;
        /// @dev Commitment to the account's live access key, from the issued certificate.
        bytes32 liveAccess;
        /// @dev Commitment to the account's live transaction key, from the issued certificate.
        bytes32 liveTransaction;
        /// @dev Commitment to the account's recovery access key, from the issued certificate.
        bytes32 recoveryAccess;
        /// @dev Commitment to the account's recovery transaction key, from the issued certificate.
        bytes32 recoveryTransaction;
        /// @dev Commitment to the live encapsulation key. Required at genesis: an account opened without one
        ///      could receive nothing.
        bytes32 liveKem;
        /// @dev Commitment to the recovery encapsulation key. Required at genesis for the mirror reason: an
        ///      account without one would have no successor to rotate into.
        bytes32 recoveryKem;
        /// @dev The certificate serial. With the six commitments above it completes the identity-leaf preimage —
        ///      the identity domain, the serial, and the issuer's fold of those commitments — which is the exact
        ///      certificate digest an execution chain's CREATE2 derivation uses, and which this ledger writes
        ///      into the identity tree at open. Without it the account exists on this chain and no execution
        ///      chain would ever admit its creation.
        bytes32 serial;
        /// @dev The account's owner at genesis.
        address owner;
        /// @dev Whether the account is post-quantum from birth rather than migrating later.
        bool pqEnabled;
        /// @dev The chains this account exists on at genesis, and its account on each — the identity's statement
        ///      of where it exists AS WHAT, which a zero settlement beneficiary resolves through toward a chain
        ///      whose accounts are not EVM addresses. Creation reads no row of it (phase 2, 2026-09-08), and with
        ///      `SET_CHAIN_ACCOUNT` retired genesis is the table's only writer (`restoreAccount` re-applies it). An
        ///      empty list is refused, because it means the opener never decided rather than that it decided "none".
        FinalStateTrees.ChainAccount[] deployedChains;
        /// @dev The account's rotation and guardian-change delay in milliseconds. Zero takes `DEFAULT_DELAY_MS`;
        ///      anything outside `MIN_DELAY_MS`..`MAX_DELAY_MS` is refused.
        uint64 delayMs;
        /// @dev The genesis guardian set. It lands immediately, which is the one exception to the delay rule:
        ///      there is no outgoing set to object, and a delay here would protect nobody while leaving a fresh
        ///      account unguarded through exactly the period it is least able to defend itself.
        address[] guardians;
        /// @dev How many of those guardians must authorize a freeze.
        uint16 threshold;
        /// @dev How many must authorize a cancel. Zero asks this contract to derive the strict-majority default.
        uint16 cancelThreshold;
    }

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

    /// @notice The membership registry where every co-signer, key and role is resolved.
    /// @dev Immutable, so the genesis quorum can never be pointed at a registry supplied in calldata — a quorum
    ///      that could name its own membership source is not a quorum. It is also the source the inherited sweep
    ///      authority reads, so configuration and rescue answer to one place.
    FinalIdentityRegistry public immutable registry;
    /// @notice The state trees this ledger publishes into. It is the registered writer of the account tree.
    /// @dev Immutable for the same reason as `registry`. Written on every accepted transition, so the record and
    ///      the published root can never describe different histories.
    FinalStateTrees public immutable trees;

    /// @notice The registry role whose members may attest a genesis.
    /// @dev Set by `configure` and re-settable, because a co-signer set that grows or shrinks has to be able to
    ///      move with it. It governs `openAccount` and `restoreAccount` and nothing else.
    uint256 public openerRole;
    /// @notice How many attestations one genesis needs. Zero refuses every open.
    /// @dev An unconfigured ledger therefore fails closed rather than opening accounts under a one-of-none.
    uint256 public openThreshold;
    /// @notice Replay counter bound into every genesis digest. One per `openAccount` call, whatever the batch.
    uint64 public openNonce;

    /// @notice Every account's record, by wallet address.
    /// @dev Private because `accountOf`, `recordOf` and `accountWords` are the read surfaces, and each of them
    ///      refuses an address this ledger never opened rather than returning a zeroed record that reads as one.
    mapping(address => Account) private _accounts;
    /// @notice The live guardian set of each account, in the order it was installed.
    /// @dev Membership is checked by linear scan on the RESOLVED guardian, so ordering carries no authority.
    mapping(address => address[]) private _guardians;
    /// @notice The guardian set a pending change would install, per account. Cleared on cancel and on finalize.
    mapping(address => address[]) private _pendingGuardians;
    /// @notice Next authorization nonce, per account and per action ordinal.
    /// @dev Per ACTION, not per account, and deliberately not the record's `version`. `version` advances on every
    ///      accepted transition, so binding an authorization to it would let anyone who can move the state
    ///      cheaply invalidate everyone else's in-flight signatures. `TRANSFER_OWNER` sits with the LIVE key, so
    ///      a thief holding that key could otherwise race transfers to keep the guardians' freeze signatures
    ///      perpetually stale — griefing away the exact defence aimed at them.
    mapping(address => mapping(uint8 => uint64)) public nonceOf;

    /// @notice Per-chain inactivity threshold by registry slot, in milliseconds. Zero disables dormancy there.
    /// @dev Held HERE and nowhere else. The execution chains carry no threshold at all, which is the point: five
    ///      chains holding five copies of one decision is five chances for them to disagree about when an account
    ///      is abandoned. A zero threshold also CLEARS any bit already set, so disabling the policy cannot leave
    ///      accounts stranded dormant with no way back.
    /// @dev `uint64` rather than a narrower type because a multi-year threshold in milliseconds does not fit
    ///      `uint32` at all.
    mapping(uint8 => uint64) public inactivityThresholdOf;

    /// @notice Chains an account is known to exist on, as OBSERVED by a relayer that saw its creation there.
    /// @dev Kept off the published leaf deliberately: this is the fan-out's target set, not an authorization, and
    ///      putting it in the leaf would bump `version` — and age every outstanding proof — on every new
    ///      deployment of an account that has not otherwise changed.
    mapping(address => bytes32[]) private _deployments;
    /// @notice Membership half of `_deployments`, so an observation is idempotent without a scan.
    mapping(address => mapping(bytes32 => bool)) private _hasDeployment;

    /// @notice The holder's `(chainRef, account)` table — the published leaf's `deployedChains`.
    /// @dev Written at genesis and re-applied by `restoreAccount`; the live action that extended it
    ///      (`SET_CHAIN_ACCOUNT`) is retired. Rows are replaced, never removed: removing one strands assets at an
    ///      account nothing can resolve from the leaf any more.
    mapping(address => FinalStateTrees.ChainAccount[]) private _chainAccounts;

    /// @notice Every opened account, in the order it was opened. Paged through `walletsBetween`.
    address[] private _wallets;
    /// @notice The owner → wallets index: every account an owner holds, in the order they arrived.
    /// @dev The identity tree's second branch commits to exactly this array, so the leaf is recomputed over it
    ///      whenever an owner is touched. `walletsByOwner` is the readable half a reader asks first.
    mapping(address owner => address[]) private _walletsByOwner;
    /// @notice Position plus one of a wallet inside its owner's array; zero means it is not indexed.
    /// @dev Plus one so that the default zero is unambiguously "absent" rather than "at the front".
    mapping(address wallet => uint256) private _ownerSlotPlusOne;
    /// @notice Replay counter for the restore lane. One per `restoreAccount` call.
    uint64 public restoreNonce;
    /// @notice True once `sealRestore` has run.
    /// @dev The lane that re-creates registrations carried over from a predecessor ledger is then closed for the
    ///      life of this contract, and every account from that point on enters through `openAccount`.
    bool public restoreSealed;

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

    /// @notice An account was registered at genesis under the opener quorum.
    /// @param wallet The account now open.
    /// @param owner Its owner at genesis.
    /// @param pqEnabled Whether it is post-quantum from birth rather than migrating later.
    event AccountOpened(address indexed wallet, address indexed owner, bool pqEnabled);
    /// @notice A registration carried over from a predecessor ledger was re-created verbatim.
    /// @param wallet The account now open here.
    /// @param owner Its owner as the carried record holds it, which need not be its owner at genesis.
    /// @param version The record's version as carried, so a reader can see the history was preserved.
    event AccountRestored(address indexed wallet, address indexed owner, uint64 version);
    /// @notice The restore lane was closed for the life of this ledger.
    event RestoreSealed();
    /// @notice A request was authorized and applied, and the account's leaf republished.
    /// @param wallet The account that moved.
    /// @param action Which transition was applied.
    /// @param actor Who this ledger RESOLVED the credential to. Never what the request claimed.
    /// @param version The record's version after the transition.
    event RequestApplied(address indexed wallet, Action indexed action, Actor actor, uint64 version);
    /// @notice A relayer reported seeing this account created on another chain.
    /// @param wallet The account observed.
    /// @param chainRef The chain it was observed on.
    event DeploymentObserved(address indexed wallet, bytes32 indexed chainRef);
    /// @notice A row of the account's `deployedChains` table was set or replaced.
    /// @param wallet The account whose table changed.
    /// @param chainRef The chain the row addresses.
    /// @param account The account as that chain's own address space expresses it.
    event ChainAccountSet(address indexed wallet, bytes32 indexed chainRef, bytes32 account);
    /// @notice The opener role or its threshold was set.
    /// @param openerRole The registry role that may now attest a genesis.
    /// @param openThreshold How many attestations a genesis now needs. Zero refuses every open.
    event LedgerConfigured(uint256 openerRole, uint256 openThreshold);

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

    /// @notice The caller does not hold the role this entrypoint requires.
    /// @param caller The rejected sender.
    error NotAuthorized(address caller);
    /// @notice No account is open at this address.
    /// @dev Raised rather than returning a zeroed record, so "absent" can never be read as "present with
    ///      defaults" — which for `frozen` and for a key commitment would be the dangerous reading.
    /// @param wallet The address with no record.
    error UnknownAccount(address wallet);

    /// @notice Evidence that this account's holder is alive was recorded.
    /// @param wallet The account stamped.
    /// @param at The new liveness stamp, in milliseconds. Only ever later than the previous one.
    event ActivityRecorded(address indexed wallet, uint64 at);
    /// @notice An account's per-chain dormancy verdict changed and was republished.
    /// @param wallet The account re-evaluated.
    /// @param dormantChains The new bitmap, one bit per registry slot.
    /// @param version The record's version after the change.
    event DormancyRefreshed(address indexed wallet, uint32 dormantChains, uint64 version);
    /// @notice An account already exists at this address.
    /// @param wallet The address already open.
    error AccountAlreadyOpen(address wallet);
    /// @notice `restoreAccount` was called after `sealRestore` closed the lane.
    error RestoreIsSealed();
    /// @notice A carried record that does not decode to an opened account, or that names the zero wallet.
    /// @param wallet The wallet the record named.
    error InvalidRestore(address wallet);

    /// @notice A genesis carried no chain row.
    /// @dev That is an opener which never decided, not one that decided "none", and the two must not look alike.
    ///      Creation reads no row (phase 2, 2026-09-08); the settlement's zero-beneficiary resolution still does.
    /// @param wallet The account whose genesis was refused.
    error NoChainDeclared(address wallet);
    /// @notice The requested action is retired: the ledger performs it for no credential.
    /// @dev `SET_CHAIN_ACCOUNT` since 2026-09-08 — creation reads no row of the chain table. Refused where a permitted action
    ///      would apply, after the credential check, so a retired action and a permitted one fail in the same place
    ///      and a stale client learns the truth about its request rather than about its key.
    /// @param action The retired action.
    error ActionRetired(Action action);
    /// @notice A genesis with no certificate serial.
    /// @dev The admission leaf needs it, and zero is the shape of an opener that never resolved it.
    /// @param wallet The account whose genesis was refused.
    error ZeroSerial(address wallet);
    /// @notice A `deployedChains` row names the zero chain or the zero account.
    /// @param chainRef The chain half of the rejected row.
    /// @param account The account half of the rejected row.
    error InvalidChainAccount(bytes32 chainRef, bytes32 account);
    /// @notice The ledger has no opener threshold, so it can open nothing.
    error LedgerNotConfigured();
    /// @notice The requested threshold exceeds the number of live members the role currently has.
    /// @dev Refused up front, because a threshold nobody can reach is a lane that fails only when it is needed.
    /// @param live Live members the role holds now.
    /// @param required The threshold that was asked for.
    error ThresholdUnreachable(uint256 live, uint256 required);
    /// @notice The request's nonce is not this account's next nonce for that action.
    /// @param expected The nonce the ledger holds.
    /// @param supplied The nonce the request carried.
    error NonceMismatch(uint64 expected, uint64 supplied);
    /// @notice The request's expiry has already passed.
    /// @param expiresAt The expiry the request carried, in milliseconds.
    /// @param nowSeconds The chain's current time, in the same milliseconds.
    error RequestExpired(uint64 expiresAt, uint256 nowSeconds);
    /// @notice The request would stay valid for longer than `MAX_REQUEST_TTL_MS`.
    /// @param span How far out the expiry sits, in milliseconds.
    /// @param cap The ceiling it exceeded.
    error ExpiryTooFar(uint64 span, uint64 cap);
    /// @notice No credential was supplied where one is required.
    error NoCredential();
    /// @notice A credential was supplied that this action does not accept.
    /// @dev Raised rather than ignoring the surplus credential: freeze and cancel exist BECAUSE a key may be the
    ///      compromised party, so accepting the account's own key alongside a guardian quorum would hand an
    ///      attacker both halves of the gate.
    /// @param why Which rule refused it.
    error CredentialNotPermitted(string why);
    /// @notice The account's live and recovery access commitments are equal, so a credential names neither.
    /// @dev Refused instead of resolved, because picking whichever branch ran first would silently grant the live
    ///      key the recovery key's powers.
    error AmbiguousCredential();
    /// @notice The presented public key hashes to no commitment this account holds.
    error KeyCommitmentMismatch();
    /// @notice A signature failed verification, or an ECDSA one recovered to nobody.
    error SignatureInvalid();
    /// @notice A credential was offered under an algorithm the ACCESS class does not use.
    /// @dev The transaction-class key verifies perfectly well under its own algorithm; it is refused here anyway.
    /// @param supplied The algorithm id the blob carried.
    error WrongAlgorithmForSlot(uint8 supplied);
    /// @notice A post-quantum blob is too short to decode as a credential.
    error MalformedBlob();
    /// @notice An authorization resolved to somebody who is not a guardian of this account, or the guardian count
    ///         fell short of the threshold.
    /// @param who The resolved non-member, or the zero address when the count itself was short.
    error NotAGuardian(address who);
    /// @notice The same guardian authorized twice in one request.
    /// @dev Checked on the RESOLVED guardian, so nothing a request labels itself with can turn an M-of-N into a
    ///      one-of-N without changing a single visible parameter.
    /// @param who The guardian offered a second time.
    error DuplicateGuardian(address who);
    /// @notice The transition is not applicable to the account's current state.
    /// @param why Which precondition failed.
    error InvalidTransition(string why);
    /// @notice A delay outside `MIN_DELAY_MS`..`MAX_DELAY_MS`.
    /// @param delayMs The rejected delay, in milliseconds.
    error InvalidDelay(uint64 delayMs);
    /// @notice A guardian set whose shape this ledger refuses.
    /// @param why Which structural rule it broke.
    error InvalidGuardianSet(string why);
    /// @notice A finalize arrived before the account's delay had run.
    /// @param readyAt When it becomes finalizable, in milliseconds.
    /// @param nowSeconds The chain's current time, in the same milliseconds.
    error DelayNotElapsed(uint64 readyAt, uint256 nowSeconds);

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

    /**
     * @notice Pin the membership registry and the state trees this ledger answers to, for good.
     * @dev The precompile probe is the point of having a constructor at all. A ledger deployed where
     *      SLH-DSA-SHAKE-256s cannot be verified would accept no credential it was ever given, and the first
     *      symptom would be an account plane that silently refuses every holder — so it refuses to exist there
     *      instead. Neither pointer is settable afterwards: the genesis quorum resolves its membership through
     *      `registry`, and a registry that could be rotated later is a quorum whose membership the rotator
     *      chooses.
     *
     *      Deployment is not complete at construction. `configure` must still name the opener role and its
     *      threshold, and `FinalStateTrees` must register this address as the account tree's writer, before any
     *      account can be opened. Until then `openThreshold` is zero and every open fails closed.
     * @param registry_ The identity registry that resolves co-signers, keys, roles and the bootstrap window.
     * @param trees_ The state trees this ledger publishes account leaves, admission leaves and the owner index
     *        into.
     */
    constructor(FinalIdentityRegistry registry_, FinalStateTrees trees_) {
        FinalChainPrecompiles.assertAvailable();
        registry = registry_;
        trees = trees_;
    }

    /**
     * @notice Set which registry role may attest a genesis, and how many attestations one needs.
     * @dev Gated exactly as every other contract on this state plane is: the registry's bootstrap admin alone
     *      while that window is open, and the sealed registrar quorum once it closes. `approvals` is therefore
     *      empty during bootstrap and carries a K-of-N afterwards. This is a configuration authority and not an
     *      account authority — it decides who may ATTEST an issuance, and it can neither open an account itself
     *      nor touch one that is already open.
     *
     *      Re-callable, because a co-signer set that grows or shrinks has to be able to move its threshold with
     *      it. A non-zero threshold is checked against the role's live membership up front, so a threshold nobody
     *      can reach is refused here rather than discovered at the first genesis that needs it. Setting `k` to
     *      zero deliberately closes the open and restore lanes.
     * @param role Registry role whose live members may attest a genesis.
     * @param k How many distinct attestations one genesis needs.
     * @param anchorBlock Block the quorum's approvals are anchored to, forwarded to the registry unchanged.
     * @param approvals Registrar approvals authorizing this configuration. Empty while the bootstrap admin acts.
     */
    function configure(
        uint256 role,
        uint256 k,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (registry.bootstrapSealed() || msg.sender != registry.bootstrapAdmin()) {
            registry.requireRegistrarQuorum(
                ACTION_CONFIGURE, keccak256(abi.encode(role, k)), anchorBlock, approvals
            );
        }
        if (k != 0) {
            uint256 live = registry.liveMemberCount(role);
            if (live < k) revert ThresholdUnreachable(live, k);
        }
        openerRole = role;
        openThreshold = k;
        emit LedgerConfigured(role, k);
    }

    // ---------------------------------------------------------------- opens

    /**
     * @notice Register a batch of accounts at genesis, under a post-quantum quorum.
     *
     * @dev **This is the one place a quorum of protocol keys stands in for evidence, and the grant is bounded to
     *      it.** Issuance happens at a trust boundary this chain cannot observe — a certificate produced
     *      elsewhere fixed each account's key commitments and, through them, its address — so a K-of-N ML-DSA-87
     *      quorum over `openerRole` attests that the certificate existed and carried exactly these values. After
     *      genesis no set of protocol keys can move the account: every later transition takes the holder's own
     *      credential, and there is no path back into this function for an address already open.
     *
     *      The whole batch is covered by ONE digest over `(openNonce, batch)`, so approvals cannot be shuffled
     *      between batches, replayed, or split apart to admit an account the co-signers did not see. The nonce
     *      burns once per call regardless of batch size.
     *
     *      Three tree writes happen in the same transaction as the records: the account leaves, the identity
     *      tree's admission leaf for each account, and the owner index for every owner the batch touched. The
     *      admission half is not optional — without its leaf the account exists here and is creatable nowhere —
     *      and it is write-once by construction, since the certificate is what determined the address and no
     *      later mutation can move it.
     *
     *      The genesis guardian set lands immediately, and that is the one exception to the delay rule: there is
     *      no outgoing set to object, and a delay here would protect nobody while leaving a fresh account
     *      unguarded through exactly the period it is least able to defend itself.
     * @param batch The accounts to open. Each is refused whole if it is already open, carries no chain row, has
     *        no certificate serial, names a delay outside the bounds, or offers a malformed guardian set.
     * @param anchorBlock Block the approvals are anchored to, forwarded to the quorum unchanged.
     * @param approvals The co-signer approvals over this batch's digest.
     */
    function openAccount(
        Genesis[] calldata batch,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        uint256 k = openThreshold;
        if (k == 0) revert LedgerNotConfigured();

        uint64 n = openNonce;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(address(this), ACTION_OPEN, anchorBlock, keccak256(abi.encode(n, batch))),
            openerRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        openNonce = n + 1;

        FinalStateTrees.AccountStateLeaf[] memory leaves =
            new FinalStateTrees.AccountStateLeaf[](batch.length);
        bytes32[] memory identityKeys = new bytes32[](batch.length);
        bytes32[] memory identityLeaves = new bytes32[](batch.length);
        for (uint256 i = 0; i < batch.length; i++) {
            leaves[i] = _open(batch[i]);
            identityKeys[i] = trees.identityKeyFor(batch[i].wallet);
            identityLeaves[i] = _identityLeafOf(batch[i]);
        }
        trees.setAccountStatesAsWriter(leaves);
        // The admission half, in the same transaction as the record: without this leaf the account exists here
        // and is creatable on no execution chain.
        trees.setLeavesAsWriter(TREE_IDENTITY_ID, BRANCH_MAIN_ID, identityKeys, identityLeaves);
        // One owner-index leaf per owner the batch touched.
        address[] memory owners = new address[](batch.length);
        for (uint256 i = 0; i < batch.length; i++) owners[i] = batch[i].owner;
        _writeOwnerIndex(owners);
    }

    // -------------------------------------------------------------- restore

    /// @notice How many storage words one `Account` record occupies.
    /// @dev The `Account` struct's packed fields fit exactly this many slots, and `accountWords` /
    ///      `restoreAccount` copy precisely that many. A field added to the struct that does not fit the last
    ///      slot needs this constant raised in the same change, or a restore silently truncates the record.
    uint256 public constant ACCOUNT_WORDS = 15;

    /**
     * @notice One account as it is carried across a ledger redeploy: the record's storage words verbatim, the
     *         lists beside it, its per-action nonces, and its identity-tree admission leaf.
     * @dev Raw words rather than a decoded struct, deliberately. A calldata `Account` copied field by field costs
     *      kilobytes of bytecode this contract cannot spare under the deployed-code ceiling, and the words ARE
     *      the record: same source, same layout, byte-exact, with no field able to be dropped by an encoder that
     *      has not been taught about it.
     *
     *      `identityLeaf` is carried rather than re-derived, because the admission leaf commits to the GENESIS
     *      certificate's keys and a rotated account's live keys are not those — re-deriving it from the current
     *      record would produce a leaf no execution chain would admit. `nonces` are carried because the request
     *      digest binds the chain and the salt rather than this contract's address, so a reset to zero would make
     *      every request the holder ever signed replayable against the successor.
     */
    struct Restored {
        /// @dev The account being re-created. Must not already be open here.
        address wallet;
        /// @dev The predecessor record's storage words, written back unchanged.
        bytes32[ACCOUNT_WORDS] words;
        /// @dev The live guardian set as it stood.
        address[] guardians;
        /// @dev The staged guardian set as it stood, empty when no change was pending.
        address[] pendingGuardians;
        /// @dev Every per-action nonce, in `Action` order, so no signed request becomes replayable.
        uint64[ACTION_COUNT] nonces;
        /// @dev Observed deployments, re-applied idempotently.
        bytes32[] deployments;
        /// @dev The holder's `(chainRef, account)` table, re-applied row by row through `_setChainAccount`'s
        ///      validation — with `SET_CHAIN_ACCOUNT` retired, genesis and this carry-over are the table's writers.
        FinalStateTrees.ChainAccount[] chainAccounts;
        /// @dev The account's admission leaf, carried verbatim from the predecessor's identity tree.
        bytes32 identityLeaf;
    }

    /// @notice The raw storage words of one account's record — what a carry-over export reads.
    /// @dev Reads the record's slots directly, so the export cannot drift from the struct the way a hand-written
    ///      field-by-field encoder would. Pairs with `Restored.words`, which writes exactly these back.
    /// @param wallet The account to export. Must be open.
    /// @return words The record's `ACCOUNT_WORDS` storage words, in slot order.
    function accountWords(address wallet) external view returns (bytes32[ACCOUNT_WORDS] memory words) {
        if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
        Account storage a = _accounts[wallet];
        uint256 base;
        assembly { base := a.slot }
        for (uint256 i = 0; i < ACCOUNT_WORDS; i++) {
            bytes32 w;
            assembly { w := sload(add(base, i)) }
            words[i] = w;
        }
    }

    /**
     * @notice Re-create one registration carried over from a predecessor ledger, verbatim.
     *
     * @dev This contract is immutable, so any change to its surface is a redeploy at a new address on the same
     *      chain, keeping the same identities. Every account registration is exported before that redeploy and
     *      replayed here afterwards. `openAccount` cannot do the job: it writes a GENESIS — version one, no
     *      pending rotation, no pending guardian change, zero nonces — so an account that had already rotated
     *      would come back honouring keys its holder retired, and every request it had ever signed would be
     *      replayable. This writes the record as it stood: keys, version, per-action nonces, pending changes,
     *      chain accounts, observed deployments and dormancy.
     *
     *      It republishes the same three leaves an open does, but from different sources: the account leaf is
     *      recomputed from the restored record, the admission leaf is written verbatim from the carried value
     *      because it commits to genesis keys, and the owner index is rebuilt from this ledger's own array.
     *
     *      Same authority as an open — the opener quorum, under its own action and its own nonce, one round per
     *      record so a refused record names itself — and available only for wallets this ledger does not already
     *      know, and only until `sealRestore` closes the lane. It exists for the carry-over step and for nothing
     *      after it. The published roots are new by construction, since the trees' shape may differ across a
     *      redeploy, and they are anchored above the predecessor's epoch.
     * @param r The carried record.
     * @param anchorBlock Block the approvals are anchored to, forwarded to the quorum unchanged.
     * @param approvals The opener-quorum approvals over this record's digest.
     */
    function restoreAccount(
        Restored calldata r,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (restoreSealed) revert RestoreIsSealed();
        uint256 k = openThreshold;
        if (k == 0) revert LedgerNotConfigured();
        uint64 n = restoreNonce;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(address(this), ACTION_RESTORE, anchorBlock, keccak256(abi.encode(n, r))),
            openerRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        restoreNonce = n + 1;

        address wallet = r.wallet;
        if (wallet == address(0)) revert InvalidRestore(wallet);
        Account storage a = _accounts[wallet];
        if (a.opened) revert AccountAlreadyOpen(wallet);
        uint256 base;
        assembly { base := a.slot }
        for (uint256 j = 0; j < ACCOUNT_WORDS; j++) {
            bytes32 w = r.words[j];
            assembly { sstore(add(base, j), w) }
        }
        if (!a.opened || a.serial == bytes32(0) || a.owner == address(0)) revert InvalidRestore(wallet);
        _guardians[wallet] = r.guardians;
        _pendingGuardians[wallet] = r.pendingGuardians;
        for (uint8 j = 0; j < ACTION_COUNT; j++) nonceOf[wallet][j] = r.nonces[j];
        for (uint256 j = 0; j < r.deployments.length; j++) {
            bytes32 ref = r.deployments[j];
            if (_hasDeployment[wallet][ref]) continue;
            _hasDeployment[wallet][ref] = true;
            _deployments[wallet].push(ref);
        }
        for (uint256 j = 0; j < r.chainAccounts.length; j++) {
            _setChainAccount(wallet, r.chainAccounts[j].chainRef, r.chainAccounts[j].account);
        }
        _wallets.push(wallet);
        _indexOwner(wallet, a.owner);
        emit AccountRestored(wallet, a.owner, a.version);

        FinalStateTrees.AccountStateLeaf[] memory one = new FinalStateTrees.AccountStateLeaf[](1);
        one[0] = _leafOf(wallet);
        trees.setAccountStatesAsWriter(one);
        bytes32[] memory keys = new bytes32[](1);
        bytes32[] memory leaves = new bytes32[](1);
        keys[0] = trees.identityKeyFor(wallet);
        leaves[0] = r.identityLeaf;
        trees.setLeavesAsWriter(TREE_IDENTITY_ID, BRANCH_MAIN_ID, keys, leaves);
        address[] memory owners = new address[](1);
        owners[0] = a.owner;
        _writeOwnerIndex(owners);
    }

    /// @notice Close the restore lane for good.
    /// @dev The configuration authority's call — the bootstrap admin while that window is open, the registrar
    ///      quorum afterwards — run once the carried-over roots have been checked. One-way: there is no reopen,
    ///      because a lane that can re-create an arbitrary record is exactly the admin path this ledger refuses
    ///      to keep. After it, every account enters through `openAccount` and its attested genesis.
    /// @param anchorBlock Block the approvals are anchored to, forwarded to the registry unchanged.
    /// @param approvals Registrar approvals authorizing the seal. Empty while the bootstrap admin acts.
    function sealRestore(uint64 anchorBlock, FinalPqQuorum.Approval[] calldata approvals) external {
        if (registry.bootstrapSealed() || msg.sender != registry.bootstrapAdmin()) {
            registry.requireRegistrarQuorum(
                ACTION_SEAL_RESTORE, keccak256(abi.encode(address(this))), anchorBlock, approvals
            );
        }
        restoreSealed = true;
        emit RestoreSealed();
    }

    // ---------------------------------------------------------- owner index

    /// @notice Every account an owner holds, in index order.
    /// @dev The readable half of the identity tree's owner-index branch, whose leaf commits to exactly this
    ///      array. A reader asks here first and proves against the branch when it needs the answer to be
    ///      verifiable elsewhere.
    /// @param owner The owner to look up.
    /// @return The accounts that owner holds. Empty for an owner with none.
    function walletsByOwner(address owner) external view returns (address[] memory) {
        return _walletsByOwner[owner];
    }

    /// @notice Add an account to its owner's index.
    /// @dev Remembers the position so `_unindexOwner` can remove it without a scan. Caller republishes the leaf.
    /// @param wallet The account being indexed.
    /// @param owner The owner to index it under.
    function _indexOwner(address wallet, address owner) private {
        address[] storage list = _walletsByOwner[owner];
        list.push(wallet);
        _ownerSlotPlusOne[wallet] = list.length;
    }

    /// @notice Remove an account from its owner's index.
    /// @dev Swaps the last entry into the vacated position and pops. Order within an owner's array is therefore
    ///      not a promise, and nothing may treat it as one — the committed leaf is recomputed over the array as
    ///      it stands after the move. A wallet that is not indexed is a no-op rather than an error, so a restore
    ///      or a transfer can call it unconditionally.
    /// @param wallet The account being removed.
    /// @param owner The owner it is being removed from.
    function _unindexOwner(address wallet, address owner) private {
        uint256 pos = _ownerSlotPlusOne[wallet];
        if (pos == 0) return;
        address[] storage list = _walletsByOwner[owner];
        uint256 last = list.length - 1;
        if (pos - 1 != last) {
            address moved = list[last];
            list[pos - 1] = moved;
            _ownerSlotPlusOne[moved] = pos;
        }
        list.pop();
        _ownerSlotPlusOne[wallet] = 0;
    }

    /// @notice Republish the owner-index leaf of every owner in a list.
    /// @dev Key and leaf are folded here from the same domains the trees use, so the write costs two hashes
    ///      instead of two external calls; both must stay byte-identical to the trees' own derivations, or the
    ///      leaf lands at a key nothing reads and proves nothing to a reader that folds the preimage itself. A
    ///      repeated owner simply writes the same leaf twice, which is correct and cheaper than deduplicating.
    /// @param owners The owners whose index leaves should be rewritten.
    function _writeOwnerIndex(address[] memory owners) private {
        bytes32[] memory keys = new bytes32[](owners.length);
        bytes32[] memory hashes = new bytes32[](owners.length);
        for (uint256 i = 0; i < owners.length; i++) {
            keys[i] = keccak256(abi.encode(DOMAIN_OWNER_INDEX_KEY, owners[i]));
            hashes[i] = keccak256(abi.encode(DOMAIN_OWNER_INDEX_LEAF, owners[i], _walletsByOwner[owners[i]]));
        }
        trees.setLeavesAsWriter(TREE_IDENTITY_ID, BRANCH_OWNER_INDEX_ID, keys, hashes);
    }

    /// @notice Derive the admission leaf an execution chain verifies before it will create this account.
    /// @dev `keccak256(identityDomain ‖ serial ‖ keysHash)`, where `keysHash` is the issuer's fold of the
    ///      account's six key commitments in the order the certificate fixes them. It must be byte-identical to
    ///      the gateway's own identity-leaf derivation and to what the registry derives for a service identity:
    ///      the three are separate implementations of one preimage, and a divergence in any of them is an account
    ///      that exists on this chain and can be created on none.
    /// @param g The genesis whose commitments and serial the leaf commits to.
    /// @return The admission leaf, ready to write into the identity tree's main branch.
    function _identityLeafOf(Genesis calldata g) private pure returns (bytes32) {
        bytes32 keysHash = keccak256(
            abi.encodePacked(
                g.liveAccess, g.liveTransaction, g.recoveryAccess, g.recoveryTransaction, g.liveKem, g.recoveryKem
            )
        );
        return keccak256(abi.encodePacked(DOMAIN_IDENTITY_LEAF, g.serial, keysHash));
    }

    /// @notice Write one attested genesis into storage and return the leaf it publishes.
    /// @dev Validates the shape a quorum cannot be trusted to have checked — the delay bounds, at least one chain
    ///      row, a non-zero certificate serial, and a well-formed guardian set — then installs the record. The
    ///      caller has already verified the quorum over the whole batch; this function performs no authorization
    ///      of its own and must never be reachable from anywhere that has not.
    /// @param g The genesis to install.
    /// @return The account's published leaf, for the batched tree write.
    function _open(Genesis calldata g) private returns (FinalStateTrees.AccountStateLeaf memory) {
        Account storage a = _accounts[g.wallet];
        if (a.opened) revert AccountAlreadyOpen(g.wallet);

        uint64 delay = g.delayMs == 0 ? DEFAULT_DELAY_MS : g.delayMs;
        if (delay < MIN_DELAY_MS || delay > MAX_DELAY_MS) revert InvalidDelay(delay);
        if (g.deployedChains.length == 0) revert NoChainDeclared(g.wallet);
        // A zero serial is an opener that never decided, not one that decided
        // "none" — and the admission leaf it derives would name a certificate
        // that cannot exist.
        if (g.serial == bytes32(0)) revert ZeroSerial(g.wallet);
        uint16 cancelThreshold = g.cancelThreshold == 0
            ? _cancelThresholdFor(uint16(g.guardians.length), g.threshold)
            : g.cancelThreshold;
        _assertGuardianSet(g.wallet, g.guardians, g.threshold, cancelThreshold);

        a.opened = true;
        a.liveAccess = g.liveAccess;
        a.liveTransaction = g.liveTransaction;
        a.recoveryAccess = g.recoveryAccess;
        a.recoveryTransaction = g.recoveryTransaction;
        a.serial = g.serial;
        a.liveKem = g.liveKem;
        a.recoveryKem = g.recoveryKem;
        // Generation ONE, not zero. Zero has to stay unreachable so a reader can
        // tell "this account has no KEM key" from "this is its first" — and an
        // envelope header carrying zero is then a sealer that never resolved
        // the version rather than one that resolved it to the genesis key.
        a.kemVersion = 1;
        a.owner = g.owner;
        a.pqEnabled = g.pqEnabled;
        for (uint256 i = 0; i < g.deployedChains.length; i++) {
            _setChainAccount(g.wallet, g.deployedChains[i].chainRef, g.deployedChains[i].account);
        }
        a.version = 1;
        a.delayMs = delay;
        a.threshold = g.threshold;
        a.cancelThreshold = cancelThreshold;
        _guardians[g.wallet] = g.guardians;
        _wallets.push(g.wallet);
        _indexOwner(g.wallet, g.owner);

        emit AccountOpened(g.wallet, g.owner, g.pqEnabled);
        return _leafOf(g.wallet);
    }

    // ------------------------------------------------------------- requests

    /// @notice Record evidence that this account's holder is alive.
    ///
    /// @dev **Permissionless and monotone FORWARD.** Anyone may push the stamp later; nobody may push it back.
    /// That asymmetry is the whole design: understating liveness is the only dangerous direction, because it
    /// manufactures dormancy against a holder who is still there, and the rule puts that out of reach of
    /// everyone — the holder included, who can always stamp it here directly. Overstating merely delays a
    /// legitimate recovery, which fails safe.
    ///
    /// So there is nothing to authorize and nobody to trust: a hostile stamper can only make an account look MORE
    /// alive, and a lazy one is corrected by the next party who cares.
    ///
    /// The stamp is the chain's current time, never a value the caller supplies, so no call can push an account
    /// permanently out of dormancy — which would be the same seizure-proofing failure in the opposite direction.
    /// A call that would not move the stamp forward returns quietly rather than reverting, so a caller batching
    /// several accounts is not forced to pre-check each one.
    /// @param wallet The account to stamp. Must be open.
    function recordActivity(address wallet) external {
        Account storage a = _accounts[wallet];
        if (!a.opened) revert UnknownAccount(wallet);
        if (a.lastActivityAt >= FinalChainTime.nowMs()) return;
        a.lastActivityAt = FinalChainTime.nowMs();
        emit ActivityRecorded(wallet, a.lastActivityAt);
    }

    /// @notice Recompute which chains consider this account dormant, and publish the verdict if it changed.
    ///
    /// @dev **The published leaf carries the VERDICT, not the clock.** `lastActivityAt` moves whenever the holder
    /// acts anywhere; putting it in the leaf would bump the account's version on every transaction, move the
    /// account tree every time, and age every outstanding inclusion proof. The bitmap moves only when a chain
    /// crosses its threshold — about as rarely as a freeze — so the tree keeps the cadence it was designed for.
    ///
    /// Permissionless for the same reason `recordActivity` is: the verdict derives entirely from state this
    /// contract already holds, so the caller chooses nothing and cannot make an account look dormant that is not.
    /// Publishing only on a CHANGE is what stops a caller rewriting the tree at will.
    /// @param wallet The account to re-evaluate. Must be open.
    /// @param slots Registry slots to evaluate. Explicit rather than a sweep, because the set of chains is not
    ///   this contract's to enumerate, and a slot left out simply keeps whatever bit it already had.
    function refreshDormancy(address wallet, uint8[] calldata slots) external {
        Account storage a = _accounts[wallet];
        if (!a.opened) revert UnknownAccount(wallet);

        uint32 next = a.dormantChains;
        for (uint256 i = 0; i < slots.length; i++) {
            uint8 slot = slots[i];
            uint64 threshold = inactivityThresholdOf[slot];
            uint32 bit = uint32(1) << slot;
            // A zero threshold means the chain does not accrue dormancy at all,
            // and clears any bit already set — otherwise disabling the policy
            // would leave accounts stranded dormant with no way back.
            bool dormant = threshold != 0
                && FinalChainTime.nowMs() >= uint256(a.lastActivityAt) + threshold;
            next = dormant ? (next | bit) : (next & ~bit);
        }
        if (next == a.dormantChains) return;

        a.dormantChains = next;
        a.version += 1;
        FinalStateTrees.AccountStateLeaf[] memory one = new FinalStateTrees.AccountStateLeaf[](1);
        one[0] = _leafOf(wallet);
        trees.setAccountStatesAsWriter(one);
        emit DormancyRefreshed(wallet, next, a.version);
    }

    /**
     * @notice Authorize and apply one account transition.
     *
     * @dev **Permissionless, and the separation is the point.** Whoever calls this pays the gas and carries no
     *      authority whatsoever. The credential decides, and it is verified HERE — in the SLH-DSA-SHAKE-256s
     *      precompile, against a commitment this chain already holds — rather than by some process that then
     *      tells everyone what it concluded. A submitter cannot choose the actor, cannot alter the payload the
     *      digest commits to, cannot make a refused transition succeed, and cannot withhold a holder's remedy by
     *      declining to submit, because anyone else may submit the same request instead.
     *
     *      **Verify, apply, publish — in that order, in one transaction.** The account's leaf is republished in
     *      the same call that accepts the credential, so there is never a window in which the record and the
     *      published root disagree, and no separate publication step that could describe a different history.
     *
     *      Order of checks: the nonce first, because it is the cheapest and it makes a replayed request
     *      indistinguishable from a stale one to everything below; then the expiry and its ceiling; then the
     *      actor, resolved from whichever stored commitment the presented key matches; then the transition
     *      itself, which enforces the authority a second time against what that action actually requires.
     *
     *      The nonce burns ONLY on acceptance. A refused transition must leave the authorization spendable —
     *      nothing happened, and burning it would mean a mis-ordered request costs the holder a trip back to
     *      their cold key.
     * @param request What should happen, to which account, under which nonce and until when.
     * @param credential The holder's own credential. Must be empty for the guardian-authorized actions.
     * @param guardianAuths Guardian authorizations. Must be empty for everything but the guardian actions.
     * @return version The account's record version after the transition.
     */
    function submitRequest(
        Request calldata request,
        Credential calldata credential,
        GuardianAuth[] calldata guardianAuths
    ) external returns (uint64 version) {
        Account storage a = _accounts[request.wallet];
        if (!a.opened) revert UnknownAccount(request.wallet);

        // Nonce first: the cheapest check, and the one that makes a replayed request indistinguishable from a
        // stale one to everything below it.
        uint64 expected = nonceOf[request.wallet][uint8(request.action)];
        if (request.nonce != expected) revert NonceMismatch(expected, request.nonce);
        if (request.expiresAt <= FinalChainTime.nowMs()) revert RequestExpired(request.expiresAt, FinalChainTime.nowMs());
        uint64 span = request.expiresAt - FinalChainTime.nowMs();
        if (span > MAX_REQUEST_TTL_MS) revert ExpiryTooFar(span, MAX_REQUEST_TTL_MS);

        bytes32 digest = requestDigest(request);
        (Actor actor, uint256 guardianCount) = _establishActor(a, request, credential, guardianAuths, digest);

        _apply(a, request, actor, guardianCount);

        a.version += 1;
        // Burned only on acceptance, so a refused transition leaves the authorization spendable.
        nonceOf[request.wallet][uint8(request.action)] = expected + 1;

        FinalStateTrees.AccountStateLeaf[] memory one = new FinalStateTrees.AccountStateLeaf[](1);
        one[0] = _leafOf(request.wallet);
        trees.setAccountStatesAsWriter(one);

        emit RequestApplied(request.wallet, request.action, actor, a.version);
        return a.version;
    }

    /**
     * @notice Record that an account is known to exist on another chain.
     * @dev A relayer's OBSERVATION of a creation event elsewhere, and explicitly not an authorization: it says
     *      where an account exists so the fan-out knows where to write, and it decides nothing about who may act.
     *      Add-only and idempotent, so a relayer can omit a chain and cannot remove one; a reconciler re-derives
     *      the set by probing for code and adds whatever was missed. Because the set only ever grows, a hostile
     *      relayer can at worst make the fan-out do redundant work.
     *
     *      The role is resolved through the registry's sender binding rather than looked up on `msg.sender`
     *      itself, because a relayer's transactions are sent under a key the registry binds to its identity, not
     *      under an address that holds a role in its own right.
     * @param wallet The account observed. Must be open here.
     * @param chainRef The chain it was observed on.
     */
    function observeDeployment(address wallet, bytes32 chainRef) external {
        if (!registry.senderHasRole(msg.sender, registry.ROLE_RELAYER())) revert NotAuthorized(msg.sender);
        if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
        if (_hasDeployment[wallet][chainRef]) return;
        _hasDeployment[wallet][chainRef] = true;
        _deployments[wallet].push(chainRef);
        emit DeploymentObserved(wallet, chainRef);
    }

    // ------------------------------------------------------------- the digest

    /// @notice The EIP-712 digest a request is authorized under.
    /// @dev Public, so a holder's client, a guardian and this contract all derive one value from one definition
    ///      rather than three implementations that agree by convention. The payload is bound as the hash of the
    ///      ABI encoding exactly as the caller supplied it, byte for byte, rather than re-encoded from decoded
    ///      fields: the encoding is what stops a signature being moved onto different arguments, so re-deriving
    ///      it would reintroduce the very substitution it prevents.
    /// @param request The request to hash.
    /// @return The digest a credential must sign for this request to be accepted.
    function requestDigest(Request calldata request) public view returns (bytes32) {
        bytes32 structHash = keccak256(
            abi.encode(
                REQUEST_TYPEHASH,
                request.wallet,
                keccak256(bytes(_actionName(request.action))),
                keccak256(request.payload),
                request.nonce,
                request.expiresAt
            )
        );
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash));
    }

    /// @notice The EIP-712 domain separator every request digest is built on.
    /// @dev Binds the chain id and this ledger's own salt, and deliberately not this contract's address, so a
    ///      credential stays verifiable across a redeploy of the ledger while remaining useless on any other
    ///      chain. Computed on each call rather than cached, because a cached separator is a value that can be
    ///      wrong exactly once and never says so.
    /// @return The domain separator for this chain.
    function domainSeparator() public view returns (bytes32) {
        return keccak256(
            abi.encode(
                EIP712_DOMAIN_TYPEHASH,
                DOMAIN_NAME,
                DOMAIN_VERSION,
                block.chainid,
                DOMAIN_ACCOUNT_STATE_REQUEST
            )
        );
    }

    /// @notice The canonical string an action is hashed under inside the request digest.
    /// @dev A string rather than the ordinal, so a signing client shows the holder what they are authorizing and
    ///      a digest is readable to a human checking it. Every credential producer must agree with this mapping
    ///      byte for byte: a mismatch is a signature no client can produce and nothing that names the cause. The
    ///      final action is returned by fallthrough, so a new variant appended to `Action` without a branch here
    ///      would silently take its name — add the branch in the same change.
    /// @param action The action to name.
    /// @return The exact string the digest commits to.
    function _actionName(Action action) private pure returns (string memory) {
        if (action == Action.FREEZE) return "freeze";
        if (action == Action.UNFREEZE) return "unfreeze";
        if (action == Action.INITIATE_ROTATION) return "initiate-rotation";
        if (action == Action.CANCEL_ROTATION) return "cancel-rotation";
        if (action == Action.FINALIZE_ROTATION) return "finalize-rotation";
        if (action == Action.INITIATE_GUARDIAN_CHANGE) return "initiate-guardian-change";
        if (action == Action.CANCEL_GUARDIAN_CHANGE) return "cancel-guardian-change";
        if (action == Action.FINALIZE_GUARDIAN_CHANGE) return "finalize-guardian-change";
        if (action == Action.TRANSFER_OWNER) return "transfer-owner";
        if (action == Action.ENABLE_PQ) return "enable-pq";
        return "set-chain-account";
    }

    // ------------------------------------------------------------- the actor

    /// @notice Which class of credential each action is considered under.
    /// @dev An early refusal and a statement of intent, not the authority gate itself: `_apply` re-checks the
    ///      resolved actor against what the transition actually requires, so a mistake here cannot widen an
    ///      action's authority — only narrow it. The two finalize actions map to `NONE` on purpose. Their delay
    ///      has already run and the outcome is determined, so requiring the initiator to come back would let an
    ///      attacker win simply by keeping the holder away from their cold key.
    /// @param action The action being requested.
    /// @return The actor class the request must establish.
    function _expectedActor(Action action) private pure returns (Actor) {
        if (
            action == Action.FREEZE || action == Action.CANCEL_ROTATION
                || action == Action.CANCEL_GUARDIAN_CHANGE
        ) return Actor.GUARDIANS;
        // `SET_CHAIN_ACCOUNT` is retired and keeps its class on purpose: the credential check runs first and
        // `_apply` refuses it as retired in the place a permitted action would apply.
        if (action == Action.TRANSFER_OWNER || action == Action.ENABLE_PQ || action == Action.SET_CHAIN_ACCOUNT) {
            return Actor.LIVE_KEY;
        }
        if (action == Action.FINALIZE_ROTATION || action == Action.FINALIZE_GUARDIAN_CHANGE) {
            // Permissionless by design: the delay has run and the outcome is already determined.
            return Actor.NONE;
        }
        return Actor.RECOVERY_KEY;
    }

    /// @notice Resolve who authorized this request, and how many guardians did.
    /// @dev Routes by the action's expected class and refuses the credential the OTHER class would use. The two
    ///      exclusions matter: an account's own key offered for a guardian action is refused rather than ignored,
    ///      because freeze and cancel exist precisely for the case where a key is the compromised party; and
    ///      guardian authorizations offered for a key action are refused for the mirror reason.
    /// @param a The account's record, for the commitments and guardian state the resolution reads.
    /// @param request The request being authorized.
    /// @param credential The holder's credential, which must be empty for a guardian action.
    /// @param guardianAuths The guardian authorizations, which must be empty for a key action.
    /// @param digest The request digest every signature must be over.
    /// @return The resolved actor, and the number of distinct guardians when that actor is `GUARDIANS`.
    function _establishActor(
        Account storage a,
        Request calldata request,
        Credential calldata credential,
        GuardianAuth[] calldata guardianAuths,
        bytes32 digest
    ) private view returns (Actor, uint256) {
        Actor wanted = _expectedActor(request.action);
        if (wanted == Actor.NONE) return (Actor.NONE, 0);

        if (wanted == Actor.GUARDIANS) {
            if (credential.pqBlob.length != 0 || credential.ownerSignature.length != 0) {
                // The ACCOUNT's own key, offered for a guardian action. Refused rather than ignored: freeze and
                // cancel exist BECAUSE a key may be the compromised party, so accepting one here would hand an
                // attacker both halves of the gate.
                revert CredentialNotPermitted("guardian action");
            }
            return (Actor.GUARDIANS, _countGuardians(a, request.wallet, digest, guardianAuths));
        }

        if (guardianAuths.length != 0) revert CredentialNotPermitted("not a guardian action");
        return (_resolveKeyActor(a, credential, digest), 0);
    }

    /**
     * @notice Establish which of the account's own keys signed, by matching the presented key's commitment.
     *
     * @dev The actor is the OUTPUT of the match, never an input: nothing in the request says which key it is,
     *      and the commitment that matched is what names it. An account whose live and recovery access
     *      commitments are EQUAL has no second credential at all, so that case is refused outright — resolving
     *      it to whichever comparison ran first would silently grant the live key the recovery key's powers.
     *
     *      The two lanes are mutually exclusive by the account's own state rather than by the caller's choice.
     *      An ECDSA owner signature resolves to `LIVE_KEY` only while the account has not migrated, and the
     *      post-quantum live commitment resolves to `LIVE_KEY` only once it has, so exactly one credential can
     *      ever reach a live-key action. The recovery commitment resolves the same way in both states, because a
     *      recovery key is what an account rotates WITH and must keep working across the migration it authorizes.
     * @param a The account's record, holding the commitments the presented key is matched against.
     * @param credential The credential offered. Exactly one of its two fields may be populated.
     * @param digest The request digest the signature must be over.
     * @return The resolved actor.
     */
    function _resolveKeyActor(Account storage a, Credential calldata credential, bytes32 digest)
        private
        view
        returns (Actor)
    {
        if (credential.pqBlob.length == 0 && credential.ownerSignature.length == 0) revert NoCredential();

        if (credential.ownerSignature.length != 0) {
            // The pre-quantum live credential. Refused once the account has migrated: a migrated account's ECDSA
            // owner is a settlement destination and not an authority, and leaving this path open would keep the
            // weaker credential live forever behind the stronger one.
            if (a.pqEnabled) revert CredentialNotPermitted("account is PQ");
            if (_recover(digest, credential.ownerSignature) != a.owner) revert SignatureInvalid();
            return Actor.LIVE_KEY;
        }

        (bytes memory publicKey, bytes memory signature) = _decodeAccessBlob(credential.pqBlob);
        bytes32 presented = keccak256(publicKey);
        if (a.liveAccess == a.recoveryAccess) revert AmbiguousCredential();

        Actor actor;
        if (presented == a.recoveryAccess) actor = Actor.RECOVERY_KEY;
        else if (presented == a.liveAccess && a.pqEnabled) actor = Actor.LIVE_KEY;
        else revert KeyCommitmentMismatch();

        if (!FinalChainPrecompiles.verifySlhDsa(publicKey, abi.encodePacked(digest), signature)) {
            revert SignatureInvalid();
        }
        return actor;
    }

    /**
     * @notice Count distinct guardian authorizations over one request digest.
     *
     * @dev Distinctness is checked on the RESOLVED guardian, never on anything the request labels itself with, so
     *      one guardian cannot reach a threshold by authorizing several times under different labels — the
     *      failure that turns an M-of-N into a one-of-N without changing a single visible parameter. Membership
     *      is checked on the same resolved value, so an authorization that verifies but names a non-member is a
     *      refusal rather than a silently uncounted entry.
     *
     *      Both scans are linear over sets a holder chose and this contract bounds, which is the right trade on a
     *      chain where gas is not a design constraint: a sorted or mapped structure would be cheaper and would
     *      put a second representation of the guardian set in play.
     * @param a The account's record. Reserved for the resolution's state reads.
     * @param wallet The account whose guardian list membership is checked against.
     * @param digest The request digest every guardian signature must be over.
     * @param auths The guardian authorizations offered.
     * @return count How many DISTINCT guardians of this account authorized the digest.
     */
    function _countGuardians(
        Account storage a,
        address wallet,
        bytes32 digest,
        GuardianAuth[] calldata auths
    ) private view returns (uint256 count) {
        address[] storage members = _guardians[wallet];
        address[] memory seen = new address[](auths.length);
        for (uint256 i = 0; i < auths.length; i++) {
            address who = _resolveGuardian(auths[i], digest);
            bool isMember;
            for (uint256 m = 0; m < members.length; m++) {
                if (members[m] == who) { isMember = true; break; }
            }
            if (!isMember) revert NotAGuardian(who);
            for (uint256 s = 0; s < count; s++) {
                if (seen[s] == who) revert DuplicateGuardian(who);
            }
            seen[count] = who;
            count += 1;
        }
        a; // Referenced so the parameter stays in the signature without a compiler warning.
    }

    /// @notice Resolve one guardian authorization to the guardian address it actually speaks for.
    /// @dev Which of the three forms applies is decided from LEDGER STATE, never from the shape of the argument:
    ///      a named guardian must be an open account here, and whether it is checked as a post-quantum wallet or
    ///      as a pre-quantum one follows that account's own `pqEnabled` rather than which field the caller
    ///      filled in. A named guardian with no record is refused rather than trusted, because only the recovered
    ///      form can speak for an address this ledger does not know, and that form names nobody.
    ///
    ///      A frozen guardian is refused outright. A freeze means that account's own live key is under suspicion,
    ///      and letting it cancel someone else's rotation is precisely the move a compromised guardian would make.
    ///
    ///      A guardian wallet signs with its LIVE access key, not its recovery one. Acting as a guardian is an
    ///      ordinary action for that account; its recovery pair authorizes rotating its own credentials and
    ///      nothing else.
    /// @param auth The authorization to resolve.
    /// @param digest The request digest the signature must be over.
    /// @return The guardian address this authorization speaks for, verified.
    function _resolveGuardian(GuardianAuth calldata auth, bytes32 digest) private view returns (address) {
        if (auth.guardian == address(0)) {
            if (auth.signature.length == 0) revert NoCredential();
            return _recover(digest, auth.signature);
        }

        Account storage g = _accounts[auth.guardian];
        // A named guardian with no record here is a claim this contract cannot check, rather than one it should
        // take on faith.
        if (!g.opened) revert UnknownAccount(auth.guardian);
        if (g.frozen) revert CredentialNotPermitted("guardian is frozen");

        if (auth.pqBlob.length != 0) {
            if (!g.pqEnabled) revert CredentialNotPermitted("guardian is not PQ");
            (bytes memory publicKey, bytes memory signature) = _decodeAccessBlob(auth.pqBlob);
            if (keccak256(publicKey) != g.liveAccess) revert KeyCommitmentMismatch();
            if (!FinalChainPrecompiles.verifySlhDsa(publicKey, abi.encodePacked(digest), signature)) {
                revert SignatureInvalid();
            }
            return auth.guardian;
        }

        if (auth.signature.length != 0) {
            if (g.pqEnabled) revert CredentialNotPermitted("guardian is PQ");
            if (_recover(digest, auth.signature) != g.owner) revert SignatureInvalid();
            return auth.guardian;
        }
        revert NoCredential();
    }

    /**
     * @notice Decode a post-quantum credential blob and enforce that it belongs to the ACCESS class.
     *
     * @dev The check is on the CLASS, not on a named slot. Both access slots carry the same algorithm, so naming
     *      one of them would happen to work while enforcing nothing. What this actually enforces is that an
     *      ML-DSA-87 transaction key — which verifies perfectly well under its own algorithm — is still refused:
     *      a key that signs spends does not get to rotate a credential set, its own or anyone else's.
     *
     *      The length floor rejects a blob too short to be a well-formed encoding before the decode runs, so a
     *      truncated argument fails as malformed rather than as an ABI error with nothing to name.
     * @param blob `abi.encode(uint8 algorithmId, bytes publicKey, bytes signature)`.
     * @return The public key and the signature it carried.
     */
    function _decodeAccessBlob(bytes calldata blob) private pure returns (bytes memory, bytes memory) {
        if (blob.length < 96) revert MalformedBlob();
        (uint8 algorithmId, bytes memory publicKey, bytes memory signature) =
            abi.decode(blob, (uint8, bytes, bytes));
        if (algorithmId != ALG_SLH_DSA_SHAKE_256S) revert WrongAlgorithmForSlot(algorithmId);
        return (publicKey, signature);
    }

    /// @notice Recover the signer of an ECDSA signature over a digest.
    /// @dev `ecrecover` with both of its traps closed. High-`s` signatures are refused, so one authorization
    ///      cannot be re-encoded into a second distinct-looking one that recovers to the same signer; and a
    ///      recovery to the zero address is refused, so a malformed signature cannot resolve to an "owner" that
    ///      an uninitialised field would match. The length is checked first, so a short argument reverts as an
    ///      invalid signature rather than reading past its own bounds.
    /// @param digest The digest that was signed.
    /// @param signature A 65-byte `(r, s, v)` signature.
    /// @return The recovered signer.
    function _recover(bytes32 digest, bytes memory signature) private pure returns (address) {
        if (signature.length != 65) revert SignatureInvalid();
        bytes32 r;
        bytes32 s;
        uint8 v;
        assembly ("memory-safe") {
            r := mload(add(signature, 0x20))
            s := mload(add(signature, 0x40))
            v := byte(0, mload(add(signature, 0x60)))
        }
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            revert SignatureInvalid();
        }
        address who = ecrecover(digest, v, r, s);
        if (who == address(0)) revert SignatureInvalid();
        return who;
    }

    // --------------------------------------------------------- the transitions

    /// @notice Apply one authorized transition to an account's record.
    /// @dev This is where authority is actually enforced. `_expectedActor` narrowed the credential class before
    ///      the signature work; every branch here re-checks the RESOLVED actor against what its own transition
    ///      requires, so the earlier routing can only ever have been a cheaper refusal, never a grant. Each
    ///      branch also checks the account's state, because a transition that is inapplicable must revert rather
    ///      than burn the holder's nonce on a no-op.
    ///
    ///      The payload is decoded per action, from the same bytes the digest committed to, so an argument
    ///      cannot be substituted between signing and applying. Nothing here republishes the leaf or advances
    ///      the version — the caller does both, once, after this returns.
    /// @param a The account's record, mutated in place.
    /// @param request The authorized request, for its action and its payload.
    /// @param actor Who the credential resolved to.
    /// @param guardianCount How many distinct guardians authorized, when the actor is `GUARDIANS`.
    function _apply(Account storage a, Request calldata request, Actor actor, uint256 guardianCount)
        private
    {
        Action action = request.action;

        if (action == Action.FREEZE) {
            _requireGuardians(actor, guardianCount, a.threshold);
            if (a.frozen) revert InvalidTransition("already frozen");
            a.frozen = true;
        } else if (action == Action.UNFREEZE) {
            // Guardians deliberately cannot reach this. Freezing stops spending
            // and its misuse is denial of service; lifting a freeze un-protects
            // an account that may be mid-theft, which is not bounded at all.
            _requireRecovery(actor);
            if (!a.frozen) revert InvalidTransition("not frozen");
            a.frozen = false;
        } else if (action == Action.INITIATE_ROTATION) {
            _requireRecovery(actor);
            if (a.rotationPending) revert InvalidTransition("rotation already pending");
            (bytes32 newAccess, bytes32 newTransaction, bytes32 newKem) =
                abi.decode(request.payload, (bytes32, bytes32, bytes32));
            if (newAccess == bytes32(0) || newTransaction == bytes32(0) || newKem == bytes32(0)) {
                revert InvalidTransition("rotation needs all three commitments");
            }
            a.rotationPending = true;
            a.pendingRecoveryAccess = newAccess;
            a.pendingRecoveryTransaction = newTransaction;
            a.pendingRecoveryKem = newKem;
            a.rotationInitiatedAt = FinalChainTime.nowMs();
            a.rotationCancels = 0;
        } else if (action == Action.CANCEL_ROTATION) {
            // The tie-breaker for a compromised recovery key, at a HIGHER
            // threshold than freeze: cancelling can block the holder's remedy.
            _requireGuardians(actor, guardianCount, a.cancelThreshold);
            if (!a.rotationPending) revert InvalidTransition("no rotation pending");
            if (a.rotationCancels >= MAX_ROTATION_CANCELS) {
                revert InvalidTransition("cancel budget exhausted; the rotation proceeds");
            }
            a.rotationCancels += 1;
            // Restart the clock rather than dropping the request. Dropping it
            // would make each cancel a full re-initiation by the recovery key,
            // which for a holder rotating a stolen key means going back to
            // their cold key every time a hostile guardian objects.
            a.rotationInitiatedAt = FinalChainTime.nowMs();
        } else if (action == Action.FINALIZE_ROTATION) {
            if (!a.rotationPending) revert InvalidTransition("no rotation pending");
            uint64 readyAt = a.rotationInitiatedAt + a.delayMs;
            if (FinalChainTime.nowMs() < readyAt) revert DelayNotElapsed(readyAt, FinalChainTime.nowMs());
            // **Promotion, atomically.** The committed recovery pair becomes
            // live and a freshly generated pair becomes the new recovery, in one
            // step — so the account is never without a spare, and the live key
            // never gets to touch either slot.
            a.liveAccess = a.recoveryAccess;
            a.liveTransaction = a.recoveryTransaction;
            a.liveKem = a.recoveryKem;
            // The KEM generation moves with the key, in the same statement
            // group. A promotion that advanced the key and not the counter
            // would leave every in-flight envelope claiming a version that no
            // longer describes what it was sealed to — which is the silent
            // failure the counter exists to make visible.
            a.kemVersion += 1;
            a.recoveryAccess = a.pendingRecoveryAccess;
            a.recoveryTransaction = a.pendingRecoveryTransaction;
            a.recoveryKem = a.pendingRecoveryKem;
            a.rotationPending = false;
            a.pendingRecoveryAccess = bytes32(0);
            a.pendingRecoveryTransaction = bytes32(0);
            a.pendingRecoveryKem = bytes32(0);
            a.rotationCancels = 0;
        } else if (action == Action.INITIATE_GUARDIAN_CHANGE) {
            _requireRecovery(actor);
            // **A pending rotation pins the guardian set.** Without this, a
            // compromised recovery key swaps the cancellers out and then rotates
            // — two transactions, nobody legitimate left to object, and the
            // cancel power never engages.
            if (a.rotationPending) revert InvalidTransition("guardian set pinned by a pending rotation");
            if (a.guardianChangePending) revert InvalidTransition("guardian change already pending");
            (address[] memory guardians, uint16 threshold, uint16 cancelThreshold) =
                abi.decode(request.payload, (address[], uint16, uint16));
            _assertGuardianSet(request.wallet, guardians, threshold, cancelThreshold);
            _pendingGuardians[request.wallet] = guardians;
            a.pendingThreshold = threshold;
            a.pendingCancelThreshold = cancelThreshold;
            a.guardianChangeInitiatedAt = FinalChainTime.nowMs();
            a.guardianChangePending = true;
        } else if (action == Action.CANCEL_GUARDIAN_CHANGE) {
            // Cancelled by the OUTGOING set — the people being removed. That is
            // the point: if the recovery key is the compromised credential, the
            // outgoing guardians are the only party with both the standing and
            // the motive to object to their own removal.
            _requireGuardians(actor, guardianCount, a.cancelThreshold);
            if (!a.guardianChangePending) revert InvalidTransition("no guardian change pending");
            a.guardianChangePending = false;
            delete _pendingGuardians[request.wallet];
        } else if (action == Action.FINALIZE_GUARDIAN_CHANGE) {
            if (!a.guardianChangePending) revert InvalidTransition("no guardian change pending");
            if (a.rotationPending) revert InvalidTransition("guardian set pinned by a pending rotation");
            uint64 readyAt = a.guardianChangeInitiatedAt + a.delayMs;
            if (FinalChainTime.nowMs() < readyAt) revert DelayNotElapsed(readyAt, FinalChainTime.nowMs());
            _guardians[request.wallet] = _pendingGuardians[request.wallet];
            a.threshold = a.pendingThreshold;
            a.cancelThreshold = a.pendingCancelThreshold;
            a.guardianChangePending = false;
            delete _pendingGuardians[request.wallet];
        } else if (action == Action.TRANSFER_OWNER) {
            // Selling an account and rotating a compromised key are different
            // operations. Transfer keeps an arbitrary target and stays with the
            // live key; constraining it to promotion would break ordinary use.
            if (actor != Actor.LIVE_KEY) revert CredentialNotPermitted("owner transfer needs the live key");
            if (a.frozen) revert InvalidTransition("a frozen account cannot transfer ownership");
            address newOwner = abi.decode(request.payload, (address));
            if (newOwner == address(0)) revert InvalidTransition("newOwner required");
            address oldOwner = a.owner;
            a.owner = newOwner;
            _unindexOwner(request.wallet, oldOwner);
            _indexOwner(request.wallet, newOwner);
            address[] memory touched = new address[](2);
            touched[0] = oldOwner;
            touched[1] = newOwner;
            _writeOwnerIndex(touched);
        } else if (action == Action.SET_CHAIN_ACCOUNT) {
            // Retired 2026-09-08: creation admits on the proven leaf and reads no
            // row, so the holder declares nothing per chain. The table itself stays —
            // genesis writes it and `restoreAccount` re-applies it — because
            // `FinalSettlement` resolves a zero beneficiary through it toward a
            // chain whose accounts are not EVM addresses. Refused for every
            // credential; the request reverts whole, so its nonce is untouched.
            revert ActionRetired(action);
        } else {
            // **Migration takes no key material, and that is the whole point.**
            // The four commitments were fixed at issuance and are already in
            // this record, so there is nothing to supply and nothing to get
            // wrong. An enable that took key arguments would let one account
            // acquire a different PQ identity per chain — the divergence this
            // plane exists to make impossible.
            //
            // Authorized by the LIVE key, which for a pre-PQ account is its
            // ECDSA owner: `_resolveKeyActor` resolves an owner signature to
            // `LIVE_KEY` only while `!pqEnabled`, and resolves the PQ live
            // credential to `LIVE_KEY` only while `pqEnabled`, so exactly one
            // credential can reach this and it is the right one.
            if (actor != Actor.LIVE_KEY) revert CredentialNotPermitted("PQ migration needs the live key");
            if (a.pqEnabled) revert InvalidTransition("already PQ");
            a.pqEnabled = true;
            // The sentinel is written HERE rather than resolved by each
            // consumer. A post-PQ account's owner is the same constant
            // everywhere by construction; leaving each chain to substitute it
            // would put one conditional on every authorization path and give a
            // future consumer somewhere to disagree.
            a.owner = FINAL_PQ_NATIVE_OWNER;
        }
    }

    /// @notice Set or replace one row of an account's `(chainRef, account)` table.
    /// @dev Replaces in place when the chain already has a row and appends otherwise, so the table never carries
    ///      two answers for one chain and a row is never removed — removing one would strand assets at an
    ///      account nothing can resolve from the published leaf any more. Zero in either half is refused: a
    ///      zero chain names nothing, and a zero account is what settlement reads as "unspecified", which this
    ///      table exists to RESOLVE rather than to restate.
    /// @param wallet The account whose table is being written.
    /// @param chainRef The chain the row addresses.
    /// @param account The account as that chain's own address space expresses it.
    function _setChainAccount(address wallet, bytes32 chainRef, bytes32 account) private {
        if (chainRef == bytes32(0) || account == bytes32(0)) revert InvalidChainAccount(chainRef, account);
        FinalStateTrees.ChainAccount[] storage rows = _chainAccounts[wallet];
        for (uint256 i = 0; i < rows.length; i++) {
            if (rows[i].chainRef == chainRef) {
                rows[i].account = account;
                emit ChainAccountSet(wallet, chainRef, account);
                return;
            }
        }
        rows.push(FinalStateTrees.ChainAccount({chainRef: chainRef, account: account}));
        emit ChainAccountSet(wallet, chainRef, account);
    }

    /// @notice Demand that the recovery key authorized this transition.
    /// @dev The live key is refused here even though it is the account's own, because these transitions are what
    ///      a holder uses to recover FROM a stolen live key.
    /// @param actor The resolved actor.
    function _requireRecovery(Actor actor) private pure {
        if (actor != Actor.RECOVERY_KEY) revert CredentialNotPermitted("recovery key only");
    }

    /// @notice Demand a guardian quorum at a given threshold.
    /// @dev A zero threshold is refused rather than treated as satisfied: an account that configured no guardian
    ///      set has no guardian authority, and a zero-of-zero quorum would make every guardian action free.
    /// @param actor The resolved actor.
    /// @param count How many distinct guardians authorized.
    /// @param threshold How many this transition requires.
    function _requireGuardians(Actor actor, uint256 count, uint16 threshold) private pure {
        if (actor != Actor.GUARDIANS) revert CredentialNotPermitted("guardians only");
        if (threshold == 0) revert CredentialNotPermitted("no guardian set configured");
        if (count < threshold) revert NotAGuardian(address(0));
    }

    // ------------------------------------------------------- guardian shape

    /// @notice The default cancel threshold for a guardian set: a strict majority, never below the freeze
    ///         threshold plus one.
    /// @dev The asymmetry is what bounds a badly-chosen guardian set. Freezing is fail-closed and at worst
    ///      self-harming, so a low bar there is fine; cancelling can block the holder's own remedy, so one
    ///      hostile guardian must never be able to do it alone. An empty set gets zero, which the guardian-set
    ///      checks then read as "this account configured no guardians".
    /// @param count How many guardians the set holds.
    /// @param freezeThreshold The set's freeze threshold.
    /// @return The derived cancel threshold.
    function _cancelThresholdFor(uint16 count, uint16 freezeThreshold) private pure returns (uint16) {
        if (count == 0) return 0;
        uint16 majority = count / 2 + 1;
        return majority > freezeThreshold + 1 ? majority : freezeThreshold + 1;
    }

    /**
     * @notice Structural checks a guardian set must pass before it is installed.
     *
     * @dev These bound SHAPE, not independence. Three addresses one party controls pass every rule here, and
     *      nothing on chain can see that they are one party — so this is deliberately not where the protection
     *      lives. It removes the configurations that are wrong on their face; the threshold asymmetry and the
     *      bounded cancel budget are what make a badly-chosen set survivable.
     *
     *      A zero threshold means "no guardian set", and it must come with no guardians: a populated list at a
     *      zero threshold reads to a holder as protection and provides none.
     * @param wallet The account the set would guard. It may not guard itself.
     * @param guardians The candidate guardian addresses.
     * @param threshold How many must authorize a freeze.
     * @param cancelThreshold How many must authorize a cancel. Must exceed `threshold` and fit the set.
     */
    function _assertGuardianSet(
        address wallet,
        address[] memory guardians,
        uint16 threshold,
        uint16 cancelThreshold
    ) private pure {
        uint256 n = guardians.length;
        if (threshold == 0) {
            if (n != 0) revert InvalidGuardianSet("guardians supplied with a zero threshold");
            return;
        }
        if (n < threshold) revert InvalidGuardianSet("threshold exceeds the guardian count");
        if (n >= 3 && threshold < 2) revert InvalidGuardianSet("three or more guardians need a threshold of at least 2");
        if (cancelThreshold <= threshold) revert InvalidGuardianSet("cancel threshold must exceed the freeze threshold");
        if (cancelThreshold > n) revert InvalidGuardianSet("cancel threshold exceeds the guardian count");
        for (uint256 i = 0; i < n; i++) {
            address g = guardians[i];
            if (g == address(0)) revert InvalidGuardianSet("zero address is not a guardian");
            if (g == wallet) revert InvalidGuardianSet("a wallet cannot guard itself");
            for (uint256 j = i + 1; j < n; j++) {
                if (guardians[j] == g) revert InvalidGuardianSet("duplicate guardian");
            }
        }
    }

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

    /// @notice One account's full record.
    /// @dev Reverts for an address this ledger never opened, rather than returning a zeroed record. A default
    ///      `Account` reads as unfrozen, pre-quantum and owned by nobody, and none of those are safe defaults for
    ///      a caller that failed to check existence separately.
    /// @param wallet The account to read. Must be open.
    /// @return The record as stored, guardian lists excluded.
    function accountOf(address wallet) external view returns (Account memory) {
        if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
        return _accounts[wallet];
    }

    /**
     * @notice Which generation of this account's encapsulation key is current.
     *
     * @dev What a sender stamps into an encrypted intent's header, and what a recipient compares against to
     *      decide which stage's key an envelope was sealed to.
     *
     *      A dedicated getter rather than reaching through `recordOf`, because it is read while composing every
     *      intent and `recordOf` returns the whole account plus four arrays. Zero means the account was never
     *      opened — a genesis starts at one — so a caller cannot mistake "no such account" for "the first key".
     * @param wallet The account to read.
     * @return The current encapsulation-key generation, or zero if the account was never opened.
     */
    function kemVersionOf(address wallet) external view returns (uint16) {
        return _accounts[wallet].kemVersion;
    }

    /// @notice The genesis certificate serial — the raw half of the admission leaf's preimage.
    /// @dev What a carry-over snapshot captures beside the published leaf: the admission hash is not invertible,
    ///      so re-creating the same leaf on a successor ledger needs the serial itself.
    /// @param wallet The account to read.
    /// @return The serial, or zero for an account that was never opened.
    function serialOf(address wallet) external view returns (bytes32) {
        return _accounts[wallet].serial;
    }

    /// @notice Whether an account exists here at all.
    /// @dev The non-reverting existence probe every other read is checked against.
    /// @param wallet The address to test.
    /// @return True once a genesis or a carried-over restore has opened it.
    function isOpen(address wallet) external view returns (bool) {
        return _accounts[wallet].opened;
    }

    /// @notice The live guardian set.
    /// @param wallet The account to read.
    /// @return Its guardians, in the order they were installed. Empty when the account configured none.
    function guardiansOf(address wallet) external view returns (address[] memory) {
        return _guardians[wallet];
    }

    /// @notice The guardian set a pending change would install.
    /// @param wallet The account to read.
    /// @return The staged guardians. Empty when no change is pending.
    function pendingGuardiansOf(address wallet) external view returns (address[] memory) {
        return _pendingGuardians[wallet];
    }

    /// @notice Chains this account is known to exist on — the fan-out's target set.
    /// @dev Observed, not authorized, and add-only: read it to decide where to write, never to decide who may act.
    /// @param wallet The account to read.
    /// @return The chain references observed for it.
    function deploymentsOf(address wallet) external view returns (bytes32[] memory) {
        return _deployments[wallet];
    }

    /// @notice The holder's `(chainRef, account)` table — the published leaf's `deployedChains`.
    /// @param wallet The account to read.
    /// @return Its rows, one per chain it has declared an account on.
    function chainAccountsOf(address wallet) external view returns (FinalStateTrees.ChainAccount[] memory) {
        return _chainAccounts[wallet];
    }

    /// @notice The account this wallet holds on one chain, or zero if it has declared no row there.
    /// @dev What an unspecified settlement beneficiary toward that chain resolves to, and what a co-signer checks
    ///      at admission before a source-side lock. Zero means the holder never declared one, and a caller must
    ///      treat that as "unknown" rather than as an address.
    /// @param wallet The account to read.
    /// @param chainRef The chain to resolve on.
    /// @return The account in that chain's own address space, or zero.
    function accountOn(address wallet, bytes32 chainRef) external view returns (bytes32) {
        FinalStateTrees.ChainAccount[] storage rows = _chainAccounts[wallet];
        for (uint256 i = 0; i < rows.length; i++) {
            if (rows[i].chainRef == chainRef) return rows[i].account;
        }
        return bytes32(0);
    }

    /// @notice Every per-action nonce for one account, in `Action` order.
    /// @dev One call rather than one per action, because a reader fetching them separately would be fetching
    ///      them at that many different blocks and could assemble a set of nonces that never coexisted.
    /// @param wallet The account to read.
    /// @return out The next nonce for each action, indexed by its `Action` ordinal.
    function noncesOf(address wallet) public view returns (uint64[11] memory out) {
        for (uint8 i = 0; i < ACTION_COUNT; i++) out[i] = nonceOf[wallet][i];
    }

    /// @notice Everything the ledger holds for one account, in a single consistent read.
    /// @dev What an off-chain mirror of the account plane reads, and what a carry-over export reads beside
    ///      `accountWords` — this returns the decoded view, `accountWords` returns the record verbatim.
    /// @param wallet The account to read. Must be open.
    /// @return account The record itself.
    /// @return guardians Its live guardian set.
    /// @return pendingGuardians The guardian set a pending change would install, if any.
    /// @return nonces Every per-action nonce, in `Action` order.
    /// @return deployments The chains it has been observed on.
    /// @return chainAccounts Its declared `(chainRef, account)` rows.
    function recordOf(address wallet)
        external
        view
        returns (
            Account memory account,
            address[] memory guardians,
            address[] memory pendingGuardians,
            uint64[11] memory nonces,
            bytes32[] memory deployments,
            FinalStateTrees.ChainAccount[] memory chainAccounts
        )
    {
        if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
        return (
            _accounts[wallet],
            _guardians[wallet],
            _pendingGuardians[wallet],
            noncesOf(wallet),
            _deployments[wallet],
            _chainAccounts[wallet]
        );
    }

    /// @notice How many accounts this ledger has opened.
    /// @dev The upper bound for `walletsBetween`. It never decreases, since an account is never removed.
    /// @return The number of accounts, opened and carried-over alike.
    function walletCount() external view returns (uint256) {
        return _wallets.length;
    }

    /// @notice A page of accounts, in the order they were opened.
    /// @dev Paged rather than returned whole because a publisher iterates every account and this chain caps a
    ///      call's gas like any other; an unbounded getter would stop working at exactly the size where it starts
    ///      to matter. Both bounds are clamped instead of validated, so a caller may page past the end without
    ///      pre-reading `walletCount` and simply receives a shorter page.
    /// @param from First index to return, inclusive.
    /// @param to Last index to return, exclusive. Clamped to the account count.
    /// @return page The accounts in that range.
    function walletsBetween(uint256 from, uint256 to) external view returns (address[] memory page) {
        if (to > _wallets.length) to = _wallets.length;
        if (from > to) from = to;
        page = new address[](to - from);
        for (uint256 i = from; i < to; i++) page[i - from] = _wallets[i];
    }

    /// @notice The published leaf for an account — exactly the committed fields, in the order the account tree
    ///         and every execution-chain verifier fold them.
    /// @dev What a relayer attaches to an intent so an execution chain can prove the account's authority against
    ///      a published root. The record holds more than this; only what is here is committed to, and a field
    ///      that is not in the leaf cannot be proven anywhere else.
    /// @param wallet The account to read. Must be open.
    /// @return Its current published leaf.
    function leafOf(address wallet) external view returns (FinalStateTrees.AccountStateLeaf memory) {
        if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
        return _leafOf(wallet);
    }

    /// @notice Build an account's published leaf from its record.
    /// @dev The single place the committed field set is assembled, so the leaf written on a transition and the
    ///      leaf served to a reader can never disagree. It carries the frozen flag and the owner deliberately:
    ///      an execution chain refuses on the PROVEN freeze and the PROVEN owner rather than on a local copy that
    ///      is only as fresh as the last fan-out.
    /// @param wallet The account to fold. Existence is the caller's to check.
    /// @return The leaf.
    function _leafOf(address wallet) private view returns (FinalStateTrees.AccountStateLeaf memory) {
        Account storage a = _accounts[wallet];
        return FinalStateTrees.AccountStateLeaf({
            wallet: wallet,
            liveAccess: a.liveAccess,
            liveTransaction: a.liveTransaction,
            recoveryAccess: a.recoveryAccess,
            recoveryTransaction: a.recoveryTransaction,
            liveKem: a.liveKem,
            recoveryKem: a.recoveryKem,
            owner: a.owner,
            pqEnabled: a.pqEnabled,
            frozen: a.frozen,
            deployedChains: _chainAccounts[wallet],
            dormantChains: a.dormantChains,
            version: a.version
        });
    }

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

    /// @notice The membership registry the inherited sweep authority resolves against.
    /// @dev The same immutable registry this contract's configuration gate reads, so rescue and configuration
    ///      answer to one place and neither can be pointed somewhere the other does not recognise.
    /// @return The identity registry.
    function _sweepRegistry() internal view override returns (FinalIdentityRegistry) {
        return registry;
    }

    /// @dev No reservation is declared, and the inherited default of zero is correct: nothing is owed, because
    ///      this contract has no payable entrypoint and no custody line — it records, it does not hold. Anything
    ///      it carries arrived by accident and is sweepable in full to one of the two proven destinations.
}

contracts/finalchain/FinalCertificate.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

contracts/finalchain/FinalChainPrecompiles.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

contracts/finalchain/FinalChainTime.sol

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

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

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

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

contracts/finalchain/FinalIdentityRegistry.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        accountOfCertificate[live.certHash] = account;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

contracts/finalchain/FinalPlaneSweep.sol

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

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

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

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

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

contracts/finalchain/FinalPqQuorum.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            valid++;
        }

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

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

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

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

contracts/finalchain/FinalStateTrees.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

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

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

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

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

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

        _bump(TREE_ACCOUNTS, leaves.length);
    }

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

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

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

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        _leaf[treeId][idx] = leaf;

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

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

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

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

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

contracts/utils/FinalSweep.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

abi

[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "registry_",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      },
      {
        "name": "trees_",
        "type": "address",
        "internalType": "contract FinalStateTrees"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "ACCOUNT_WORDS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_CONFIGURE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_COUNT",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SEAL_RESTORE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ALG_SLH_DSA_SHAKE_256S",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DEFAULT_DELAY_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_ACCOUNT_STATE_REQUEST",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "FINAL_PQ_NATIVE_OWNER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "MAX_DELAY_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "MAX_REQUEST_TTL_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "MAX_ROTATION_CANCELS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "MIN_DELAY_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalAccountLedger.Account",
        "components": [
          {
            "name": "opened",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "liveAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "serial",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "kemVersion",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "dormantChains",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "lastActivityAt",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "pqEnabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "frozen",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "version",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "delayMs",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "threshold",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "cancelThreshold",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "rotationPending",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "rotationCancels",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "rotationInitiatedAt",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "pendingRecoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "pendingRecoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "pendingRecoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "guardianChangePending",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "pendingThreshold",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "pendingCancelThreshold",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "guardianChangeInitiatedAt",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountOn",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountWords",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "words",
        "type": "bytes32[15]",
        "internalType": "bytes32[15]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "chainAccountsOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple[]",
        "internalType": "struct FinalStateTrees.ChainAccount[]",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "account",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "configure",
    "inputs": [
      {
        "name": "role",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "k",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "deploymentsOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "domainSeparator",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "guardiansOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address[]",
        "internalType": "address[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "inactivityThresholdOf",
    "inputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "isOpen",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "kemVersionOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "leafOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalStateTrees.AccountStateLeaf",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "liveAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "pqEnabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "frozen",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "deployedChains",
            "type": "tuple[]",
            "internalType": "struct FinalStateTrees.ChainAccount[]",
            "components": [
              {
                "name": "chainRef",
                "type": "bytes32",
                "internalType": "bytes32"
              },
              {
                "name": "account",
                "type": "bytes32",
                "internalType": "bytes32"
              }
            ]
          },
          {
            "name": "dormantChains",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "version",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "nonceOf",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "noncesOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "out",
        "type": "uint64[11]",
        "internalType": "uint64[11]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "observeDeployment",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "onERC1155BatchReceived",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC1155Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC721Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "openAccount",
    "inputs": [
      {
        "name": "batch",
        "type": "tuple[]",
        "internalType": "struct FinalAccountLedger.Genesis[]",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "liveAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "serial",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "pqEnabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "deployedChains",
            "type": "tuple[]",
            "internalType": "struct FinalStateTrees.ChainAccount[]",
            "components": [
              {
                "name": "chainRef",
                "type": "bytes32",
                "internalType": "bytes32"
              },
              {
                "name": "account",
                "type": "bytes32",
                "internalType": "bytes32"
              }
            ]
          },
          {
            "name": "delayMs",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "guardians",
            "type": "address[]",
            "internalType": "address[]"
          },
          {
            "name": "threshold",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "cancelThreshold",
            "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": "openNonce",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "openThreshold",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "openerRole",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "pendingGuardiansOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address[]",
        "internalType": "address[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "recordActivity",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "recordOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "account",
        "type": "tuple",
        "internalType": "struct FinalAccountLedger.Account",
        "components": [
          {
            "name": "opened",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "liveAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "serial",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "kemVersion",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "dormantChains",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "lastActivityAt",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "pqEnabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "frozen",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "version",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "delayMs",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "threshold",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "cancelThreshold",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "rotationPending",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "rotationCancels",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "rotationInitiatedAt",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "pendingRecoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "pendingRecoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "pendingRecoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "guardianChangePending",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "pendingThreshold",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "pendingCancelThreshold",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "guardianChangeInitiatedAt",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "name": "guardians",
        "type": "address[]",
        "internalType": "address[]"
      },
      {
        "name": "pendingGuardians",
        "type": "address[]",
        "internalType": "address[]"
      },
      {
        "name": "nonces",
        "type": "uint64[11]",
        "internalType": "uint64[11]"
      },
      {
        "name": "deployments",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "chainAccounts",
        "type": "tuple[]",
        "internalType": "struct FinalStateTrees.ChainAccount[]",
        "components": [
          {
            "name": "chainRef",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "account",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "refreshDormancy",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "slots",
        "type": "uint8[]",
        "internalType": "uint8[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "registry",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "requestDigest",
    "inputs": [
      {
        "name": "request",
        "type": "tuple",
        "internalType": "struct FinalAccountLedger.Request",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "action",
            "type": "uint8",
            "internalType": "enum FinalAccountLedger.Action"
          },
          {
            "name": "nonce",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "expiresAt",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "payload",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "restoreAccount",
    "inputs": [
      {
        "name": "r",
        "type": "tuple",
        "internalType": "struct FinalAccountLedger.Restored",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "words",
            "type": "bytes32[15]",
            "internalType": "bytes32[15]"
          },
          {
            "name": "guardians",
            "type": "address[]",
            "internalType": "address[]"
          },
          {
            "name": "pendingGuardians",
            "type": "address[]",
            "internalType": "address[]"
          },
          {
            "name": "nonces",
            "type": "uint64[11]",
            "internalType": "uint64[11]"
          },
          {
            "name": "deployments",
            "type": "bytes32[]",
            "internalType": "bytes32[]"
          },
          {
            "name": "chainAccounts",
            "type": "tuple[]",
            "internalType": "struct FinalStateTrees.ChainAccount[]",
            "components": [
              {
                "name": "chainRef",
                "type": "bytes32",
                "internalType": "bytes32"
              },
              {
                "name": "account",
                "type": "bytes32",
                "internalType": "bytes32"
              }
            ]
          },
          {
            "name": "identityLeaf",
            "type": "bytes32",
            "internalType": "bytes32"
          }
        ]
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "restoreNonce",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "restoreSealed",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "sealRestore",
    "inputs": [
      {
        "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": "serialOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "submitRequest",
    "inputs": [
      {
        "name": "request",
        "type": "tuple",
        "internalType": "struct FinalAccountLedger.Request",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "action",
            "type": "uint8",
            "internalType": "enum FinalAccountLedger.Action"
          },
          {
            "name": "nonce",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "expiresAt",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "payload",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      },
      {
        "name": "credential",
        "type": "tuple",
        "internalType": "struct FinalAccountLedger.Credential",
        "components": [
          {
            "name": "pqBlob",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "ownerSignature",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      },
      {
        "name": "guardianAuths",
        "type": "tuple[]",
        "internalType": "struct FinalAccountLedger.GuardianAuth[]",
        "components": [
          {
            "name": "guardian",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "pqBlob",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "version",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "sweepAsset",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "moved",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "sweepableSurplus",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "surplus",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "trees",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalStateTrees"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "walletCount",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "walletsBetween",
    "inputs": [
      {
        "name": "from",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "to",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "page",
        "type": "address[]",
        "internalType": "address[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "walletsByOwner",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address[]",
        "internalType": "address[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "event",
    "name": "AccountOpened",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "owner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "pqEnabled",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "AccountRestored",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "owner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "version",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "ActivityRecorded",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "at",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "AssetSwept",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "indexed": true,
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "ChainAccountSet",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "account",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "DeploymentObserved",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "DormancyRefreshed",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "dormantChains",
        "type": "uint32",
        "indexed": false,
        "internalType": "uint32"
      },
      {
        "name": "version",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "LedgerConfigured",
    "inputs": [
      {
        "name": "openerRole",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "openThreshold",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "RequestApplied",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "action",
        "type": "uint8",
        "indexed": true,
        "internalType": "enum FinalAccountLedger.Action"
      },
      {
        "name": "actor",
        "type": "uint8",
        "indexed": false,
        "internalType": "enum FinalAccountLedger.Actor"
      },
      {
        "name": "version",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "RestoreSealed",
    "inputs": [],
    "anonymous": false
  },
  {
    "type": "error",
    "name": "AccountAlreadyOpen",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "ActionRetired",
    "inputs": [
      {
        "name": "action",
        "type": "uint8",
        "internalType": "enum FinalAccountLedger.Action"
      }
    ]
  },
  {
    "type": "error",
    "name": "AmbiguousCredential",
    "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": "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": "CredentialNotPermitted",
    "inputs": [
      {
        "name": "why",
        "type": "string",
        "internalType": "string"
      }
    ]
  },
  {
    "type": "error",
    "name": "DelayNotElapsed",
    "inputs": [
      {
        "name": "readyAt",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "nowSeconds",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "DuplicateGuardian",
    "inputs": [
      {
        "name": "who",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "ExpiryTooFar",
    "inputs": [
      {
        "name": "span",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "cap",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidChainAccount",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "account",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidDelay",
    "inputs": [
      {
        "name": "delayMs",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidGuardianSet",
    "inputs": [
      {
        "name": "why",
        "type": "string",
        "internalType": "string"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidRestore",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidTransition",
    "inputs": [
      {
        "name": "why",
        "type": "string",
        "internalType": "string"
      }
    ]
  },
  {
    "type": "error",
    "name": "KeyCommitmentMismatch",
    "inputs": []
  },
  {
    "type": "error",
    "name": "LedgerNotConfigured",
    "inputs": []
  },
  {
    "type": "error",
    "name": "MalformedBlob",
    "inputs": []
  },
  {
    "type": "error",
    "name": "NoChainDeclared",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "NoCredential",
    "inputs": []
  },
  {
    "type": "error",
    "name": "NonceMismatch",
    "inputs": [
      {
        "name": "expected",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "supplied",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAGuardian",
    "inputs": [
      {
        "name": "who",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAuthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "PrecompileUnavailable",
    "inputs": [
      {
        "name": "precompile",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "RequestExpired",
    "inputs": [
      {
        "name": "expiresAt",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "nowSeconds",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "RestoreIsSealed",
    "inputs": []
  },
  {
    "type": "error",
    "name": "SignatureInvalid",
    "inputs": []
  },
  {
    "type": "error",
    "name": "SignerLacksRole",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "roleMask",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SignersNotAscending",
    "inputs": [
      {
        "name": "previous",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "next",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepAboveSurplus",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "requested",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "surplus",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepDestinationNotAllowed",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepTransferFailed",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepUnauthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepZeroAmount",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ThresholdIsZero",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ThresholdNotMet",
    "inputs": [
      {
        "name": "valid",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "ThresholdUnreachable",
    "inputs": [
      {
        "name": "live",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "UnknownAccount",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "WrongAlgorithm",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "got",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "required",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "WrongAlgorithmForSlot",
    "inputs": [
      {
        "name": "supplied",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "ZeroSerial",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ]
  }
]

read contract

bytecode · 28,017 bytes

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

disassembly (first 4,000 ops)

pcopoperand
0000PUSH10x80
0002PUSH10x40
0004MSTORE
0005PUSH10x04
0007CALLDATASIZE
0008LT
0009ISZERO
000aPUSH20x0011
000dJUMPI
000ePUSH0
000fDUP1
0010REVERT
0011JUMPDEST
0012PUSH0
0013PUSH0
0014CALLDATALOAD
0015PUSH10xe0
0017SHR
0018DUP1
0019PUSH40x03a2ce34
001eEQ
001fPUSH20x3a61
0022JUMPI
0023DUP1
0024PUSH40x150b7a02
0029EQ
002aPUSH20x3a0b
002dJUMPI
002eDUP1
002fPUSH40x178bcc93
0034EQ
0035PUSH20x39c7
0038JUMPI
0039DUP1
003aPUSH40x23a64734
003fEQ
0040PUSH20x39a8
0043JUMPI
0044DUP1
0045PUSH40x29b57c69
004aEQ
004bPUSH20x398b
004eJUMPI
004fDUP1
0050PUSH40x2cd0ef57
0055EQ
0056PUSH20x396c
0059JUMPI
005aDUP1
005bPUSH40x2f7c88f5
0060EQ
0061PUSH20x3949
0064JUMPI
0065DUP1
0066PUSH40x2fecac30
006bEQ
006cPUSH20x3911
006fJUMPI
0070DUP1
0071PUSH40x33a7c02f
0076EQ
0077PUSH20x38ec
007aJUMPI
007bDUP1
007cPUSH40x34c209d9
0081EQ
0082PUSH20x29dd
0085JUMPI
0086DUP1
0087PUSH40x38eb0788
008cEQ
008dPUSH20x29c1
0090JUMPI
0091DUP1
0092PUSH40x4217f757
0097EQ
0098PUSH20x2972
009bJUMPI
009cDUP1
009dPUSH40x499326fb
00a2EQ
00a3PUSH20x294a
00a6JUMPI
00a7DUP1
00a8PUSH40x51510e4a
00adEQ
00aePUSH20x290f
00b1JUMPI
00b2DUP1
00b3PUSH40x5575e683
00b8EQ
00b9PUSH20x28d0
00bcJUMPI
00bdDUP1
00bePUSH40x57f73695
00c3EQ
00c4PUSH20x2881
00c7JUMPI
00c8DUP1
00c9PUSH40x58137dff
00ceEQ
00cfPUSH20x2844
00d2JUMPI
00d3DUP1
00d4PUSH40x5d419257
00d9EQ
00daPUSH20x2828
00ddJUMPI
00deDUP1
00dfPUSH40x5e732005
00e4EQ
00e5PUSH20x2808
00e8JUMPI
00e9DUP1
00eaPUSH40x60a18008
00efEQ
00f0PUSH20x27d2
00f3JUMPI
00f4DUP1
00f5PUSH40x65e84331
00faEQ
00fbPUSH20x26ec
00feJUMPI
00ffDUP1
0100PUSH40x6b75b6a1
0105EQ
0106PUSH20x246a
0109JUMPI
010aDUP1
010bPUSH40x6edb17dc
0110EQ
0111PUSH20x2427
0114JUMPI
0115DUP1
0116PUSH40x795e8d4a
011bEQ
011cPUSH20x236f
011fJUMPI
0120DUP1
0121PUSH40x7b103999
0126EQ
0127PUSH20x232a
012aJUMPI
012bDUP1
012cPUSH40x8086b8ba
0131EQ
0132PUSH20x22c2
0135JUMPI
0136DUP1
0137PUSH40x81c0dd54
013cEQ
013dPUSH20x22a4
0140JUMPI
0141DUP1
0142PUSH40x82a44847
0147EQ
0148PUSH20x2269
014bJUMPI
014cDUP1
014dPUSH40x840b625b
0152EQ
0153PUSH20x2229
0156JUMPI
0157DUP1
0158PUSH40x8af1bee6
015dEQ
015ePUSH20x21da
0161JUMPI
0162DUP1
0163PUSH40x8f48dc3f
0168EQ
0169PUSH20x217c
016cJUMPI
016dDUP1
016ePUSH40x909473a9
0173EQ
0174PUSH20x2141
0177JUMPI
0178DUP1
0179PUSH40x92880ad0
017eEQ
017fPUSH20x2125
0182JUMPI
0183DUP1
0184PUSH40x94bc4e96
0189EQ
018aPUSH20x1daf
018dJUMPI
018eDUP1
018fPUSH40x96f191d4
0194EQ
0195PUSH20x156e
0198JUMPI
0199DUP1
019aPUSH40x96f51f3a
019fEQ
01a0PUSH20x1250
01a3JUMPI
01a4DUP1
01a5PUSH40xa7ce2703
01aaEQ
01abPUSH20x1229
01aeJUMPI
01afDUP1
01b0PUSH40xb19f4805
01b5EQ
01b6PUSH20x11ee
01b9JUMPI
01baDUP1
01bbPUSH40xb580a787
01c0EQ
01c1PUSH20x11c8
01c4JUMPI
01c5DUP1
01c6PUSH40xbbc7ba35
01cbEQ
01ccPUSH20x1186
01cfJUMPI
01d0DUP1
01d1PUSH40xbc197c81
01d6EQ
01d7PUSH20x10ed
01daJUMPI
01dbDUP1
01dcPUSH40xbd252f13
01e1EQ
01e2PUSH20x10ae
01e5JUMPI
01e6DUP1
01e7PUSH40xbf6bff17
01ecEQ
01edPUSH20x054c
01f0JUMPI
01f1DUP1
01f2PUSH40xc0676111
01f7EQ
01f8PUSH20x0525
01fbJUMPI
01fcDUP1
01fdPUSH40xc06ac6a3
0202EQ
0203PUSH20x03dc
0206JUMPI
0207DUP1
0208PUSH40xc2eeb670
020dEQ
020ePUSH20x03bd
0211JUMPI
0212DUP1
0213PUSH40xe3b5908a
0218EQ
0219PUSH20x0389
021cJUMPI
021dDUP1
021ePUSH40xf23a6e61
0223EQ
0224PUSH20x032e
0227JUMPI
0228DUP1
0229PUSH40xf487885e
022eEQ
022fPUSH20x0311
0232JUMPI
0233DUP1
0234PUSH40xf698da25
0239EQ
023aPUSH20x02ee
023dJUMPI
023eDUP1
023fPUSH40xfc72c4ce
0244EQ
0245PUSH20x02d2
0248JUMPI
0249PUSH40xfd6bc547
024eEQ
024fPUSH20x0256
0252JUMPI
0253PUSH0
0254DUP1
0255REVERT
0256JUMPDEST
0257CALLVALUE
0258PUSH20x02cf
025bJUMPI
025cPUSH10x20
025eCALLDATASIZE
025fPUSH10x03
0261NOT
0262ADD
0263SLT
0264PUSH20x02cf
0267JUMPI
0268PUSH20x026f
026bPUSH20x3a80
026eJUMP
026fJUMPDEST
0270SWAP1
0271PUSH20x0278
0274PUSH20x406b
0277JUMP
0278JUMPDEST
0279POP
027aPUSH10x01
027cPUSH10x01
027ePUSH10xa0
0280SHL
0281SUB
0282DUP3
0283AND
0284DUP1
0285DUP3
0286MSTORE
0287PUSH10x03
0289PUSH10x20
028bMSTORE
028cPUSH10x40
028eDUP3
028fKECCAK256
0290SLOAD
0291PUSH10xff
0293AND
0294ISZERO
0295PUSH20x02bc
0298JUMPI
0299PUSH20x02b8
029cPUSH20x02a4
029fDUP5
02a0PUSH20x52b0
02a3JUMP
02a4JUMPDEST
02a5PUSH10x40
02a7MLOAD
02a8SWAP2
02a9DUP3
02aaSWAP2
02abPUSH10x20
02adDUP4
02aeMSTORE
02afPUSH10x20
02b1DUP4
02b2ADD
02b3SWAP1
02b4PUSH20x3dad
02b7JUMP
02b8JUMPDEST
02b9SUB
02baSWAP1
02bbRETURN
02bcJUMPDEST
02bdPUSH40x3131bf79
02c2PUSH10xe2
02c4SHL
02c5DUP3
02c6MSTORE
02c7PUSH10x04
02c9MSTORE
02caPUSH10x24
02ccSWAP2
02cdPOP
02ceREVERT
02cfJUMPDEST
02d0DUP1
02d1REVERT
02d2JUMPDEST
02d3POP
02d4CALLVALUE
02d5PUSH20x02cf
02d8JUMPI
02d9DUP1
02daPUSH10x03
02dcNOT
02ddCALLDATASIZE
02deADD
02dfSLT
02e0PUSH20x02cf
02e3JUMPI
02e4PUSH10x20
02e6PUSH10x40
02e8MLOAD
02e9PUSH10x0f
02ebDUP2
02ecMSTORE
02edRETURN
02eeJUMPDEST
02efPOP
02f0CALLVALUE
02f1PUSH20x02cf
02f4JUMPI
02f5DUP1
02f6PUSH10x03
02f8NOT
02f9CALLDATASIZE
02faADD
02fbSLT
02fcPUSH20x02cf
02ffJUMPI
0300PUSH10x20
0302PUSH20x0309
0305PUSH20x4ef3
0308JUMP
0309JUMPDEST
030aPUSH10x40
030cMLOAD
030dSWAP1
030eDUP2
030fMSTORE
0310RETURN
0311JUMPDEST
0312POP
0313CALLVALUE
0314PUSH20x02cf
0317JUMPI
0318DUP1
0319PUSH10x03
031bNOT
031cCALLDATASIZE
031dADD
031eSLT
031fPUSH20x02cf
0322JUMPI
0323PUSH10x20
0325SWAP1
0326SLOAD
0327PUSH10x40
0329MLOAD
032aSWAP1
032bDUP2
032cMSTORE
032dRETURN
032eJUMPDEST
032fPOP
0330CALLVALUE
0331PUSH20x02cf
0334JUMPI
0335PUSH10xa0
0337CALLDATASIZE
0338PUSH10x03
033aNOT
033bADD
033cSLT
033dPUSH20x02cf
0340JUMPI
0341PUSH20x0348
0344PUSH20x3a80
0347JUMP
0348JUMPDEST
0349POP
034aPUSH20x0351
034dPUSH20x3a96
0350JUMP
0351JUMPDEST
0352POP
0353PUSH10x84
0355CALLDATALOAD
0356PUSH10x01
0358PUSH10x01
035aPUSH10x40
035cSHL
035dSUB
035eDUP2
035fGT
0360PUSH20x0385
0363JUMPI
0364PUSH20x0371
0367SWAP1
0368CALLDATASIZE
0369SWAP1
036aPUSH10x04
036cADD
036dPUSH20x3ac0
0370JUMP
0371JUMPDEST
0372POP
0373POP
0374PUSH10x40
0376MLOAD
0377PUSH40xf23a6e61
037cPUSH10xe0
037eSHL
037fDUP2
0380MSTORE
0381PUSH10x20
0383SWAP1
0384RETURN
0385JUMPDEST
0386POP
0387DUP1
0388REVERT
0389JUMPDEST
038aPOP
038bCALLVALUE
038cPUSH20x02cf
038fJUMPI
0390PUSH10x20
0392CALLDATASIZE
0393PUSH10x03
0395NOT
0396ADD
0397SLT
0398PUSH20x02cf
039bJUMPI
039cPUSH20x0160
039fPUSH20x03ae
03a2PUSH20x03a9
03a5PUSH20x3a80
03a8JUMP
03a9JUMPDEST
03aaPUSH20x4e87
03adJUMP
03aeJUMPDEST
03afPUSH20x03bb
03b2PUSH10x40
03b4MLOAD
03b5DUP1
03b6SWAP3
03b7PUSH20x3d7d
03baJUMP
03bbJUMPDEST
03bcRETURN
03bdJUMPDEST
03bePOP
03bfCALLVALUE
03c0PUSH20x02cf
03c3JUMPI
03c4DUP1
03c5PUSH10x03
03c7NOT
03c8CALLDATASIZE
03c9ADD
03caSLT
03cbPUSH20x02cf
03ceJUMPI
03cfPUSH10x40
03d1MLOAD
03d2PUSH30x36ee80
03d6DUP2
03d7MSTORE
03d8PUSH10x20
03daSWAP1
03dbRETURN
03dcJUMPDEST
03ddPOP
03deCALLVALUE
03dfPUSH20x02cf
03e2JUMPI
03e3PUSH10x20
03e5CALLDATASIZE
03e6PUSH10x03
03e8NOT
03e9ADD
03eaSLT
03ebPUSH20x02cf
03eeJUMPI
03efPUSH20x03f6
03f2PUSH20x3a80
03f5JUMP
03f6JUMPDEST
03f7SWAP1
03f8PUSH20x03ff
03fbPUSH20x4628
03feJUMP
03ffJUMPDEST
0400POP
0401PUSH20x0160
0404PUSH10x40
0406MLOAD
0407PUSH20x0410
040aDUP3
040bDUP3
040cPUSH20x3f7a
040fJUMP
0410JUMPDEST
0411CALLDATASIZE
0412SWAP1
0413CALLDATACOPY
0414PUSH10x01
0416PUSH10x01
0418PUSH10xa0
041aSHL
041bSUB
041cDUP3
041dAND
041eDUP1
041fDUP3
0420MSTORE
0421PUSH10x03
0423PUSH10x20
0425MSTORE
0426PUSH10x40
0428DUP3
0429KECCAK256
042aSLOAD
042bSWAP1
042cSWAP2
042dSWAP1
042ePUSH10xff
0430AND
0431ISZERO
0432PUSH20x0513
0435JUMPI
0436PUSH20x04c7
0439SWAP3
043aPUSH20x0504
043dDUP3
043eDUP5
043fPUSH20x02b8
0442SWAP5
0443MSTORE
0444PUSH10x03
0446PUSH10x20
0448MSTORE
0449PUSH10x40
044bDUP2
044cKECCAK256
044dDUP6
044eDUP3
044fMSTORE
0450PUSH10x04
0452PUSH10x20
0454MSTORE
0455PUSH20x04f6
0458PUSH20x04ea
045bPUSH10x40
045dDUP5
045eKECCAK256
045fSWAP8
0460DUP1
0461DUP6
0462MSTORE
0463PUSH10x05
0465PUSH10x20
0467MSTORE
0468PUSH20x04db
046bPUSH20x04ba
046ePUSH20x04b4
0471PUSH20x04ae
0474PUSH20x04a8
0477PUSH20x04a2
047aPUSH10x40
047cPUSH20x0487
047fDUP2
0480DUP14
0481KECCAK256
0482SWAP15
0483PUSH20x4e87
0486JUMP
0487JUMPDEST
0488SWAP12
0489DUP9
048aDUP2
048bMSTORE
048cPUSH10x08
048ePUSH10x20
0490MSTORE
0491DUP2
0492DUP2
0493KECCAK256
0494SWAP9
0495DUP2
0496MSTORE
0497PUSH10x0a
0499PUSH10x20
049bMSTORE
049cKECCAK256
049dSWAP10
049ePUSH20x46f0
04a1JUMP
04a2JUMPDEST
04a3SWAP14
04a4PUSH20x3f9b
04a7JUMP
04a8JUMPDEST
04a9SWAP11
04aaPUSH20x3f9b
04adJUMP
04aeJUMPDEST
04afSWAP4
04b0PUSH20x41a9
04b3JUMP
04b4JUMPDEST
04b5SWAP6
04b6PUSH20x4264
04b9JUMP
04baJUMPDEST
04bbSWAP8
04bcPUSH10x40
04beMLOAD
04bfSWAP12
04c0DUP13
04c1DUP1
04c2SWAP13
04c3PUSH20x3bf5
04c6JUMP
04c7JUMPDEST
04c8PUSH20x0560
04cbPUSH20x0380
04ceDUP13
04cfADD
04d0MSTORE
04d1PUSH20x0560
04d4DUP12
04d5ADD
04d6SWAP1
04d7PUSH20x3aed
04daJUMP
04dbJUMPDEST
04dcSWAP1
04ddDUP10
04deDUP3
04dfSUB
04e0PUSH20x03a0
04e3DUP12
04e4ADD
04e5MSTORE
04e6PUSH20x3aed
04e9JUMP
04eaJUMPDEST
04ebSWAP3
04ecPUSH20x03c0
04efDUP9
04f0ADD
04f1SWAP1
04f2PUSH20x3d7d
04f5JUMP
04f6JUMPDEST
04f7DUP6
04f8DUP3
04f9SUB
04faPUSH20x0520
04fdDUP8
04feADD
04ffMSTORE
0500PUSH20x3b59
0503JUMP
0504JUMPDEST
0505SWAP1
0506DUP4
0507DUP3
0508SUB
0509PUSH20x0540
050cDUP6
050dADD
050eMSTORE
050fPUSH20x3b8c
0512JUMP
0513JUMPDEST
0514PUSH10x24
0516SWAP2
0517PUSH40x3131bf79
051cPUSH10xe2
051eSHL
051fDUP3
0520MSTORE
0521PUSH10x04
0523MSTORE
0524REVERT
0525JUMPDEST
0526POP
0527CALLVALUE
0528PUSH20x02cf
052bJUMPI
052cDUP1
052dPUSH10x03
052fNOT
0530CALLDATASIZE
0531ADD
0532SLT
0533PUSH20x02cf
0536JUMPI
0537PUSH10x20
0539PUSH10x01
053bPUSH10x01
053dPUSH10x40
053fSHL
0540SUB
0541PUSH10x02
0543SLOAD
0544AND
0545PUSH10x40
0547MLOAD
0548SWAP1
0549DUP2
054aMSTORE
054bRETURN
054cJUMPDEST
054dPOP
054eCALLVALUE
054fPUSH20x02cf
0552JUMPI
0553PUSH10x60
0555CALLDATASIZE
0556PUSH10x03
0558NOT
0559ADD
055aSLT
055bPUSH20x02cf
055eJUMPI
055fPUSH10x04
0561CALLDATALOAD
0562PUSH10x01
0564PUSH10x01
0566PUSH10x40
0568SHL
0569SUB
056aDUP2
056bGT
056cPUSH20x0385
056fJUMPI
0570PUSH20x057d
0573SWAP1
0574CALLDATASIZE
0575SWAP1
0576PUSH10x04
0578ADD
0579PUSH20x3b29
057cJUMP
057dJUMPDEST
057ePUSH20x0588
0581SWAP3
0582SWAP2
0583SWAP3
0584PUSH20x3bcb
0587JUMP
0588JUMPDEST
0589PUSH10x44
058bCALLDATALOAD
058cPUSH10x01
058ePUSH10x01
0590PUSH10x40
0592SHL
0593SUB
0594DUP2
0595GT
0596PUSH20x08f4
0599JUMPI
059aPUSH20x05a7
059dSWAP1
059eCALLDATASIZE
059fSWAP1
05a0PUSH10x04
05a2ADD
05a3PUSH20x3b29
05a6JUMP
05a7JUMPDEST
05a8SWAP1
05a9SWAP2
05aaPUSH10x01
05acSLOAD
05adDUP1
05aeISZERO
05afPUSH20x109f
05b2JUMPI
05b3PUSH10x02
05b5SLOAD
05b6SWAP4
05b7PUSH10x01
05b9PUSH10x01
05bbPUSH10x40
05bdSHL
05beSUB
05bfDUP6
05c0AND
05c1SWAP4
05c2PUSH10x40
05c4MLOAD
05c5DUP8
05c6PUSH10x60
05c8DUP3
05c9ADD
05caDUP8
05cbPUSH10x20
05cdDUP5
05ceADD
05cfMSTORE
05d0PUSH10x40
05d2DUP1
05d3DUP5
05d4ADD
05d5MSTORE
05d6MSTORE
05d7PUSH10x80
05d9DUP2
05daADD
05dbPUSH10x80
05ddDUP10
05dePUSH10x05
05e0SHL
05e1DUP4
05e2ADD
05e3ADD
05e4SWAP1
05e5DUP12
05e6SWAP1
05e7DUP12
05e8DUP14
05e9PUSH20x01de
05ecNOT
05edSWAP1
05eeCALLDATASIZE
05efSUB
05f0ADD
05f1SWAP1
05f2JUMPDEST
05f3DUP13
05f4DUP2
05f5LT
05f6PUSH20x0f52
05f9JUMPI
05faPOP
05fbPOP
05fcPOP
05fdPOP
05feSWAP3
05ffPUSH20x06d7
0602SWAP6
0603SWAP3
0604DUP3
0605PUSH20x0624
0608PUSH10x01
060aPUSH10x01
060cPUSH10x40
060eSHL
060fSUB
0610SWAP10
0611SWAP8
0612SWAP5
0613PUSH20x06d1
0616SWAP8
0617SUB
0618PUSH10x1f
061aNOT
061bDUP2
061cADD
061dDUP4
061eMSTORE
061fDUP3
0620PUSH20x3f7a
0623JUMP
0624JUMPDEST
0625PUSH10x20
0627DUP2
0628MLOAD
0629SWAP2
062aADD
062bKECCAK256
062cPUSH10x40
062eMLOAD
062fPUSH10x20
0631DUP2
0632ADD
0633SWAP2
0634PUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
0655DUP4
0656MSTORE
0657CHAINID
0658PUSH10x40
065aDUP4
065bADD
065cMSTORE
065dADDRESS
065ePUSH10x60
0660DUP4
0661ADD
0662MSTORE
0663PUSH320x1d2159d826062d6d8bb06b1f7449d53275f95106855af24febedc5e555741358
0684PUSH10x80
0686DUP4
0687ADD
0688MSTORE
0689DUP11
068aDUP8
068bAND
068cPUSH10xa0
068eDUP4
068fADD
0690MSTORE
0691PUSH10xc0
0693DUP3
0694ADD
0695MSTORE
0696PUSH10xc0
0698DUP2
0699MSTORE
069aPUSH20x06a4
069dPUSH10xe0
069fDUP3
06a0PUSH20x3f7a
06a3JUMP
06a4JUMPDEST
06a5MLOAD
06a6SWAP1
06a7KECCAK256
06a8SWAP1
06a9DUP12
06aaSLOAD
06abSWAP3
06acPUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
06cdPUSH20x5747
06d0JUMP
06d1JUMPDEST
06d2POP
06d3PUSH20x4016
06d6JUMP
06d7JUMPDEST
06d8AND
06d9SWAP1
06daPUSH10x01
06dcPUSH10x01
06dePUSH10x40
06e0SHL
06e1SUB
06e2NOT
06e3AND
06e4OR
06e5PUSH10x02
06e7SSTORE
06e8PUSH20x06f0
06ebDUP2
06ecPUSH20x4054
06efJUMP
06f0JUMPDEST
06f1SWAP3
06f2PUSH20x06fe
06f5PUSH10x40
06f7MLOAD
06f8SWAP5
06f9DUP6
06faPUSH20x3f7a
06fdJUMP
06feJUMPDEST
06ffDUP2
0700DUP5
0701MSTORE
0702PUSH10x1f
0704NOT
0705PUSH20x070d
0708DUP4
0709PUSH20x4054
070cJUMP
070dJUMPDEST
070eADD
070fDUP4
0710JUMPDEST
0711DUP2
0712DUP2
0713LT
0714PUSH20x0f3b
0717JUMPI
0718POP
0719POP
071aPUSH20x0722
071dDUP3
071ePUSH20x42ef
0721JUMP
0722JUMPDEST
0723PUSH20x072b
0726DUP4
0727PUSH20x42ef
072aJUMP
072bJUMPDEST
072cSWAP1
072dPUSH30x36ee80
0731DUP6
0732JUMPDEST
0733DUP6
0734DUP2
0735LT
0736PUSH20x08f8
0739JUMPI
073aPOP
073bDUP6
073cSWAP3
073dSWAP2
073eSWAP1
073fPOP
0740DUP7
0741PUSH320x0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d
0762PUSH10x01
0764PUSH10x01
0766PUSH10xa0
0768SHL
0769SUB
076aAND
076bEXTCODESIZE
076cISZERO
076dPUSH20x08f4
0770JUMPI
0771DUP4
0772PUSH20x078f
0775SWAP2
0776PUSH10x40
0778MLOAD
0779DUP1
077aSWAP4
077bDUP2
077cSWAP3
077dPUSH40x2728f271
0782PUSH10xe2
0784SHL
0785DUP4
0786MSTORE
0787PUSH10x04
0789DUP4
078aADD
078bPUSH20x412c
078eJUMP
078fJUMPDEST
0790SUB
0791DUP2
0792DUP4
0793PUSH320x0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d
07b4PUSH10x01
07b6PUSH10x01
07b8PUSH10xa0
07baSHL
07bbSUB
07bcAND
07bdGAS
07beCALL
07bfSWAP1
07c0DUP2
07c1ISZERO
07c2PUSH20x08e9
07c5JUMPI
07c6DUP5
07c7SWAP2
07c8PUSH20x08d4
07cbJUMPI
07ccJUMPDEST
07cdPOP
07cePOP
07cfPUSH320x0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d
07f0PUSH10x01
07f2PUSH10x01
07f4PUSH10xa0
07f6SHL
07f7SUB
07f8AND
07f9EXTCODESIZE
07faISZERO
07fbPUSH20x08c5
07feJUMPI
07ffPUSH10x40
0801MLOAD
0802PUSH40xabf1570d
0807PUSH10xe0
0809SHL
080aDUP2
080bMSTORE
080cSWAP2
080dDUP4
080eSWAP2
080fDUP4
0810SWAP2
0811DUP3
0812SWAP2
0813PUSH20x0820
0816SWAP2
0817SWAP1
0818PUSH10x04
081aDUP5
081bADD
081cPUSH20x4c4b
081fJUMP
0820JUMPDEST
0821SUB
0822DUP2
0823DUP4
0824PUSH320x0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d
0845PUSH10x01
0847PUSH10x01
0849PUSH10xa0
084bSHL
084cSUB
084dAND
084eGAS
084fCALL
0850DUP1
0851ISZERO
0852PUSH20x08c9
0855JUMPI
0856PUSH20x08b0
0859JUMPI
085aJUMPDEST
085bPOP
085cPOP
085dPUSH20x0865
0860DUP3
0861PUSH20x42ef
0864JUMP
0865JUMPDEST
0866SWAP2
0867DUP4
0868JUMPDEST
0869DUP2
086aDUP2
086bLT
086cPUSH20x087c
086fJUMPI
0870DUP5
0871PUSH20x0879
0874DUP6
0875PUSH20x5b3c
0878JUMP
0879JUMPDEST
087aDUP1
087bRETURN
087cJUMPDEST
087dDUP1
087ePUSH20x0896
0881PUSH20x0100
0884PUSH20x0890
0887PUSH10x01
0889SWAP5
088aDUP7
088bDUP9
088cPUSH20x4e64
088fJUMP
0890JUMPDEST
0891ADD
0892PUSH20x3fee
0895JUMP
0896JUMPDEST
0897PUSH20x08a0
089aDUP3
089bDUP8
089cPUSH20x4118
089fJUMP
08a0JUMPDEST
08a1SWAP1
08a2DUP4
08a3DUP1
08a4PUSH10xa0
08a6SHL
08a7SUB
08a8AND
08a9SWAP1
08aaMSTORE
08abADD
08acPUSH20x0868
08afJUMP
08b0JUMPDEST
08b1DUP2
08b2PUSH20x08ba
08b5SWAP2
08b6PUSH20x3f7a
08b9JUMP
08baJUMPDEST
08bbPUSH20x08c5
08beJUMPI
08bfDUP3
08c0DUP5
08c1PUSH20x085a
08c4JUMP
08c5JUMPDEST
08c6DUP3
08c7DUP1
08c8REVERT
08c9JUMPDEST
08caPUSH10x40
08ccMLOAD
08cdRETURNDATASIZE
08ceDUP5
08cfDUP3
08d0RETURNDATACOPY
08d1RETURNDATASIZE
08d2SWAP1
08d3REVERT
08d4JUMPDEST
08d5DUP2
08d6PUSH20x08de
08d9SWAP2
08daPUSH20x3f7a
08ddJUMP
08deJUMPDEST
08dfPUSH20x08c5
08e2JUMPI
08e3DUP3
08e4DUP8
08e5PUSH20x07cc
08e8JUMP
08e9JUMPDEST
08eaPUSH10x40
08ecMLOAD
08edRETURNDATASIZE
08eeDUP7
08efDUP3
08f0RETURNDATACOPY
08f1RETURNDATASIZE
08f2SWAP1
08f3REVERT
08f4JUMPDEST
08f5DUP4
08f6DUP1
08f7REVERT
08f8JUMPDEST
08f9SWAP4
08faSWAP1
08fbSWAP6
08fcSWAP5
08fdSWAP2
08fePUSH20x0908
0901DUP6
0902DUP5
0903DUP5
0904PUSH20x4e64
0907JUMP
0908JUMPDEST
0909SWAP8
090aPUSH20x0911
090dPUSH20x406b
0910JUMP
0911JUMPDEST
0912POP
0913PUSH10x01
0915PUSH10x01
0917PUSH10xa0
0919SHL
091aSUB
091bPUSH20x0923
091eDUP11
091fPUSH20x3fee
0922JUMP
0923JUMPDEST
0924AND
0925DUP8
0926MSTORE
0927PUSH10x03
0929PUSH10x20
092bMSTORE
092cPUSH10x40
092eDUP8
092fKECCAK256
0930SWAP8
0931DUP9
0932SLOAD
0933SWAP1
0934PUSH10xff
0936DUP3
0937AND
0938PUSH20x0f17
093bJUMPI
093cPUSH20x0160
093fDUP12
0940ADD
0941PUSH10x01
0943PUSH10x01
0945PUSH10x40
0947SHL
0948SUB
0949PUSH20x0951
094cDUP3
094dPUSH20x4002
0950JUMP
0951JUMPDEST
0952AND
0953PUSH20x0f08
0956JUMPI
0957POP
0958PUSH40x05265c00
095dSWAP2
095eJUMPDEST
095fDUP2
0960PUSH10x01
0962PUSH10x01
0964PUSH10x40
0966SHL
0967SUB
0968DUP5
0969AND
096aLT
096bDUP1
096cISZERO
096dPUSH20x0ef2
0970JUMPI
0971JUMPDEST
0972PUSH20x0ed6
0975JUMPI
0976PUSH20x0983
0979PUSH20x0140
097cDUP14
097dADD
097eDUP14
097fPUSH20x4bbd
0982JUMP
0983JUMPDEST
0984SWAP1
0985POP
0986ISZERO
0987PUSH20x0eb2
098aJUMPI
098bPUSH10xe0
098dDUP13
098eADD
098fCALLDATALOAD
0990SWAP1
0991DUP2
0992ISZERO
0993PUSH20x0e8e
0996JUMPI
0997SWAP1
0998PUSH10x01
099aPUSH20x01a0
099dSWAP5
099eSWAP4
099fSWAP3
09a0PUSH20x0a21
09a3DUP16
09a4DUP15
09a5PUSH20x01c0
09a8DUP3
09a9ADD
09aaSWAP1
09abPUSH20xffff
09aePUSH20x09b6
09b1DUP4
09b2PUSH20x5f7b
09b5JUMP
09b6JUMPDEST
09b7AND
09b8ISZERO
09b9ISZERO
09baSWAP1
09bbPOP
09bcPUSH20x0e7d
09bfJUMPI
09c0POP
09c1PUSH20x09e8
09c4PUSH20x09d1
09c7PUSH20x0180
09caDUP4
09cbADD
09ccDUP4
09cdPUSH20x4acf
09d0JUMP
09d1JUMPDEST
09d2SWAP1
09d3POP
09d4PUSH20xffff
09d7PUSH20x09e1
09daDUP12
09dbDUP6
09dcADD
09ddPUSH20x5f7b
09e0JUMP
09e1JUMPDEST
09e2SWAP2
09e3AND
09e4PUSH20x68a4
09e7JUMP
09e8JUMPDEST
09e9SWAP8
09eaDUP9
09ebSWAP2
09ecJUMPDEST
09edPUSH20x0a1b
09f0PUSH20x09f8
09f3DUP3
09f4PUSH20x3fee
09f7JUMP
09f8JUMPDEST
09f9SWAP2
09faPUSH20x0a13
09fdPUSH20x0a0a
0a00PUSH20x0180
0a03DUP4
0a04ADD
0a05DUP4
0a06PUSH20x4acf
0a09JUMP
0a0aJUMPDEST
0a0bSWAP6
0a0cSWAP1
0a0dSWAP3
0a0eADD
0a0fPUSH20x5f7b
0a12JUMP
0a13JUMPDEST
0a14SWAP4
0a15CALLDATASIZE
0a16SWAP2
0a17PUSH20x5248
0a1aJUMP
0a1bJUMPDEST
0a1cSWAP1
0a1dPUSH20x63b3
0a20JUMP
0a21JUMPDEST
0a22PUSH10xff
0a24NOT
0a25AND
0a26OR
0a27DUP13
0a28SSTORE
0a29PUSH10x20
0a2bDUP14
0a2cADD
0a2dCALLDATALOAD
0a2ePUSH10x01
0a30DUP14
0a31ADD
0a32SSTORE
0a33PUSH10x40
0a35DUP14
0a36ADD
0a37CALLDATALOAD
0a38PUSH10x02
0a3aDUP14
0a3bADD
0a3cSSTORE
0a3dPUSH10x60
0a3fDUP14
0a40ADD
0a41CALLDATALOAD
0a42PUSH10x03
0a44DUP14
0a45ADD
0a46SSTORE
0a47PUSH10x80
0a49DUP14
0a4aADD
0a4bCALLDATALOAD
0a4cPUSH10x04
0a4eDUP14
0a4fADD
0a50SSTORE
0a51PUSH10x05
0a53DUP13
0a54ADD
0a55SSTORE
0a56PUSH10xa0
0a58DUP13
0a59ADD
0a5aCALLDATALOAD
0a5bPUSH10x06
0a5dDUP13
0a5eADD
0a5fSSTORE
0a60PUSH10xc0
0a62DUP13
0a63ADD
0a64CALLDATALOAD
0a65PUSH10x07
0a67DUP13
0a68ADD
0a69SSTORE
0a6aPUSH10x08
0a6cDUP12
0a6dADD
0a6ePUSH10x01
0a70PUSH20xffff
0a73NOT
0a74DUP3
0a75SLOAD
0a76AND
0a77OR
0a78SWAP1
0a79SSTORE
0a7aPUSH20x0a86
0a7dPUSH20x0100
0a80DUP14
0a81ADD
0a82PUSH20x3fee
0a85JUMP
0a86JUMPDEST
0a87PUSH10x09
0a89DUP13
0a8aADD
0a8bDUP1
0a8cSLOAD
0a8dPUSH10x01
0a8fPUSH10x01
0a91PUSH10xa0
0a93SHL
0a94SUB
0a95NOT
0a96AND
0a97PUSH10x01
0a99PUSH10x01
0a9bPUSH10xa0
0a9dSHL
0a9eSUB
0a9fSWAP3
0aa0SWAP1
0aa1SWAP3
0aa2AND
0aa3SWAP2
0aa4SWAP1
0aa5SWAP2
0aa6OR
0aa7SWAP1
0aa8SSTORE
0aa9PUSH20x0ab5
0aacPUSH20x0120
0aafDUP14
0ab0ADD
0ab1PUSH20x5f8a
0ab4JUMP
0ab5JUMPDEST
0ab6PUSH10x09
0ab8DUP13
0ab9ADD
0abaDUP1
0abbSLOAD
0abcPUSH10xff
0abePUSH10xa0
0ac0SHL
0ac1NOT
0ac2AND
0ac3SWAP2
0ac4ISZERO
0ac5ISZERO
0ac6PUSH10xa0
0ac8SHL
0ac9PUSH10xff
0acbPUSH10xa0
0acdSHL
0aceAND
0acfSWAP2
0ad0SWAP1
0ad1SWAP2
0ad2OR
0ad3SWAP1
0ad4SSTORE
0ad5DUP10
0ad6JUMPDEST
0ad7PUSH20x0ae4
0adaPUSH20x0140
0addDUP15
0adeADD
0adfDUP15
0ae0PUSH20x4bbd
0ae3JUMP
0ae4JUMPDEST
0ae5SWAP1
0ae6POP
0ae7DUP2
0ae8LT
0ae9ISZERO
0aeaPUSH20x0b3a
0aedJUMPI
0aeePUSH10x01
0af0SWAP1
0af1PUSH20x0b34
0af4DUP15
0af5PUSH20x0afd
0af8DUP2
0af9PUSH20x3fee
0afcJUMP
0afdJUMPDEST
0afeSWAP1
0affPUSH10x20
0b01PUSH20x0b2c
0b04DUP6
0b05PUSH20x0b19
0b08PUSH20x0b1f
0b0bDUP3
0b0cPUSH20x0b19
0b0fPUSH20x0140
0b12DUP9
0b13ADD
0b14DUP9
0b15PUSH20x4bbd
0b18JUMP
0b19JUMPDEST
0b1aSWAP1
0b1bPUSH20x4bf2
0b1eJUMP
0b1fJUMPDEST
0b20CALLDATALOAD
0b21SWAP5
0b22PUSH20x0140
0b25DUP2
0b26ADD
0b27SWAP1
0b28PUSH20x4bbd
0b2bJUMP
0b2cJUMPDEST
0b2dADD
0b2eCALLDATALOAD
0b2fSWAP2
0b30PUSH20x59b9
0b33JUMP
0b34JUMPDEST
0b35ADD
0b36PUSH20x0ad6
0b39JUMP
0b3aJUMPDEST
0b3bPOP
0b3cSWAP5
0b3dSWAP8
0b3eSWAP4
0b3fSWAP6
0b40SWAP9
0b41SWAP10
0b42SWAP1
0b43PUSH10x01
0b45PUSH10x01
0b47PUSH10x40
0b49SHL
0b4aSUB
0b4bPUSH10x0a
0b4dPUSH20x0bc7
0b50SWAP5
0b51SWAP4
0b52SWAP14
0b53SWAP6
0b54SWAP14
0b55PUSH10x01
0b57PUSH10xb0
0b59SHL
0b5aDUP4
0b5bPUSH10xb0
0b5dSHL
0b5eNOT
0b5fPUSH10x09
0b61DUP4
0b62ADD
0b63SLOAD
0b64AND
0b65OR
0b66PUSH10x09
0b68DUP3
0b69ADD
0b6aSSTORE
0b6bADD
0b6cSWAP2
0b6dAND
0b6ePUSH10x01
0b70PUSH10x01
0b72PUSH10x40
0b74SHL
0b75SUB
0b76NOT
0b77DUP3
0b78SLOAD
0b79AND
0b7aOR
0b7bDUP2
0b7cSSTORE
0b7dPUSH20x0ba8
0b80PUSH20x0b8c
0b83PUSH20x01a0
0b86DUP7
0b87ADD
0b88PUSH20x5f7b
0b8bJUMP
0b8cJUMPDEST
0b8dDUP3
0b8eSLOAD
0b8fPUSH20xffff
0b92PUSH10x40
0b94SHL
0b95NOT
0b96AND
0b97PUSH10x40
0b99SWAP2
0b9aSWAP1
0b9bSWAP2
0b9cSHL
0b9dPUSH20xffff
0ba0PUSH10x40
0ba2SHL
0ba3AND
0ba4OR
0ba5DUP3
0ba6SSTORE
0ba7JUMP
0ba8JUMPDEST
0ba9DUP1
0baaSLOAD
0babPUSH20xffff
0baePUSH10x50
0bb0SHL
0bb1NOT
0bb2AND
0bb3PUSH10x50
0bb5SWAP3
0bb6SWAP1
0bb7SWAP3
0bb8SHL
0bb9PUSH20xffff
0bbcPUSH10x50
0bbeSHL
0bbfAND
0bc0SWAP2
0bc1SWAP1
0bc2SWAP2
0bc3OR
0bc4SWAP1
0bc5SSTORE
0bc6JUMP
0bc7JUMPDEST
0bc8PUSH20x0bd5
0bcbPUSH20x0180
0bceDUP3
0bcfADD
0bd0DUP3
0bd1PUSH20x4acf
0bd4JUMP
0bd5JUMPDEST
0bd6SWAP1
0bd7PUSH10x01
0bd9PUSH10x01
0bdbPUSH10xa0
0bddSHL
0bdeSUB
0bdfPUSH20x0be7
0be2DUP5
0be3PUSH20x3fee
0be6JUMP
0be7JUMPDEST
0be8AND
0be9DUP11
0beaMSTORE
0bebPUSH10x04
0bedPUSH10x20
0befMSTORE
0bf0PUSH10x40
0bf2DUP11
0bf3KECCAK256
0bf4SWAP1
0bf5PUSH10x01
0bf7PUSH10x01
0bf9PUSH10x40
0bfbSHL
0bfcSUB
0bfdDUP4
0bfeGT
0bffPUSH20x0e69
0c02JUMPI
0c03PUSH20x0c0c
0c06DUP4
0c07DUP4
0c08PUSH20x4b34
0c0bJUMP
0c0cJUMPDEST
0c0dSWAP1
0c0eDUP11
0c0fMSTORE
0c10PUSH10x20
0c12DUP11
0c13KECCAK256
0c14DUP11
0c15JUMPDEST
0c16DUP4
0c17DUP2
0c18LT
0c19PUSH20x0e4e
0c1cJUMPI
0c1dPOP
0c1ePOP
0c1fPOP
0c20POP
0c21PUSH20x0cbb
0c24DUP2
0c25PUSH20x0c38
0c28PUSH20x0c33
0c2bPUSH20x0cc0
0c2eSWAP5
0c2fPUSH20x3fee
0c32JUMP
0c33JUMPDEST
0c34PUSH20x4c02
0c37JUMP
0c38JUMPDEST
0c39PUSH20x0c57
0c3cPUSH20x0c44
0c3fDUP3
0c40PUSH20x3fee
0c43JUMP
0c44JUMPDEST
0c45PUSH20x0c51
0c48PUSH20x0100
0c4bDUP5
0c4cADD
0c4dPUSH20x3fee
0c50JUMP
0c51JUMPDEST
0c52SWAP1
0c53PUSH20x5ae6
0c56JUMP
0c57JUMPDEST
0c58PUSH20x0c60
0c5bDUP2
0c5cPUSH20x3fee
0c5fJUMP
0c60JUMPDEST
0c61PUSH20x0c6d
0c64PUSH20x0100
0c67DUP4
0c68ADD
0c69PUSH20x3fee
0c6cJUMP
0c6dJUMPDEST
0c6ePUSH320x568403fd429f133b4cc18a945d220c328c59a445a8122f240f1d74fd55fb6937
0c8fPUSH10x20
0c91PUSH20x0c9d
0c94PUSH20x0120
0c97DUP7
0c98ADD
0c99PUSH20x5f8a
0c9cJUMP
0c9dJUMPDEST
0c9ePUSH10x40
0ca0MLOAD
0ca1SWAP1
0ca2ISZERO
0ca3ISZERO
0ca4DUP2
0ca5MSTORE
0ca6PUSH10x01
0ca8PUSH10x01
0caaPUSH10xa0
0cacSHL
0cadSUB
0caeSWAP4
0cafDUP5
0cb0AND
0cb1SWAP5
0cb2SWAP1
0cb3SWAP4
0cb4AND
0cb5SWAP3
0cb6LOG3
0cb7PUSH20x3fee
0cbaJUMP
0cbbJUMPDEST
0cbcPUSH20x52b0
0cbfJUMP
0cc0JUMPDEST
0cc1PUSH20x0cca
0cc4DUP3
0cc5DUP11
0cc6PUSH20x4118
0cc9JUMP
0ccaJUMPDEST
0ccbMSTORE
0cccPUSH20x0cd5
0ccfDUP2
0cd0DUP10
0cd1PUSH20x4118
0cd4JUMP
0cd5JUMPDEST
0cd6POP
0cd7PUSH20x0ce9
0cdaPUSH20x0ce4
0cddDUP3
0cdeDUP9
0cdfDUP9
0ce0PUSH20x4e64
0ce3JUMP
0ce4JUMPDEST
0ce5PUSH20x3fee
0ce8JUMP
0ce9JUMPDEST
0ceaPUSH10x40
0cecMLOAD
0cedPUSH40x82edfbd9
0cf2PUSH10xe0
0cf4SHL
0cf5DUP2
0cf6MSTORE
0cf7PUSH10x01
0cf9PUSH10x01
0cfbPUSH10xa0
0cfdSHL
0cfeSUB
0cffSWAP2
0d00DUP3
0d01AND
0d02PUSH10x04
0d04DUP3
0d05ADD
0d06MSTORE
0d07SWAP2
0d08SWAP1
0d09PUSH10x20
0d0bSWAP1
0d0cDUP4
0d0dSWAP1
0d0ePUSH10x24
0d10SWAP1
0d11DUP3
0d12SWAP1
0d13PUSH320x0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d
0d34AND
0d35GAS
0d36STATICCALL
0d37DUP1
0d38ISZERO
0d39PUSH20x0e43
0d3cJUMPI
0d3dDUP9
0d3eSWAP1
0d3fPUSH20x0e0d
0d42JUMPI
0d43JUMPDEST
0d44PUSH10x01
0d46SWAP3
0d47POP
0d48PUSH20x0d51
0d4bDUP3
0d4cDUP7
0d4dPUSH20x4118
0d50JUMP
0d51JUMPDEST
0d52MSTORE
0d53PUSH20x0d5d
0d56DUP2
0d57DUP9
0d58DUP9
0d59PUSH20x4e64
0d5cJUMP
0d5dJUMPDEST
0d5ePUSH10x40
0d60MLOAD
0d61PUSH10x20
0d63DUP2
0d64ADD
0d65SWAP1
0d66PUSH10x20
0d68DUP4
0d69ADD
0d6aCALLDATALOAD
0d6bDUP3
0d6cMSTORE
0d6dPUSH10x40
0d6fDUP4
0d70ADD
0d71CALLDATALOAD
0d72PUSH10x40
0d74DUP3
0d75ADD
0d76MSTORE
0d77PUSH10x60
0d79DUP4
0d7aADD
0d7bCALLDATALOAD
0d7cPUSH10x60
0d7eDUP3
0d7fADD
0d80MSTORE
0d81PUSH10x80
0d83DUP4
0d84ADD
0d85CALLDATALOAD
0d86PUSH10x80
0d88DUP3
0d89ADD
0d8aMSTORE
0d8bPUSH10xa0
0d8dDUP4
0d8eADD
0d8fCALLDATALOAD
0d90PUSH10xa0
0d92DUP3
0d93ADD
0d94MSTORE
0d95PUSH10xc0
0d97DUP4
0d98ADD
0d99CALLDATALOAD
0d9aPUSH10xc0
0d9cDUP3
0d9dADD
0d9eMSTORE
0d9fPUSH10xc0
0da1DUP2
0da2MSTORE
0da3PUSH20x0dad
0da6PUSH10xe0
0da8DUP3
0da9PUSH20x3f7a
0dacJUMP
0dadJUMPDEST
0daeMLOAD
0dafSWAP1
0db0KECCAK256
0db1PUSH10x40
0db3MLOAD
0db4SWAP1
0db5PUSH10xe0
0db7PUSH10x20
0db9DUP4
0dbaADD
0dbbSWAP4
0dbcPUSH320xcc25d3fea88291f95ddfb5590a6b760f02245a0e4ca7c0b69285c6cd26543afd
0dddDUP6
0ddeMSTORE
0ddfADD
0de0CALLDATALOAD
0de1PUSH10x40
0de3DUP4
0de4ADD
0de5MSTORE
0de6PUSH10x60
0de8DUP3
0de9ADD
0deaMSTORE
0debPUSH10x60
0dedDUP2
0deeMSTORE
0defPUSH20x0df9
0df2PUSH10x80
0df4DUP3
0df5PUSH20x3f7a
0df8JUMP
0df9JUMPDEST
0dfaMLOAD
0dfbSWAP1
0dfcKECCAK256
0dfdPUSH20x0e06
0e00DUP3
0e01DUP8
0e02PUSH20x4118
0e05JUMP
0e06JUMPDEST
0e07MSTORE
0e08ADD
0e09PUSH20x0732
0e0cJUMP
0e0dJUMPDEST
0e0ePOP
0e0fPUSH10x20
0e11DUP3
0e12RETURNDATASIZE
0e13DUP3
0e14GT
0e15PUSH20x0e3b
0e18JUMPI
0e19JUMPDEST
0e1aDUP2
0e1bPUSH20x0e26
0e1ePUSH10x20
0e20SWAP4
0e21DUP4
0e22PUSH20x3f7a
0e25JUMP
0e26JUMPDEST
0e27DUP2
0e28ADD
0e29SUB
0e2aSLT
0e2bPUSH20x0e37
0e2eJUMPI
0e2fPUSH10x01
0e31SWAP2
0e32MLOAD
0e33PUSH20x0d43
0e36JUMP
0e37JUMPDEST
0e38PUSH0
0e39DUP1
0e3aREVERT
0e3bJUMPDEST
0e3cRETURNDATASIZE
0e3dSWAP2
0e3ePOP
0e3fPUSH20x0e19
0e42JUMP
0e43JUMPDEST
0e44PUSH10x40
0e46MLOAD
0e47RETURNDATASIZE
0e48DUP11
0e49DUP3
0e4aRETURNDATACOPY
0e4bRETURNDATASIZE
0e4cSWAP1
0e4dREVERT
0e4eJUMPDEST
0e4fPUSH10x01
0e51SWAP1
0e52PUSH10x20
0e54PUSH20x0e5c
0e57DUP6
0e58PUSH20x3fee
0e5bJUMP
0e5cJUMPDEST
0e5dSWAP5
0e5eADD
0e5fSWAP4
0e60DUP2
0e61DUP5
0e62ADD
0e63SSTORE
0e64ADD
0e65PUSH20x0c15
0e68JUMP
0e69JUMPDEST
0e6aPUSH40x4e487b71
0e6fPUSH10xe0
0e71SHL
0e72DUP12
0e73MSTORE
0e74PUSH10x41
0e76PUSH10x04
0e78MSTORE
0e79PUSH10x24
0e7bDUP12
0e7cREVERT
0e7dJUMPDEST
0e7ePUSH20x0e86
0e81SWAP1
0e82PUSH20x5f7b
0e85JUMP
0e86JUMPDEST
0e87SWAP8
0e88DUP9
0e89SWAP2
0e8aPUSH20x09ec
0e8dJUMP
0e8eJUMPDEST
0e8fPUSH10x24
0e91DUP12
0e92PUSH20x0e9a
0e95DUP16
0e96PUSH20x3fee
0e99JUMP
0e9aJUMPDEST
0e9bPUSH40x16efda7d
0ea0PUSH10xe2
0ea2SHL
0ea3DUP3
0ea4MSTORE
0ea5PUSH10x01
0ea7PUSH10x01
0ea9PUSH10xa0
0eabSHL
0eacSUB
0eadAND
0eaePUSH10x04
0eb0MSTORE
0eb1REVERT
0eb2JUMPDEST
0eb3PUSH10x24
0eb5DUP11
0eb6PUSH20x0ebe
0eb9DUP15
0ebaPUSH20x3fee
0ebdJUMP
0ebeJUMPDEST
0ebfPUSH40xfe3230df
0ec4PUSH10xe0
0ec6SHL
0ec7DUP3
0ec8MSTORE
0ec9PUSH10x01
0ecbPUSH10x01
0ecdPUSH10xa0
0ecfSHL
0ed0SUB
0ed1AND
0ed2PUSH10x04
0ed4MSTORE
0ed5REVERT
0ed6JUMPDEST
0ed7PUSH40x10b0f875
0edcPUSH10xe1
0edeSHL
0edfDUP11
0ee0MSTORE
0ee1PUSH10x01
0ee3PUSH10x01
0ee5PUSH10x40
0ee7SHL
0ee8SUB
0ee9DUP4
0eeaAND
0eebPUSH10x04
0eedMSTORE
0eeePUSH10x24
0ef0DUP11
0ef1REVERT
0ef2JUMPDEST
0ef3POP
0ef4PUSH40x9a7ec800
0ef9PUSH10x01
0efbPUSH10x01
0efdPUSH10x40
0effSHL
0f00SUB
0f01DUP5
0f02AND
0f03GT
0f04PUSH20x0971
0f07JUMP
0f08JUMPDEST
0f09PUSH20x0f11
0f0cSWAP1
0f0dPUSH20x4002
0f10JUMP
0f11JUMPDEST
0f12SWAP2
0f13PUSH20x095e
0f16JUMP
0f17JUMPDEST
0f18PUSH10x24
0f1aDUP10
0f1bPUSH20x0f23
0f1eDUP14
0f1fPUSH20x3fee
0f22JUMP
0f23JUMPDEST
0f24PUSH40x3b490093
0f29PUSH10xe1
0f2bSHL
0f2cDUP3
0f2dMSTORE
0f2ePUSH10x01
0f30PUSH10x01
0f32PUSH10xa0
0f34SHL
0f35SUB
0f36AND
0f37PUSH10x04
0f39MSTORE
0f3aREVERT
0f3bJUMPDEST
0f3cPUSH10x20
0f3eSWAP1
0f3fPUSH20x0f46
0f42PUSH20x406b
0f45JUMP
0f46JUMPDEST
0f47DUP3
0f48DUP3
0f49DUP10
0f4aADD
0f4bADD
0f4cMSTORE
0f4dADD
0f4ePUSH20x0710
0f51JUMP
0f52JUMPDEST
0f53SWAP1
0f54SWAP2
0f55SWAP3
0f56SWAP4
0f57PUSH10x7f
0f59NOT
0f5aDUP7
0f5bDUP3
0f5cSUB
0f5dADD
0f5eDUP5
0f5fMSTORE
0f60DUP5
0f61CALLDATALOAD
0f62DUP4
0f63DUP2
0f64SLT
0f65ISZERO
0f66PUSH20x109b
0f69JUMPI
0f6aDUP16
0f6bADD
0f6cSWAP1
0f6dPUSH10x01
0f6fPUSH10x01
0f71PUSH10xa0
0f73SHL
0f74SUB
0f75PUSH20x0f7d
0f78DUP4
0f79PUSH20x3aac
0f7cJUMP
0f7dJUMPDEST
0f7eAND
0f7fDUP2
0f80MSTORE
0f81PUSH10x20
0f83DUP3
0f84ADD
0f85CALLDATALOAD
0f86PUSH10x20
0f88DUP3
0f89ADD
0f8aMSTORE
0f8bPUSH10x40
0f8dDUP3
0f8eADD
0f8fCALLDATALOAD
0f90PUSH10x40
0f92DUP3
0f93ADD
0f94MSTORE
0f95PUSH10x60
0f97DUP3
0f98ADD
0f99CALLDATALOAD
0f9aPUSH10x60
0f9cDUP3
0f9dADD
0f9eMSTORE
0f9fPUSH10x80
0fa1DUP3
0fa2ADD
0fa3CALLDATALOAD
0fa4PUSH10x80
0fa6DUP3
0fa7ADD
0fa8MSTORE
0fa9PUSH10xa0
0fabDUP3
0facADD
0fadCALLDATALOAD
0faePUSH10xa0
0fb0DUP3
0fb1ADD
0fb2MSTORE
0fb3PUSH10xc0
0fb5DUP3
0fb6ADD
0fb7CALLDATALOAD
0fb8PUSH10xc0
0fbaDUP3
0fbbADD
0fbcMSTORE
0fbdPUSH10xe0
0fbfDUP3
0fc0ADD
0fc1CALLDATALOAD
0fc2PUSH10xe0
0fc4DUP3
0fc5ADD
0fc6MSTORE
0fc7PUSH10x01
0fc9DUP1
0fcaPUSH10xa0
0fccSHL
0fcdSUB
0fcePUSH20x0fda
0fd1PUSH20x0100
0fd4DUP5
0fd5ADD
0fd6PUSH20x3aac
0fd9JUMP
0fdaJUMPDEST
0fdbAND
0fdcPUSH20x0100
0fdfDUP3
0fe0ADD
0fe1MSTORE
0fe2PUSH20x0120
0fe5DUP3
0fe6ADD
0fe7CALLDATALOAD
0fe8DUP1
0fe9ISZERO
0feaISZERO
0febDUP1
0fecSWAP2
0fedSUB
0feePUSH20x1097
0ff1JUMPI
0ff2PUSH10x01
0ff4SWAP3
0ff5DUP3
0ff6PUSH10x20
0ff8SWAP4
0ff9SWAP3
0ffaPUSH20x0120
0ffdDUP6
0ffeSWAP5
0fffADD
1000MSTORE
1001PUSH20x01c0
1004PUSH20xffff
1007PUSH20x1087
100aDUP3
100bPUSH20x106b
100ePUSH20x1032
1011PUSH20x101e
1014PUSH20x0140
1017DUP10
1018ADD
1019DUP10
101aPUSH20x4a62
101dJUMP
101eJUMPDEST
101fPUSH20x01e0
1022PUSH20x0140
1025DUP11
1026ADD
1027MSTORE
1028PUSH20x01e0
102bDUP10
102cADD
102dSWAP2
102ePUSH20x4a96
1031JUMP
1032JUMPDEST
1033PUSH10x01
1035PUSH10x01
1037PUSH10x40
1039SHL
103aSUB
103bPUSH20x1047
103ePUSH20x0160
1041DUP11
1042ADD
1043PUSH20x3be1
1046JUMP
1047JUMPDEST
1048AND
1049PUSH20x0160
104cDUP9
104dADD
104eMSTORE
104fPUSH20x105c
1052PUSH20x0180
1055DUP10
1056ADD
1057DUP10
1058PUSH20x49ef
105bJUMP
105cJUMPDEST
105dSWAP1
105eDUP9
105fDUP4
1060SUB
1061PUSH20x0180
1064DUP11
1065ADD
1066MSTORE
1067PUSH20x4a23
106aJUMP
106bJUMPDEST
106cSWAP6
106dDUP4
106ePUSH20x107a
1071PUSH20x01a0
1074DUP4
1075ADD
1076PUSH20x4e55
1079JUMP
107aJUMPDEST
107bAND
107cPUSH20x01a0
107fDUP8
1080ADD
1081MSTORE
1082ADD
1083PUSH20x4e55
1086JUMP
1087JUMPDEST
1088AND
1089SWAP2
108aADD
108bMSTORE
108cSWAP7
108dADD
108eSWAP5
108fADD
1090SWAP3
1091SWAP2
1092ADD
1093PUSH20x05f2
1096JUMP
1097JUMPDEST
1098DUP16
1099DUP1
109aREVERT
109bJUMPDEST
109cDUP15
109dDUP1
109eREVERT
109fJUMPDEST
10a0PUSH40x1d087e61
10a5PUSH10xe2
10a7SHL
10a8DUP7
10a9MSTORE
10aaPUSH10x04
10acDUP7
10adREVERT
10aeJUMPDEST
10afPOP
10b0CALLVALUE
10b1PUSH20x02cf
10b4JUMPI
10b5PUSH10x20
10b7CALLDATASIZE
10b8PUSH10x03
10baNOT
10bbADD
10bcSLT
10bdPUSH20x02cf
10c0JUMPI
10c1PUSH10x04
10c3CALLDATALOAD
10c4PUSH10xff
10c6DUP2
10c7AND
10c8DUP1
10c9SWAP2
10caSUB
10cbPUSH20x0385
10ceJUMPI
10cfPUSH10x40
10d1DUP3
10d2PUSH10x01
10d4PUSH10x01
10d6PUSH10x40
10d8SHL
10d9SUB
10daSWAP3
10dbPUSH10x20
10ddSWAP5
10deMSTORE
10dfPUSH10x07
10e1DUP5
10e2MSTORE
10e3KECCAK256
10e4SLOAD
10e5AND
10e6PUSH10x40
10e8MLOAD
10e9SWAP1
10eaDUP2
10ebMSTORE
10ecRETURN
10edJUMPDEST
10eePOP
10efCALLVALUE
10f0PUSH20x02cf
10f3JUMPI
10f4PUSH10xa0
10f6CALLDATASIZE
10f7PUSH10x03
10f9NOT
10faADD
10fbSLT
10fcPUSH20x02cf
10ffJUMPI
1100PUSH20x1107
1103PUSH20x3a80
1106JUMP
1107JUMPDEST
1108POP
1109PUSH20x1110
110cPUSH20x3a96
110fJUMP
1110JUMPDEST
1111POP
1112PUSH10x44
1114CALLDATALOAD
1115PUSH10x01
1117PUSH10x01
1119PUSH10x40
111bSHL
111cSUB
111dDUP2
111eGT
111fPUSH20x0385
1122JUMPI
1123PUSH20x1130
1126SWAP1
1127CALLDATASIZE
1128SWAP1
1129PUSH10x04
112bADD
112cPUSH20x3b29
112fJUMP
1130JUMPDEST
1131POP
1132POP
1133PUSH10x64
1135CALLDATALOAD
1136PUSH10x01
1138PUSH10x01
113aPUSH10x40
113cSHL
113dSUB
113eDUP2
113fGT
1140PUSH20x0385
1143JUMPI
1144PUSH20x1151
1147SWAP1
1148CALLDATASIZE
1149SWAP1
114aPUSH10x04
114cADD
114dPUSH20x3b29
1150JUMP
1151JUMPDEST
1152POP
1153POP
1154PUSH10x84
1156CALLDATALOAD
1157PUSH10x01
1159PUSH10x01
115bPUSH10x40
115dSHL
115eSUB
115fDUP2
1160GT
1161PUSH20x0385
1164JUMPI
1165PUSH20x1172
1168SWAP1
1169CALLDATASIZE
116aSWAP1
116bPUSH10x04
116dADD
116ePUSH20x3ac0
1171JUMP
1172JUMPDEST
1173POP
1174POP
1175PUSH10x40
1177MLOAD
1178PUSH40xbc197c81
117dPUSH10xe0
117fSHL
1180DUP2
1181MSTORE
1182PUSH10x20
1184SWAP1
1185RETURN
1186JUMPDEST
1187POP
1188CALLVALUE
1189PUSH20x02cf
118cJUMPI
118dPUSH10x20
118fCALLDATASIZE
1190PUSH10x03
1192NOT
1193ADD
1194SLT
1195PUSH20x02cf
1198JUMPI
1199PUSH10x20
119bSWAP1
119cPUSH20xffff
119fSWAP1
11a0PUSH10x08
11a2SWAP1
11a3PUSH10x40
11a5SWAP1
11a6PUSH10x01
11a8PUSH10x01
11aaPUSH10xa0
11acSHL
11adSUB
11aePUSH20x11b5
11b1PUSH20x3a80
11b4JUMP
11b5JUMPDEST
11b6AND
11b7DUP2
11b8MSTORE
11b9PUSH10x03
11bbDUP6
11bcMSTORE
11bdKECCAK256
11beADD
11bfSLOAD
11c0AND
11c1PUSH10x40
11c3MLOAD
11c4SWAP1
11c5DUP2
11c6MSTORE
11c7RETURN
11c8JUMPDEST
11c9POP
11caCALLVALUE
11cbPUSH20x02cf
11ceJUMPI
11cfPUSH10x40
11d1CALLDATASIZE
11d2PUSH10x03
11d4NOT
11d5ADD
11d6SLT
11d7PUSH20x02cf
11daJUMPI
11dbPUSH20x0879
11dePUSH20x11e5
11e1PUSH20x3a80
11e4JUMP
11e5JUMPDEST
11e6PUSH10x24
11e8CALLDATALOAD
11e9SWAP1
11eaPUSH20x4cb0
11edJUMP
11eeJUMPDEST
11efPOP
11f0CALLVALUE
11f1PUSH20x02cf
11f4JUMPI
11f5DUP1
11f6PUSH10x03
11f8NOT
11f9CALLDATASIZE
11faADD
11fbSLT
11fcPUSH20x02cf
11ffJUMPI
1200PUSH10x20
1202PUSH10x40
1204MLOAD
1205PUSH320x3154287b2470d9f05573ebd18908404f28e212134930a0f6df0005bc02e1c515
1226DUP2
1227MSTORE
1228RETURN
1229JUMPDEST
122aPOP
122bCALLVALUE
122cPUSH20x02cf
122fJUMPI
1230DUP1
1231PUSH10x03
1233NOT
1234CALLDATASIZE
1235ADD
1236SLT
1237PUSH20x02cf
123aJUMPI
123bPUSH10x20
123dPUSH10x01
123fPUSH10x01
1241PUSH10x40
1243SHL
1244SUB
1245PUSH10x0e
1247SLOAD
1248AND
1249PUSH10x40
124bMLOAD
124cSWAP1
124dDUP2
124eMSTORE
124fRETURN
1250JUMPDEST
1251POP
1252CALLVALUE
1253PUSH20x02cf
1256JUMPI
1257PUSH10xa0
1259CALLDATASIZE
125aPUSH10x03
125cNOT
125dADD
125eSLT
125fPUSH20x02cf
1262JUMPI
1263PUSH10x04
1265CALLDATALOAD
1266PUSH10x04
1268DUP2
1269LT
126aISZERO
126bPUSH20x0385
126eJUMPI
126fPUSH20x1276
1272PUSH20x3a96
1275JUMP
1276JUMPDEST
1277SWAP2
1278PUSH10x64
127aCALLDATALOAD
127bSWAP2
127cPUSH10x84
127eCALLDATALOAD
127fPUSH10x01
1281PUSH10x01
1283PUSH10xa0
1285SHL
1286SUB
1287DUP2
1288AND
1289SWAP3
128aPUSH10x44
128cCALLDATALOAD
128dSWAP3
128eSWAP2
128fDUP5
1290DUP2
1291SUB
1292PUSH20x0385
1295JUMPI
1296PUSH20x129d
1299PUSH20x5d3f
129cJUMP
129dJUMPDEST
129ePUSH10x40
12a0MLOAD
12a1PUSH40xf5778b03
12a6PUSH10xe0
12a8SHL
12a9DUP2
12aaMSTORE
12abPUSH10x20
12adDUP2
12aePUSH10x04
12b0DUP2
12b1PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
12d2PUSH10x01
12d4PUSH10x01
12d6PUSH10xa0
12d8SHL
12d9SUB
12daAND
12dbGAS
12dcSTATICCALL
12ddSWAP1
12deDUP2
12dfISZERO
12e0PUSH20x1563
12e3JUMPI
12e4DUP4
12e5SWAP2
12e6PUSH20x1534
12e9JUMPI
12eaJUMPDEST
12ebPOP
12ecDUP6
12edISZERO
12eeSWAP1
12efDUP2
12f0ISZERO
12f1PUSH20x1510
12f4JUMPI
12f5JUMPDEST
12f6POP
12f7PUSH20x14fc
12faJUMPI
12fbPUSH20x1305
12feDUP5
12ffDUP9
1300DUP6
1301PUSH20x42d6
1304JUMP
1305JUMPDEST
1306SWAP6
1307PUSH0
1308NOT
1309DUP2
130aSUB
130bPUSH20x14f7
130eJUMPI
130fPOP
1310DUP6
1311JUMPDEST
1312DUP1
1313SWAP7
1314DUP2
1315ISZERO
1316PUSH20x14e8
1319JUMPI
131aDUP1
131bDUP3
131cGT
131dPUSH20x14c3
1320JUMPI
1321POP
1322PUSH20x132a
1325DUP5
1326PUSH20x418b
1329JUMP
132aJUMPDEST
132bDUP4
132cPUSH20x1397
132fJUMPI
1330POP
1331POP
1332DUP1
1333DUP1
1334DUP1
1335DUP1
1336DUP9
1337DUP9
1338GAS
1339CALL
133aPUSH20x1341
133dPUSH20x4c81
1340JUMP
1341JUMPDEST
1342POP
1343ISZERO
1344PUSH20x1383
1347JUMPI
1348POP
1349PUSH0
134aMLOAD
134bPUSH10x20
134dPUSH20x6d51
1350PUSH0
1351CODECOPY
1352PUSH0
1353MLOAD
1354SWAP1
1355PUSH0
1356MSTORE
1357PUSH10x40
1359PUSH10x20
135bSWAP7
135cJUMPDEST
135dPUSH20x1365
1360DUP5
1361PUSH20x418b
1364JUMP
1365JUMPDEST
1366DUP2
1367MLOAD
1368SWAP5
1369DUP6
136aMSTORE
136bDUP8
136cDUP6
136dADD
136eDUP8
136fSWAP1
1370MSTORE
1371PUSH10x01
1373PUSH10x01
1375PUSH10xa0
1377SHL
1378SUB
1379AND
137aSWAP4
137bLOG4
137cPUSH10x40
137eMLOAD
137fSWAP1
1380DUP2
1381MSTORE
1382RETURN
1383JUMPDEST
1384PUSH40x65f4a9ef
1389PUSH10xe1
138bSHL
138cDUP2
138dMSTORE
138ePUSH10x04
1390DUP2
1391SWAP1
1392MSTORE
1393PUSH10x24
1395SWAP1
1396REVERT
1397JUMPDEST
1398PUSH10x20
139aSWAP8
139bSWAP3
139cPUSH20x13a4
139fDUP6
13a0PUSH20x418b
13a3JUMP
13a4JUMPDEST
13a5PUSH10x01
13a7DUP6
13a8SUB
13a9PUSH20x140c
13acJUMPI
13adPOP
13aePOP
13afPUSH10x40
13b1DUP1
13b2MLOAD
13b3PUSH40xa9059cbb
13b8PUSH10xe0
13baSHL
13bbDUP10
13bcDUP3
13bdADD
13beMSTORE
13bfPUSH10x01
13c1PUSH10x01
13c3PUSH10xa0
13c5SHL
13c6SUB
13c7SWAP1
13c8SWAP3
13c9AND
13caPUSH10x24
13ccDUP4
13cdADD
13ceMSTORE
13cfPUSH10x44
13d1DUP3
13d2ADD
13d3DUP8
13d4SWAP1
13d5MSTORE
13d6PUSH0
13d7MLOAD
13d8PUSH10x20
13daPUSH20x6d51
13ddPUSH0
13deCODECOPY
13dfPUSH0
13e0MLOAD
13e1SWAP1
13e2PUSH0
13e3MSTORE
13e4SWAP3
13e5SWAP1
13e6SWAP2
13e7PUSH20x1407
13eaSWAP1
13ebPUSH20x1401
13eeDUP2
13efPUSH10x64
13f1DUP2
13f2ADD
13f3JUMPDEST
13f4SUB
13f5PUSH10x1f
13f7NOT
13f8DUP2
13f9ADD
13faDUP4
13fbMSTORE
13fcDUP3
13fdPUSH20x3f7a
1400JUMP
1401JUMPDEST
1402DUP3
1403PUSH20x5edb
1406JUMP
1407JUMPDEST
1408PUSH20x135c
140bJUMP
140cJUMPDEST
140dSWAP3
140eSWAP7
140fSWAP3
1410SWAP1
1411SWAP2
1412POP
1413PUSH20x141b
1416DUP5
1417PUSH20x418b
141aJUMP
141bJUMPDEST
141cPUSH10x02
141eDUP5
141fSUB
1420PUSH20x146b
1423JUMPI
1424POP
1425POP
1426POP
1427PUSH0
1428MLOAD
1429PUSH10x20
142bPUSH20x6d51
142ePUSH0
142fCODECOPY
1430PUSH0
1431MLOAD
1432SWAP1
1433PUSH0
1434MSTORE
1435PUSH10x40
1437PUSH10x01
1439SWAP6
143aPUSH20x1407
143dDUP3
143eMLOAD
143fPUSH40x23b872dd
1444PUSH10xe0
1446SHL
1447DUP11
1448DUP3
1449ADD
144aMSTORE
144bADDRESS
144cPUSH10x24
144eDUP3
144fADD
1450MSTORE
1451DUP8
1452PUSH10x44
1454DUP3
1455ADD
1456MSTORE
1457DUP7
1458PUSH10x64
145aDUP3
145bADD
145cMSTORE
145dPUSH10x64
145fDUP2
1460MSTORE
1461PUSH20x1401
1464PUSH10x84
1466DUP3
1467PUSH20x3f7a
146aJUMP
146bJUMPDEST
146cSWAP6
146dPUSH20x1407
1470PUSH10x40
1472SWAP3
1473PUSH0
1474MLOAD
1475PUSH10x20
1477PUSH20x6d51
147aPUSH0
147bCODECOPY
147cPUSH0
147dMLOAD
147eSWAP1
147fPUSH0
1480MSTORE
1481SWAP5
1482SWAP9
1483DUP5
1484MLOAD
1485SWAP2
1486PUSH40x79212195
148bPUSH10xe1
148dSHL
148eDUP13
148fDUP5
1490ADD
1491MSTORE
1492ADDRESS
1493PUSH10x24
1495DUP5
1496ADD
1497MSTORE
1498DUP10
1499PUSH10x44
149bDUP5
149cADD
149dMSTORE
149eDUP9
149fPUSH10x64
14a1DUP5
14a2ADD
14a3MSTORE
14a4PUSH10x84
14a6DUP4
14a7ADD
14a8MSTORE
14a9PUSH10xa0
14abPUSH10xa4
14adDUP4
14aeADD
14afMSTORE
14b0PUSH10xc4
14b2DUP3
14b3ADD
14b4MSTORE
14b5PUSH10xc4
14b7DUP2
14b8MSTORE
14b9PUSH20x1401
14bcPUSH10xe4
14beDUP3
14bfPUSH20x3f7a
14c2JUMP
14c3JUMPDEST
14c4PUSH40x21909681
14c9PUSH10xe0
14cbSHL
14ccDUP5
14cdMSTORE
14cePUSH10x01
14d0PUSH10x01
14d2PUSH10xa0
14d4SHL
14d5SUB
14d6DUP10
14d7AND
14d8PUSH10x04
14daMSTORE
14dbPUSH10x24
14ddSWAP2
14deSWAP1
14dfSWAP2
14e0MSTORE
14e1PUSH10x44
14e3MSTORE
14e4PUSH10x64
14e6DUP3
14e7REVERT
14e8JUMPDEST
14e9PUSH40x7c2e506f
14eePUSH10xe1
14f0SHL
14f1DUP5
14f2MSTORE
14f3PUSH10x04
14f5DUP5
14f6REVERT
14f7JUMPDEST
14f8PUSH20x1311
14fbJUMP
14fcJUMPDEST
14fdPUSH40x15150d4d
1502PUSH10xe3
1504SHL
1505DUP3
1506MSTORE
1507PUSH10x04
1509DUP6
150aSWAP1
150bMSTORE
150cPUSH10x24
150eDUP3
150fREVERT
1510JUMPDEST
1511PUSH10x01
1513PUSH10x01
1515PUSH10xa0
1517SHL
1518SUB
1519AND
151aDUP7
151bEQ
151cISZERO
151dSWAP1
151ePOP
151fDUP1
1520PUSH20x152a
1523JUMPI
1524JUMPDEST
1525PUSH0
1526PUSH20x12f5
1529JUMP
152aJUMPDEST
152bPOP
152cCALLER
152dDUP6
152eEQ
152fISZERO
1530PUSH20x1524
1533JUMP
1534JUMPDEST
1535PUSH20x1556
1538SWAP2
1539POP
153aPUSH10x20
153cRETURNDATASIZE
153dPUSH10x20
153fGT
1540PUSH20x155c
1543JUMPI
1544JUMPDEST
1545PUSH20x154e
1548DUP2
1549DUP4
154aPUSH20x3f7a
154dJUMP
154eJUMPDEST
154fDUP2
1550ADD
1551SWAP1
1552PUSH20x434e
1555JUMP
1556JUMPDEST
1557PUSH0
1558PUSH20x12ea
155bJUMP
155cJUMPDEST
155dPOP
155eRETURNDATASIZE
155fPUSH20x1544
1562JUMP
1563JUMPDEST
1564PUSH10x40
1566MLOAD
1567RETURNDATASIZE
1568DUP6
1569DUP3
156aRETURNDATACOPY
156bRETURNDATASIZE
156cSWAP1
156dREVERT
156eJUMPDEST
156fPOP
1570CALLVALUE
1571PUSH20x02cf
1574JUMPI
1575PUSH10x60
1577CALLDATASIZE
1578PUSH10x03
157aNOT
157bADD
157cSLT
157dPUSH20x02cf
1580JUMPI
1581PUSH10x01
1583PUSH10x01
1585PUSH10x40
1587SHL
1588SUB
1589PUSH10x04
158bCALLDATALOAD
158cGT
158dPUSH20x02cf
1590JUMPI
1591PUSH20x0400
1594PUSH10x04
1596CALLDATALOAD
1597CALLDATASIZE
1598SUB
1599PUSH10x03
159bNOT
159cADD
159dSLT
159ePUSH20x02cf
15a1JUMPI
15a2PUSH20x15a9
15a5PUSH20x3bcb
15a8JUMP
15a9JUMPDEST
15aaPUSH10x44
15acCALLDATALOAD
15adPUSH10x01
15afPUSH10x01
15b1PUSH10x40
15b3SHL
15b4SUB
15b5DUP2
15b6GT
15b7PUSH20x08c5
15baJUMPI
15bbPUSH20x15c8
15beSWAP1
15bfCALLDATASIZE
15c0SWAP1
15c1PUSH10x04
15c3ADD
15c4PUSH20x3b29
15c7JUMP
15c8JUMPDEST
15c9SWAP2
15caPUSH10x0e
15ccSLOAD
15cdPUSH10xff
15cfDUP2
15d0PUSH10x40
15d2SHR
15d3AND
15d4PUSH20x1da0
15d7JUMPI
15d8PUSH10x01
15daSLOAD
15dbSWAP4
15dcDUP5
15ddISZERO
15dePUSH20x109f
15e1JUMPI
15e2PUSH10x40
15e4DUP1
15e5MLOAD
15e6PUSH10x01
15e8PUSH10x01
15eaPUSH10x40
15ecSHL
15edSUB
15eeDUP5
15efAND
15f0PUSH10x20
15f2DUP3
15f3ADD
15f4MSTORE
15f5DUP1
15f6DUP3
15f7ADD
15f8SWAP2
15f9SWAP1
15faSWAP2
15fbMSTORE
15fcSWAP3
15fdPUSH10x01
15ffPUSH10x01
1601PUSH10xa0
1603SHL
1604SUB
1605PUSH20x1611
1608PUSH10x04
160aDUP1
160bCALLDATALOAD
160cADD
160dPUSH20x3aac
1610JUMP
1611JUMPDEST
1612AND
1613PUSH10x60
1615DUP6
1616ADD
1617MSTORE
1618PUSH10x24
161aPUSH10x04
161cCALLDATALOAD
161dADD
161eSWAP6
161fPUSH20x01e0
1622DUP8
1623PUSH10x80
1625DUP8
1626ADD
1627CALLDATACOPY
1628PUSH20x0204
162bPUSH10x04
162dCALLDATALOAD
162eADD
162fSWAP6
1630PUSH20x1680
1633PUSH20x1658
1636PUSH20x1644
1639DUP10
163aPUSH10x04
163cCALLDATALOAD
163dPUSH10x04
163fADD
1640PUSH20x49ef
1643JUMP
1644JUMPDEST
1645PUSH20x0400
1648PUSH20x0260
164bDUP12
164cADD
164dMSTORE
164ePUSH20x0460
1651DUP11
1652ADD
1653SWAP2
1654PUSH20x4a23
1657JUMP
1658JUMPDEST
1659PUSH20x166d
165cPUSH20x0224
165fPUSH10x04
1661CALLDATALOAD
1662ADD
1663PUSH10x04
1665CALLDATALOAD
1666PUSH10x04
1668ADD
1669PUSH20x49ef
166cJUMP
166dJUMPDEST
166eDUP10
166fDUP4
1670SUB
1671PUSH10x5f
1673NOT
1674ADD
1675PUSH20x0280
1678DUP12
1679ADD
167aMSTORE
167bSWAP1
167cPUSH20x4a23
167fJUMP
1680JUMPDEST
1681SWAP4
1682PUSH10x04
1684CALLDATALOAD
1685PUSH20x0244
1688ADD
1689DUP11
168aPUSH20x02a0
168dDUP10
168eADD
168fJUMPDEST
1690PUSH10x0b
1692DUP3
1693LT
1694PUSH20x1d7a
1697JUMPI
1698POP
1699POP
169aPOP
169bPUSH20x16af
169ePUSH20x03a4
16a1PUSH10x04
16a3CALLDATALOAD
16a4ADD
16a5PUSH10x04
16a7CALLDATALOAD
16a8PUSH10x04
16aaADD
16abPUSH20x49ef
16aeJUMP
16afJUMPDEST
16b0DUP9
16b1DUP8
16b2SUB
16b3PUSH10x5f
16b5NOT
16b6ADD
16b7PUSH20x0400
16baDUP11
16bbADD
16bcMSTORE
16bdDUP1
16beDUP8
16bfMSTORE
16c0SWAP1
16c1SWAP6
16c2PUSH10x01
16c4PUSH10x01
16c6PUSH10xfb
16c8SHL
16c9SUB
16caDUP3
16cbGT
16ccPUSH20x1d76
16cfJUMPI
16d0PUSH20x17ea
16d3SWAP7
16d4PUSH20x1713
16d7SWAP3
16d8PUSH10x05
16daSHL
16dbDUP1
16dcSWAP2
16ddPUSH10x20
16dfDUP5
16e0ADD
16e1CALLDATACOPY
16e2PUSH10x20
16e4PUSH20x16f8
16e7PUSH20x03c4
16eaPUSH10x04
16ecCALLDATALOAD
16edADD
16eePUSH10x04
16f0CALLDATALOAD
16f1PUSH10x04
16f3ADD
16f4PUSH20x4a62
16f7JUMP
16f8JUMPDEST
16f9SWAP4
16faSWAP1
16fbSWAP3
16fcADD
16fdDUP12
16feDUP2
16ffSUB
1700DUP3
1701ADD
1702PUSH10x5f
1704NOT
1705ADD
1706PUSH20x0420
1709DUP14
170aADD
170bMSTORE
170cADD
170dSWAP2
170eSWAP1
170fPUSH20x4a96
1712JUMP
1713JUMPDEST
1714SWAP7
1715PUSH20x1736
1718DUP2
1719PUSH20x03e4
171cPUSH10x04
171eCALLDATALOAD
171fADD
1720CALLDATALOAD
1721SWAP10
1722DUP11
1723PUSH20x0440
1726DUP4
1727ADD
1728MSTORE
1729SUB
172aPUSH10x1f
172cNOT
172dDUP2
172eADD
172fDUP4
1730MSTORE
1731DUP3
1732PUSH20x3f7a
1735JUMP
1736JUMPDEST
1737PUSH10x20
1739DUP2
173aMLOAD
173bSWAP2
173cADD
173dKECCAK256
173ePUSH10x40
1740MLOAD
1741PUSH10x20
1743DUP2
1744ADD
1745SWAP2
1746PUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
1767DUP4
1768MSTORE
1769CHAINID
176aPUSH10x40
176cDUP4
176dADD
176eMSTORE
176fADDRESS
1770PUSH10x60
1772DUP4
1773ADD
1774MSTORE
1775PUSH320x8ff45d05bf7eaecf1e3489de0ad3d898e5ab54735cd0ca116506a6c8a7438c95
1796PUSH10x80
1798DUP4
1799ADD
179aMSTORE
179bPUSH10x01
179dPUSH10x01
179fPUSH10x40
17a1SHL
17a2SUB
17a3DUP8
17a4AND
17a5PUSH10xa0
17a7DUP4
17a8ADD
17a9MSTORE
17aaPUSH10xc0
17acDUP3
17adADD
17aeMSTORE
17afPUSH10xc0
17b1DUP2
17b2MSTORE
17b3PUSH20x17bd
17b6PUSH10xe0
17b8DUP3
17b9PUSH20x3f7a
17bcJUMP
17bdJUMPDEST
17beMLOAD
17bfSWAP1
17c0KECCAK256
17c1SWAP1
17c2DUP11
17c3SLOAD
17c4SWAP3
17c5PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
17e6PUSH20x5747
17e9JUMP
17eaJUMPDEST
17ebPOP
17ecPUSH10x01
17eePUSH10x01
17f0PUSH10x40
17f2SHL
17f3SUB
17f4PUSH20x17fe
17f7DUP2
17f8DUP4
17f9AND
17faPUSH20x4016
17fdJUMP
17feJUMPDEST
17ffAND
1800SWAP1
1801PUSH10x01
1803PUSH10x01
1805PUSH10x40
1807SHL
1808SUB
1809NOT
180aAND
180bOR
180cPUSH10x0e
180eSSTORE
180fPUSH20x181c
1812PUSH10x04
1814CALLDATALOAD
1815PUSH10x04
1817ADD
1818PUSH20x3fee
181bJUMP
181cJUMPDEST
181dPUSH10x01
181fPUSH10x01
1821PUSH10xa0
1823SHL
1824SUB
1825DUP2
1826AND
1827SWAP3
1828SWAP1
1829SWAP2
182aSWAP1
182bDUP4
182cISZERO
182dPUSH20x1d15
1830JUMPI
1831DUP4
1832DUP7
1833MSTORE
1834PUSH10x03
1836PUSH10x20
1838MSTORE
1839PUSH10x40
183bDUP7
183cKECCAK256
183dSWAP5
183ePUSH10xff
1840DUP7
1841SLOAD
1842AND
1843PUSH20x1d62
1846JUMPI
1847DUP7
1848JUMPDEST
1849PUSH10x0f
184bDUP2
184cLT
184dPUSH20x1d4a
1850JUMPI
1851POP
1852POP
1853PUSH10xff
1855DUP6
1856SLOAD
1857AND
1858ISZERO
1859DUP1
185aISZERO
185bPUSH20x1d3e
185eJUMPI
185fJUMPDEST
1860DUP1
1861ISZERO
1862PUSH20x1d29
1865JUMPI
1866JUMPDEST
1867PUSH20x1d15
186aJUMPI
186bPUSH20x1879
186eSWAP1
186fPUSH10x04
1871CALLDATALOAD
1872PUSH10x04
1874ADD
1875PUSH20x4acf
1878JUMP
1879JUMPDEST
187aSWAP1
187bDUP5
187cDUP8
187dMSTORE
187ePUSH10x04
1880PUSH10x20
1882MSTORE
1883PUSH10x40
1885DUP8
1886KECCAK256
1887SWAP1
1888PUSH10x01
188aPUSH10x01
188cPUSH10x40
188eSHL
188fSUB
1890DUP4
1891GT
1892PUSH20x1ce6
1895JUMPI
1896PUSH20x189f
1899DUP4
189aDUP4
189bPUSH20x4b34
189eJUMP
189fJUMPDEST
18a0SWAP1
18a1DUP8
18a2MSTORE
18a3PUSH10x20
18a5DUP8
18a6KECCAK256
18a7DUP8
18a8JUMPDEST
18a9DUP4
18aaDUP2
18abLT
18acPUSH20x1cfa
18afJUMPI
18b0POP
18b1POP
18b2POP
18b3POP
18b4PUSH20x18c8
18b7PUSH20x0224
18baPUSH10x04
18bcCALLDATALOAD
18bdADD
18bePUSH10x04
18c0CALLDATALOAD
18c1PUSH10x04
18c3ADD
18c4PUSH20x4acf
18c7JUMP
18c8JUMPDEST
18c9SWAP1
18caDUP5
18cbDUP8
18ccMSTORE
18cdPUSH10x05
18cfPUSH10x20
18d1MSTORE
18d2PUSH10x40
18d4DUP8
18d5KECCAK256
18d6SWAP1
18d7PUSH10x01
18d9PUSH10x01
18dbPUSH10x40
18ddSHL
18deSUB
18dfDUP4
18e0GT
18e1PUSH20x1ce6
18e4JUMPI
18e5PUSH20x18ee
18e8DUP4
18e9DUP4
18eaPUSH20x4b34
18edJUMP
18eeJUMPDEST
18efSWAP1
18f0DUP8
18f1MSTORE
18f2PUSH10x20
18f4DUP8
18f5KECCAK256
18f6DUP8
18f7JUMPDEST
18f8DUP4
18f9DUP2
18faLT
18fbPUSH20x1ccb
18feJUMPI
18ffPOP
1900POP
1901POP
1902POP
1903DUP5
1904JUMPDEST
1905PUSH10xff
1907DUP2
1908AND
1909PUSH10x0b
190bDUP2
190cLT
190dISZERO
190ePUSH20x1966
1911JUMPI
1912PUSH10xff
1914SWAP2
1915DUP2
1916PUSH20x192f
1919PUSH20x192a
191cPUSH10x01
191eSWAP5
191fPUSH20x0244
1922PUSH10x04
1924CALLDATALOAD
1925ADD
1926PUSH20x4b78
1929JUMP
192aJUMPDEST
192bPUSH20x4002
192eJUMP
192fJUMPDEST
1930SWAP1
1931DUP8
1932DUP11
1933MSTORE
1934PUSH10x06
1936PUSH10x20
1938MSTORE
1939PUSH10x40
193bDUP11
193cKECCAK256
193dSWAP1
193ePUSH0
193fMSTORE
1940PUSH10x20
1942MSTORE
1943PUSH10x01
1945PUSH10x01
1947PUSH10x40
1949SHL
194aSUB
194bPUSH10x40
194dPUSH0
194eKECCAK256
194fSWAP2