Final Testnetexplorer K_J · Final Testnet · 48359
en

Contract

0x6213244deb6b27c2106e3e15253c2ac3041ab9d1

Address
0x6213244deb6b27c2106e3e15253c2ac3041ab9d1
Kind
verified contract FinalAccountLedger
Balance
0 vETH
Nonce
1
Code
25,951 bytes codehash 0x40073f0d94ce80fe211eeb64609c7f60307183585f2ed7c3d0090d2631d97ca3

account tree

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

source verified

Contract
FinalAccountLedger exact match · immutables masked
Compiler
v0.8.33+commit.64118f21
Optimizer
enabled · 200 runs
EVM version
prague
Verified
2026-09-06T09:34:56.993Z
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
//
// @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";

/**
 * @title FinalAccountLedger
 * @notice Who owns a Final Wallet, and what its keys are. On Final Chain.
 *
 * ## What this replaces
 *
 * A Redis list. `FinalBackend/src/accounts/journal.js` held every account's
 * state as a journal one process wrote under a fencing lease, and everything
 * else read a replica of. That journal was self-authenticating — it stored the
 * REQUEST and its signatures rather than the actor it resolved to — which was
 * the best answer available while the verification had to happen in
 * JavaScript. It bounded a compromised PUBLISHER and it did not bound a
 * compromised WRITER, and the design said so in as many words.
 *
 * Here the chain verifies. A request carries the holder's own credential, this
 * contract checks it in the SLH-DSA-SHAKE-256s precompile against the
 * commitment it already holds, and applies the transition itself. So:
 *
 *   - **`submitRequest` is permissionless.** Whoever pays gas is irrelevant;
 *     the signature decides. There is no writer to compromise, which is the
 *     entire point of the move.
 *   - **The record is public state**, not a replica of a cache. A guardian, a
 *     reconciler and a publisher read the same storage rather than three copies
 *     that agree by convention.
 *   - **Tree 1 is written from here**, so the root every other chain projects
 *     from is a consequence of the transition rather than a separate
 *     publication that could describe a different history.
 *
 * ## The actor is derived, never claimed
 *
 * A request does not say "I am the recovery key". It presents a credential, and
 * which stored commitment that credential matches is what names the actor. So
 * there is no path where a caller selects its own authority, and the strongest
 * thing a live key can assert is `LIVE_KEY`.
 *
 * Only the ACCESS slots authorize here. The wallet holds four PQ keys in two
 * pairs; each pair has an access key (SLH-DSA-SHAKE-256s) and a transaction key
 * (ML-DSA-87). Account-plane changes take the ACCESS class and the transaction
 * class is refused outright — splitting the classes buys nothing if a key that
 * signs spends can also rotate the credential set.
 *
 * ## Genesis is attested, everything after it is proven
 *
 * `openAccount` takes a K-of-N ML-DSA quorum of `ROLE_ACCOUNT_COSIGNER`.
 * Issuance happens at a different trust boundary — a certificate this chain
 * cannot see — so the fleet attests that an account was issued with these four
 * commitments. That is the one place a quorum of ours stands in for evidence,
 * and it is bounded: after genesis, no set of our keys can move an account.
 *
 * ## What is deliberately NOT here
 *
 * **No admin path.** No owner, no pause, no setter that rewrites an account.
 * A ledger whose operator can rewrite an owner is not a record of ownership.
 *
 * **No delegation to the identity registry.** Service wallets are projected
 * into tree 1 by `FinalStateTrees.syncIdentities` and are not accounts here:
 * they have no holder to sign for them, and giving them one would make the
 * fleet's own credentials movable by the fleet.
 */
contract FinalAccountLedger {
    // ------------------------------------------------------------ constants

    /// @notice EIP-712 domain salt. Distinct from `FinalRecoveryModule`'s
    /// `GuardianFreeze_v01` — that signature freezes ONE chain's copy as a
    /// liveness backstop, this one freezes the account globally, and a guardian
    /// asked for the local one must not thereby have authorized the global one.
    bytes32 public constant DOMAIN_ACCOUNT_STATE_REQUEST =
        keccak256("FINAL_ACCOUNT_STATE_REQUEST_v01");

    /// @dev `EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)`.
    /// No `verifyingContract`: the backend built this domain before any contract
    /// existed to name, and changing it now would invalidate every credential a
    /// holder has already produced.
    bytes32 private constant EIP712_DOMAIN_TYPEHASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)");
    bytes32 private constant DOMAIN_NAME = keccak256("FinalChainAccountState");
    bytes32 private constant DOMAIN_VERSION = keccak256("01");

    bytes32 private constant REQUEST_TYPEHASH = keccak256(
        "AccountStateRequest(address wallet,string action,bytes32 payloadHash,uint64 nonce,uint64 expiresAt)"
    );

    /// @dev What a co-signer's approval authorizes at genesis. Per-action, so an
    /// approval to open cannot be replayed as one for any other quorum here.
    bytes32 private constant ACTION_OPEN = keccak256("FinalAccountLedger.open.v01");
    /// @dev Registrar-quorum action, verified by the registry with this ledger
    /// as the verifying contract.
    bytes32 public constant ACTION_CONFIGURE = keccak256("FINAL_ACCOUNT_LEDGER_CONFIGURE_v01");
    /// @dev Tree 8 and its admission branch — `FinalStateTrees.TREE_IDENTITY` /
    ///      `BRANCH_MAIN`, pinned by test. Constants rather than two external
    ///      reads per batch.
    uint8 internal constant TREE_IDENTITY_ID = 8;
    uint8 internal constant BRANCH_MAIN_ID = 1;
    /// @dev Tree 8, branch 2 — the owner → wallets index (`FinalStateTrees.BRANCH_OWNER_INDEX`).
    uint8 internal constant BRANCH_OWNER_INDEX_ID = 2;
    /// @dev `FinalStateTrees.ownerIndexKeyFor` / `ownerIndexLeafHash`, restated
    ///      so the write costs two hashes and not two external calls — pinned
    ///      byte-for-byte against the trees by test.
    bytes32 private constant DOMAIN_OWNER_INDEX_KEY = keccak256("FinalStateTrees.key.ownerIndex.v01");
    bytes32 private constant DOMAIN_OWNER_INDEX_LEAF = keccak256("FINAL_OWNER_INDEX_LEAF_v01");
    /// @dev The restore lane's quorum action (the opener quorum, a separate nonce).
    bytes32 private constant ACTION_RESTORE = keccak256("FinalAccountLedger.restore.v01");
    /// @dev Closes the restore lane for good — the configuration authority's action.
    bytes32 public constant ACTION_SEAL_RESTORE = keccak256("FINAL_ACCOUNT_LEDGER_SEAL_RESTORE_v01");

    /// @notice Delay bounds. Zero removes the cancel window entirely, which
    /// deletes the only defence against a compromised recovery key; unbounded
    /// lets a hostile guardian config strand a legitimate rotation forever.
    /// @dev MILLISECONDS. `block.timestamp` on this chain is milliseconds, so a
    ///      second-denominated delay compared against it is 1000x short — the
    ///      24-hour default elapsed in 86 seconds. Written as `hours *
    ///      MS_PER_SECOND` so the intent stays readable and the unit explicit.
    uint64 public constant MIN_DELAY_MS = 1 hours * FinalChainTime.MS_PER_SECOND;
    uint64 public constant MAX_DELAY_MS = 30 days * FinalChainTime.MS_PER_SECOND;
    uint64 public constant DEFAULT_DELAY_MS = 24 hours * FinalChainTime.MS_PER_SECOND;

    /// @notice How many times guardians may cancel one rotation before it
    /// proceeds anyway. **Guardians delay a rotation; they do not veto it.** An
    /// unbounded cancel makes a captured guardian set a permanent lockout —
    /// strictly worse than having no guardians, because the user configured it
    /// believing it helped.
    uint8 public constant MAX_ROTATION_CANCELS = 2;

    /// @notice The longest a request may stay valid.
    /// @dev A nonce alone does not bound an unused authorization: a guardian's
    /// freeze signature at nonce 5 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.
    uint64 public constant MAX_REQUEST_TTL_MS = 7 days * FinalChainTime.MS_PER_SECOND;

    /// @notice The PQ algorithm id the ACCESS class uses: 5, FIPS 205.
    /// @dev Matches `FinalPqQuorum.ALG_SLH_DSA_SHAKE_256S` and the backend
    /// registry. The transaction class (4, FIPS 204) is refused here on purpose.
    uint8 public constant ALG_SLH_DSA_SHAKE_256S = 5;

    /// @notice The owner every PQ account carries, on this chain and on every
    /// execution chain.
    /// @dev Must equal `FinalWalletShared.FINAL_PQ_NATIVE_OWNER`. Declared here
    /// rather than imported for the same reason `FinalStateTrees` declares its
    /// own `DOMAIN_ACCOUNT_STATE_LEAF`: these contracts deploy only to 20678 /
    /// 48359 and pulling in a wallet-side compilation unit would couple two
    /// deploy targets that share nothing else. Pinned by a parity test — a
    /// mismatch is an owner field no execution chain agrees with, and nothing
    /// would point at the cause.
    ///
    /// Unspendable by construction: recovering a signature to a chosen 20-byte
    /// value is a ~2^160 search, and the address holds no code, so the ERC-1271
    /// branch is unreachable too.
    address public constant FINAL_PQ_NATIVE_OWNER = 0x00000000000000000000000000000046494e414c;

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

    /// @notice The ten transitions, in the order the backend enumerates them.
    /// @dev The ORDER is load-bearing twice over: it indexes the per-action
    /// nonce, and `_actionName` maps it to the exact string the EIP-712 digest
    /// hashes. Inserting one in the middle renumbers every stored nonce.
    enum Action {
        FREEZE,
        UNFREEZE,
        INITIATE_ROTATION,
        CANCEL_ROTATION,
        FINALIZE_ROTATION,
        INITIATE_GUARDIAN_CHANGE,
        CANCEL_GUARDIAN_CHANGE,
        FINALIZE_GUARDIAN_CHANGE,
        TRANSFER_OWNER,
        /// @dev Appended, and appending is the only safe direction: the ordinal
        /// indexes `nonceOf` and is committed to by every request digest, so
        /// inserting one renumbers actions users have already signed for.
        ENABLE_PQ,
        /// @dev Set or replace the account's row for one chain in the tree-1
        /// `deployedChains` table — `(chainRef, account)`, the account in that
        /// chain's own account space. Holder-set, because only the holder knows
        /// what it is on a chain whose accounts are not EVM addresses; this is
        /// what a zero settlement beneficiary resolves through.
        SET_CHAIN_ACCOUNT
    }

    /// @notice How many actions there are. Sizes `noncesOf`.
    uint8 public constant ACTION_COUNT = 11;

    /// @notice Who a verified credential establishes.
    enum Actor { NONE, RECOVERY_KEY, LIVE_KEY, GUARDIANS }

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

    /// @notice One authorization request.
    /// @param payload The action's arguments, ABI-encoded exactly as the digest
    ///        commits to them. Empty for the actions that take none.
    struct Request {
        address wallet;
        Action action;
        uint64 nonce;
        uint64 expiresAt;
        bytes payload;
    }

    /// @notice The holder's credential. Exactly one field is populated.
    /// @param pqBlob `abi.encode(uint8 algorithmId, bytes publicKey, bytes signature)`
    ///        — the same encoding `FinalBackend/src/pq/credential.js` produces.
    /// @param ownerSignature The pre-PQ path: an ECDSA signature from `owner`.
    struct Credential {
        bytes pqBlob;
        bytes ownerSignature;
    }

    /// @notice One guardian's authorization.
    /// @dev Three forms, and which one applies is decided from ledger state
    /// alone. A guardian of a PQ wallet is required to be a PQ Final Wallet, so
    /// most guardians are CONTRACTS and cannot ECDSA-recover to their own
    /// address — on an execution chain that is `SignatureChecker` falling
    /// through to ERC-1271, and here it is a lookup, because a guardian that is
    /// a Final Wallet is itself an account in this ledger.
    ///
    ///   1. `guardian == 0`      — an EOA guardian. Recover, and the recovered
    ///                             address IS the guardian.
    ///   2. `guardian`, `pqBlob` — a PQ Final Wallet, signing with its own LIVE
    ///                             access key.
    ///   3. `guardian`, `signature` — a pre-PQ Final Wallet, whose owner signs.
    struct GuardianAuth {
        address guardian;
        bytes pqBlob;
        bytes signature;
    }

    /// @notice Everything about one account except its guardian lists.
    struct Account {
        bool opened;
        bytes32 liveAccess;
        bytes32 liveTransaction;
        bytes32 recoveryAccess;
        bytes32 recoveryTransaction;
        /// @dev The genesis certificate serial — the raw half of the tree-8
        /// admission leaf's preimage. Kept so this record alone can re-derive
        /// admission on a future plane (a redeployed ledger re-opens accounts
        /// from snapshots of this record, and the derived hash in tree 8 is
        /// not invertible). Never mutated: the certificate is the address.
        bytes32 serial;
        /// @dev Encapsulation commitments, one word per stage. `recoveryKem` is
        /// the pre-committed successor `liveKem` rotates into — present for the
        /// same reason `recoveryTransaction` is, so a rotation needs no key
        /// ARGUMENT and cannot be handed a key nobody vouched for.
        bytes32 liveKem;
        bytes32 recoveryKem;
        /// @dev Which generation of `liveKem` this is. 1 at open, +1 on every
        /// promotion.
        ///
        /// **This is what an intent header's `kemKeyVersion` names**, and it is
        /// a KEM-rotation counter rather than the account `version` below. The
        /// header field is a `uint16` and `version` moves on every transition —
        /// a freeze, a guardian change, a dormancy refresh — so pinning the
        /// header to it would expire an envelope for reasons that have nothing
        /// to do with the key it was sealed to.
        ///
        /// It exists because a forced-path intent sits encrypted for up to 48 h
        /// 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 the recipient say *which* key this 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`: this
        /// increments 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 and read only the bit. The chains the
        /// account exists on — and as what — are the `(chainRef, account)` table
        /// in `_chainAccounts`, which used to be a bitmask beside this one.
        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 — it
        /// manufactures dormancy — and this rule puts that out of reach, since
        /// the holder can always stamp it here directly. Overstating merely
        /// delays a legitimate recovery, which fails safe.
        uint64 lastActivityAt;
        address owner;
        bool pqEnabled;
        bool frozen;
        uint64 version;
        uint64 delayMs;
        uint16 threshold;
        uint16 cancelThreshold;
        bool rotationPending;
        uint8 rotationCancels;
        uint64 rotationInitiatedAt;
        bytes32 pendingRecoveryAccess;
        bytes32 pendingRecoveryTransaction;
        /// @dev Staged 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;
        bool guardianChangePending;
        uint16 pendingThreshold;
        uint16 pendingCancelThreshold;
        uint64 guardianChangeInitiatedAt;
    }

    /// @notice What `openAccount` installs. Genesis, and the only shape a
    /// quorum of ours may ever write.
    struct Genesis {
        address wallet;
        bytes32 liveAccess;
        bytes32 liveTransaction;
        bytes32 recoveryAccess;
        bytes32 recoveryTransaction;
        /// @dev Both encapsulation commitments, per stage. An account opened
        /// without them could receive nothing and would have no successor to
        /// rotate into, so they are required here rather than settable later.
        bytes32 liveKem;
        bytes32 recoveryKem;
        /// @dev The certificate serial (`16 B entropy ‖ 16 B counter`). With
        /// the six commitments above it completes the identity-leaf preimage —
        /// `keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)`, the exact
        /// `certHash` inside the wallet's CREATE2 derivation — which this
        /// ledger writes into the IDENTITY tree (tree 8) at open. Without it
        /// the account exists on Final Chain but no execution chain would ever
        /// admit its creation.
        bytes32 serial;
        address owner;
        bool pqEnabled;
        /// @dev The chains this account exists on at genesis, and its account on
        /// each (`AccountStateLeaf.deployedChains`). Granted here because an
        /// account with no row could be created nowhere; the holder extends the
        /// table afterwards with `SET_CHAIN_ACCOUNT`. Empty is refused for the
        /// same reason a zero KEM version is — it means the opener never
        /// decided, not that it decided "none".
        FinalStateTrees.ChainAccount[] deployedChains;
        uint64 delayMs;
        address[] guardians;
        uint16 threshold;
        uint16 cancelThreshold;
    }

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

    /// @notice Where every co-signer, key and role is resolved. Immutable, so
    /// the genesis quorum can never be pointed at a registry from calldata.
    FinalIdentityRegistry public immutable registry;
    /// @notice Tree 1. Written on every accepted transition.
    FinalStateTrees public immutable trees;

    /// @notice The role that may attest a genesis.
    uint256 public openerRole;
    /// @notice How many attestations one genesis needs. Zero refuses every open.
    uint256 public openThreshold;
    /// @notice Bound into every genesis digest. One per `openAccount` call.
    uint64 public openNonce;

    mapping(address => Account) private _accounts;
    mapping(address => address[]) private _guardians;
    mapping(address => address[]) private _pendingGuardians;
    /// @notice Next authorization nonce, per account and per action.
    /// @dev Per ACTION, not per account. The record `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 — and `TRANSFER_OWNER` sits with the LIVE key, so a thief
    /// holding it could race transfers to keep 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, in seconds, by registry slot.
    /// @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. Zero means the chain does not accrue dormancy.
    /// @dev `uint64`, and the widening is required rather than tidy: the
    ///      two-year default is 63,072,000,000 ms, which does not fit `uint32`
    ///      at all. In seconds it did, which is why it was one.
    mapping(uint8 => uint64) public inactivityThresholdOf;

    /// @notice Chains an account is known to exist on, as OBSERVED by a relayer
    /// (`WalletCreated` seen on that chain). Off-leaf, deliberately: this is the
    /// fan-out's target set, not an authorization, and putting it in the leaf
    /// would bump `version` on every new deployment.
    mapping(address => bytes32[]) private _deployments;
    mapping(address => mapping(bytes32 => bool)) private _hasDeployment;

    /// @notice The holder's `(chainRef, account)` table — the leaf's
    /// `deployedChains`. Granted at genesis, extended by `SET_CHAIN_ACCOUNT`.
    /// Rows are replaced, never removed: removing one strands assets at an
    /// account that can no longer be resolved.
    mapping(address => FinalStateTrees.ChainAccount[]) private _chainAccounts;

    /// @notice Every opened account, in the order it was opened.
    address[] private _wallets;
    /// @dev The owner → wallets index behind tree 8's branch 2: every account
    ///      an owner holds, in the order they were opened or transferred in.
    ///      The tree commits to this array (`FinalStateTrees.ownerIndexLeafHash`);
    ///      `walletsByOwner` is the readable half a reader asks first.
    mapping(address owner => address[]) private _walletsByOwner;
    /// @dev Position + 1 of a wallet in its owner's array; 0 = not indexed.
    mapping(address wallet => uint256) private _ownerSlotPlusOne;
    /// @notice The restore lane's replay counter (`restoreAccounts`).
    uint64 public restoreNonce;
    /// @notice True once `sealRestore` has run: the lane that re-creates
    ///         registrations after a redeploy is closed for the life of this
    ///         ledger, and every account from then on enters through `openAccount`.
    bool public restoreSealed;

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

    event AccountOpened(address indexed wallet, address indexed owner, bool pqEnabled);
    event AccountRestored(address indexed wallet, address indexed owner, uint64 version);
    event RestoreSealed();
    event RequestApplied(address indexed wallet, Action indexed action, Actor actor, uint64 version);
    event DeploymentObserved(address indexed wallet, bytes32 indexed chainRef);
    /// @notice A row of the account's `deployedChains` table was set or replaced.
    event ChainAccountSet(address indexed wallet, bytes32 indexed chainRef, bytes32 account);
    event LedgerConfigured(uint256 openerRole, uint256 openThreshold);

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

    error NotAuthorized(address caller);
    error UnknownAccount(address wallet);

    event ActivityRecorded(address indexed wallet, uint64 at);
    event DormancyRefreshed(address indexed wallet, uint32 dormantChains, uint64 version);
    error AccountAlreadyOpen(address wallet);
    /// @notice `restoreAccounts` after `sealRestore`.
    error RestoreIsSealed();
    /// @notice A restored record that is not an opened account, or names the zero wallet.
    error InvalidRestore(address wallet);

    /// @notice Genesis carried no chain row — the account could exist on no
    /// chain at all, which is an opener that never decided, not a decision.
    error NoChainGranted(address wallet);
    /// @notice A genesis with no certificate serial. The admission leaf needs
    /// it, and zero is the shape of an opener that never resolved it.
    error ZeroSerial(address wallet);
    /// @notice A `deployedChains` row names the zero chain or the zero account.
    error InvalidChainAccount(bytes32 chainRef, bytes32 account);
    error LedgerNotConfigured();
    error ThresholdUnreachable(uint256 live, uint256 required);
    error NonceMismatch(uint64 expected, uint64 supplied);
    error RequestExpired(uint64 expiresAt, uint256 nowSeconds);
    error ExpiryTooFar(uint64 span, uint64 cap);
    error NoCredential();
    error CredentialNotPermitted(string why);
    error AmbiguousCredential();
    error KeyCommitmentMismatch();
    error SignatureInvalid();
    error WrongAlgorithmForSlot(uint8 supplied);
    error MalformedBlob();
    error NotAGuardian(address who);
    error DuplicateGuardian(address who);
    error InvalidTransition(string why);
    error InvalidDelay(uint64 delayMs);
    error InvalidGuardianSet(string why);
    error DelayNotElapsed(uint64 readyAt, uint256 nowSeconds);

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

    /**
     * @dev The precompile probe is the point of having a constructor at all: a
     * ledger deployed where SLH-DSA 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.
     */
    constructor(FinalIdentityRegistry registry_, FinalStateTrees trees_) {
        FinalChainPrecompiles.assertAvailable();
        registry = registry_;
        trees = trees_;
    }

    /**
     * @notice Set which role may attest a genesis, and how many attestations.
     * @dev The registry's bootstrap admin alone while its window is open, the
     * sealed `ROLE_REGISTRAR` quorum afterwards — the same window and quorum
     * the registry and the trees use. `approvals` is empty during bootstrap.
     * Re-callable, because a co-signer set that grows or shrinks has to be able
     * to move its threshold.
     */
    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 accounts at genesis, under a PQ quorum.
     *
     * @dev The genesis guardian set lands immediately, and that is the one
     * exception to the delay rule: there is no outgoing set to cancel it, and a
     * delay here would protect nobody while leaving a fresh account with no
     * guardians for its first day — which is when it is least able to defend
     * itself.
     */
    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, same-tx: without its tree-8 leaf the account
        // exists here and is creatable nowhere. Write-once by construction —
        // the certificate IS the address, so no later mutation moves it.
        trees.setLeavesAsWriter(TREE_IDENTITY_ID, BRANCH_MAIN_ID, identityKeys, identityLeaves);
        // The owner index, same-tx: one leaf per owner touched by the batch.
        address[] memory owners = new address[](batch.length);
        for (uint256 i = 0; i < batch.length; i++) owners[i] = batch[i].owner;
        _writeOwnerIndex(owners);
    }

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

    /// @notice Storage words one `Account` occupies (28 packed fields → 15
    ///         slots). Pinned by test: a field added to the struct that does
    ///         not fit the last slot is a constant bump here or a truncated
    ///         restore.
    uint256 public constant ACCOUNT_WORDS = 15;

    /**
     * @notice One account as the redeploy tooling carries it: the record's
     *         storage words verbatim (`accountWords`), the lists beside it, and
     *         its tree-8 admission leaf — what `exportLedgerAccounts.cjs` writes
     *         before a redeploy and `restoreAccounts` replays after it.
     * @dev Words rather than a decoded struct, deliberately: a calldata
     *      `Account` copied field by field is ~3 KB of bytecode this ledger does
     *      not have under EIP-170, and the words ARE the record — same source,
     *      same layout, byte-exact. `identityLeaf` is carried verbatim rather
     *      than re-derived: the admission leaf commits to the GENESIS
     *      certificate keys, and a rotated account's live keys are not those.
     *      `nonces` are carried because the request digest binds the chain and
     *      not this contract's address — a reset would replay every request the
     *      holder ever signed.
     */
    struct Restored {
        address wallet;
        bytes32[ACCOUNT_WORDS] words;
        address[] guardians;
        address[] pendingGuardians;
        uint64[ACTION_COUNT] nonces;
        bytes32[] deployments;
        FinalStateTrees.ChainAccount[] chainAccounts;
        bytes32 identityLeaf;
    }

    /// @notice The raw storage words of `wallet`'s record — the export's input.
    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 exported from a previous ledger, verbatim.
     *
     * @dev Ruled 2026-09-03/04: every account registration is saved before a
     * redeploy of the state plane and restored after it, and the redeploy is
     * NO-WIPE — same chain, same identities, new ledger. `openAccount` cannot
     * do this: it recreates a GENESIS (version 1, no rotation, no guardian
     * change, zero nonces), so a rotated account restored through it would
     * honour keys its holder already retired. This writes the record as it
     * was — keys, versions, nonces, pending changes, chain accounts,
     * deployments, dormancy — and the tree leaves the plane derives from it:
     * tree 1 from the record (`_leafOf`), tree 8 branch 1 verbatim, tree 8
     * branch 2 (the owner index) rebuilt.
     *
     * Same authority as an open (the opener quorum, its own nonce — one round
     * per record, so a refused record names itself), only for wallets this
     * ledger does not know, and only until `sealRestore`: the
     * lane exists for the restore step of a redeploy and for nothing after.
     * The restore tool verifies every record against the export before the
     * fleet is pointed here; the trees' roots are new by construction (the
     * shape may change across a redeploy) and are anchored at a higher epoch.
     */
    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. The configuration authority's
    ///         call, run once the restored roots have been verified.
    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 `owner` holds, in index order — the readable half
    ///         of tree 8's branch 2, whose leaf commits to exactly this array.
    function walletsByOwner(address owner) external view returns (address[] memory) {
        return _walletsByOwner[owner];
    }

    /// @dev Add `wallet` to `owner`'s array (position remembered for removal).
    function _indexOwner(address wallet, address owner) private {
        address[] storage list = _walletsByOwner[owner];
        list.push(wallet);
        _ownerSlotPlusOne[wallet] = list.length;
    }

    /// @dev Remove `wallet` from `owner`'s array: swap the last in, pop. Order
    ///      within an owner's array is not a promise — the leaf is recomputed
    ///      over the array as it stands.
    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;
    }

    /// @dev Write the branch-2 leaf of every owner in `owners`. A repeated
    ///      owner is the same leaf written twice — cheaper than a dedupe here.
    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);
    }

    /// @dev The identity leaf the execution chains' gateways verify at
    /// creation: `keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)` with
    /// `keysHash` the issuer's six-commitment fold — byte-identical to
    /// `IdentityRootModule.identityLeafHash(serial, keysHash)` over the same
    /// commitments, and to what `FinalIdentityRegistry.identityTreeLeafOf`
    /// derives for a service.
    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));
    }

    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 NoChainGranted(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 Authorize and apply one request.
     *
     * @dev **Permissionless.** Whoever submits pays gas and carries no
     * authority: the credential decides, and it is verified here rather than by
     * a process that then tells everyone what it concluded. That is the whole
     * difference between this and the journal it replaces.
     *
     * Verify, apply, publish — in that order, in one transaction. There is no
     * window in which the record and the tree disagree, which is what the
     * journal's trial-then-append-then-install dance existed to approximate.
     */
    /// @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 this
    /// rule puts that out of reach of anyone, including us, since the holder can
    /// always stamp it here directly. Overstating merely delays a legitimate
    /// recovery, which fails safe.
    ///
    /// So there is nothing to authorize and no one 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.
    ///
    /// A future timestamp is refused. Otherwise one call could push an account
    /// permanently out of dormancy, which is the same seizure-proofing failure
    /// in the opposite direction.
    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 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 tree 1 every time,
    /// and age every outstanding proof. The bitmap moves only when a chain
    /// crosses its threshold — the same rarity as a freeze — so tree 1 keeps the
    /// cadence it was designed for.
    ///
    /// Permissionless for the same reason as `recordActivity`: it derives
    /// entirely from state this contract already holds, so the caller chooses
    /// nothing. Publishing only on a CHANGE is what keeps a caller from
    /// rewriting tree 1 at will.
    /// @param slots Registry slots to evaluate. Explicit rather than a sweep,
    ///   because the set of chains is not this contract's to enumerate.
    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);
    }

    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: it is the cheapest check and the one that makes a
        // replayed request indistinguishable from a stale one to everything
        // below.
        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;
        // Only on acceptance. A refused transition must leave the authorization
        // spendable — nothing happened, and burning the nonce would mean a
        // mis-ordered request costs the holder a trip back to their cold key.
        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 exists on `chainRef`.
     * @dev A `ROLE_RELAYER` observation of a `WalletCreated` event on some other
     * chain, and NOT an authorization: it records where an account exists so the
     * fan-out knows where to write. Add-only and idempotent, so a relayer can
     * omit a chain and cannot remove one — the reconciler re-derives the set by
     * code probe and adds whatever was missed.
     *
     * `msg.sender` is a type-0x46 sender, derived from the relayer's
     * transaction key, so the role is resolved through the registry's sender
     * binding rather than looked up on the sender itself.
     */
    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 Pure and public, so a holder's client, a co-signer and this contract
    /// derive one value. `payloadHash` is `keccak256` of the ABI-encoded
    /// arguments exactly as the caller supplied them — the encoding is what
    /// stops a signature being moved onto different arguments, so it is bound
    /// byte for byte rather than re-derived from decoded fields.
    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 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
            )
        );
    }

    /// @dev The canonical action string the digest hashes. Restated from
    /// `accountState.js:ACTION` and pinned against it by the parity suite — a
    /// mismatch here is a signature no client can produce, with nothing naming
    /// the cause.
    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

    /// @dev Which credential each action is considered under. An early refusal
    /// and a statement of intent; the transition below is what actually
    /// enforces authority.
    function _expectedActor(Action action) private pure returns (Actor) {
        if (
            action == Action.FREEZE || action == Action.CANCEL_ROTATION
                || action == Action.CANCEL_GUARDIAN_CHANGE
        ) return Actor.GUARDIANS;
        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, the outcome is
            // already determined, and requiring the initiator to come back
            // would let an attacker win by keeping them away from their cold
            // key.
            return Actor.NONE;
        }
        return Actor.RECOVERY_KEY;
    }

    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, and accepting one here would hand
                // the attacker both halves.
                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);
    }

    /**
     * @dev Establish which key signed, by matching its commitment.
     *
     * The order of the two comparisons does not matter and must not: an account
     * whose live and recovery access commitments are EQUAL has no second
     * credential at all, and resolving that to whichever branch ran first would
     * silently grant the live key the recovery key's powers.
     */
    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-PQ live credential. Refused once the account has migrated:
            // a migrated account's ECDSA owner is a settlement destination, 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;
    }

    /**
     * @dev Count distinct guardian authorizations over `digest`.
     *
     * Distinctness is checked on the RESOLVED guardian, never on anything the
     * request labels itself with, so one guardian cannot reach a threshold by
     * submitting N times — the failure that turns an M-of-N into a 1-of-N
     * without changing a single visible parameter.
     */
    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; // silence the unused-parameter warning without widening the signature
    }

    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];
        // Not an account here. Only the EOA form can speak for it, and that form
        // names nobody — so an explicit guardian with no record is a claim this
        // cannot check rather than one it should take on faith.
        if (!g.opened) revert UnknownAccount(auth.guardian);
        // A frozen guardian is one whose own live key is under suspicion.
        // Letting it cancel someone else's rotation is precisely the move a
        // compromised guardian would make.
        if (g.frozen) revert CredentialNotPermitted("guardian is frozen");

        if (auth.pqBlob.length != 0) {
            if (!g.pqEnabled) revert CredentialNotPermitted("guardian is not PQ");
            // The LIVE access key, not the recovery one. Acting as a guardian is
            // an ordinary action for that account; its recovery pair authorizes
            // rotating its own credentials and nothing else.
            (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();
    }

    /**
     * @dev Decode a PQ blob and enforce the slot whitelist.
     *
     * The ACCESS CLASS, not a named slot. Both access slots carry the same
     * algorithm, so naming one of them would work by coincidence. What this
     * enforces is that an ML-DSA 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.
     */
    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);
    }

    /// @dev `ecrecover` with the malleability and zero-address cases closed.
    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

    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) {
            // An ordinary action of the account, so the LIVE key — and, like
            // every other action, nothing a frozen account may do: a row added
            // mid-freeze is a destination an attacker holding the live key
            // chose, and settlement toward it is exactly what the freeze stops.
            if (actor != Actor.LIVE_KEY) revert CredentialNotPermitted("chain account needs the live key");
            if (a.frozen) revert InvalidTransition("a frozen account cannot change its chain table");
            (bytes32 chainRef, bytes32 account) = abi.decode(request.payload, (bytes32, bytes32));
            _setChainAccount(request.wallet, chainRef, account);
        } 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;
        }
    }

    /// @dev Set or replace the row for `chainRef`. Zero in either half is
    ///      refused: a zero chain names nothing and a zero account is the
    ///      settlement contract's "unspecified", which this table exists to
    ///      resolve rather than restate.
    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);
    }

    function _requireRecovery(Actor actor) private pure {
        if (actor != Actor.RECOVERY_KEY) revert CredentialNotPermitted("recovery key only");
    }

    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 A strict majority, and never below the freeze threshold plus one.
    /// @dev The asymmetry is what bounds a bad guardian set. Freezing is
    /// fail-closed and self-harming at worst, so a low bar is fine; cancelling
    /// can block the holder's remedy, so one malicious guardian must not be able
    /// to do it alone.
    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;
    }

    /**
     * @dev Structural checks on a guardian set.
     *
     * These bound SHAPE, not independence — three addresses one party controls
     * pass every rule, and nothing on chain can see that either. They remove the
     * configurations that are wrong on their face; the threshold asymmetry and
     * the bounded cancel are what make a badly-chosen set survivable.
     */
    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.
    function accountOf(address wallet) external view returns (Account memory) {
        if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
        return _accounts[wallet];
    }

    /// @notice Whether an account exists here at all.
    /**
     * @notice Which generation of this account's encapsulation key is current.
     *
     * @dev What a sealer writes into an intent header's `kemKeyVersion`, 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 this
     * is read on the composing path for every intent and `recordOf` returns the
     * whole account plus four arrays. Zero means the account was never opened —
     * `_open` starts at one — so a caller cannot mistake "no such account" for
     * "the first key".
     */
    function kemVersionOf(address wallet) external view returns (uint16) {
        return _accounts[wallet].kemVersion;
    }

    /// @notice The genesis certificate serial — the raw half of the tree-8
    /// admission leaf's preimage. Zero for an account that was never opened.
    /// @dev What a plane-migration snapshot captures beside `leafOf`: the
    /// derived admission hash is not invertible, so a re-open on a fresh
    /// plane needs this to rebuild the same leaf.
    function serialOf(address wallet) external view returns (bytes32) {
        return _accounts[wallet].serial;
    }

    function isOpen(address wallet) external view returns (bool) {
        return _accounts[wallet].opened;
    }

    /// @notice The live guardian set.
    function guardiansOf(address wallet) external view returns (address[] memory) {
        return _guardians[wallet];
    }

    /// @notice The guardian set a pending change would install.
    function pendingGuardiansOf(address wallet) external view returns (address[] memory) {
        return _pendingGuardians[wallet];
    }

    /// @notice Chains this account is known to exist on — the fan-out target set.
    function deploymentsOf(address wallet) external view returns (bytes32[] memory) {
        return _deployments[wallet];
    }

    /// @notice The holder's `(chainRef, account)` table — the leaf's `deployedChains`.
    function chainAccountsOf(address wallet) external view returns (FinalStateTrees.ChainAccount[] memory) {
        return _chainAccounts[wallet];
    }

    /// @notice The account `wallet` is on `chainRef`, or zero if it has no row there.
    /// @dev What a zero settlement beneficiary toward `chainRef` resolves to,
    /// and what the co-signers check at admission before the source lock.
    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 eleven. A reader that fetched them separately
    /// would also be fetching them at eleven different blocks.
    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 How many accounts exist.
    /// @notice Everything the ledger holds for one account, in one read — the
    ///         account plane's mirror (`chainLedger.js`) and the redeploy export
    ///         read this; `accountWords` carries the record verbatim for restore.
    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]
        );
    }

    function walletCount() external view returns (uint256) {
        return _wallets.length;
    }

    /// @notice A page of accounts, in the order they were opened.
    /// @dev Paged because the 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.
    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 `FinalWalletFactory.AccountStateLeaf` order.
    function leafOf(address wallet) external view returns (FinalStateTrees.AccountStateLeaf memory) {
        if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
        return _leafOf(wallet);
    }

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

contracts/finalchain/FinalCertificate.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

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

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

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

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

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

    /// The wallet's four slots, in two stages of two.
    ///
    /// A certificate carries ONE stage, never all four. The stage is what gets
    /// issued, rotated and revoked as a unit, and a holder presenting a live
    /// certificate presents both of that stage's keys or neither — splitting
    /// them per slot would let half a stage be presented as if it were whole.
    ///
    /// This applies to services exactly as it applies to a user's wallet.
    /// A co-signer is a Final Wallet: same four slots, same split, same
    /// algorithms. There is no second kind of identity in this system.
    uint16 internal constant PURPOSE_ACTIVE_TX = 0x0010;
    uint16 internal constant PURPOSE_ACTIVE_ACCESS = 0x0011;
    uint16 internal constant PURPOSE_RECOVERY_TX = 0x0012;
    uint16 internal constant PURPOSE_RECOVERY_ACCESS = 0x0013;
    /// @dev v4's encapsulation purposes. Parsed, and each stage's pair is
    ///      resolved alongside its signing pair — `FinalIdentityRegistry` then
    ///      stores them so a sender can encapsulate to a registered party
    ///      without a second lookup somewhere less authoritative.
    ///
    ///      They were declared and skipped for one release, which is how the
    ///      registry's four encapsulation-key mappings ended up read in three
    ///      places and written in none: `kemCommitments` hashed the empty
    ///      string for every account and `kemKeysOf` returned nothing.
    uint16 internal constant PURPOSE_ACTIVE_KEM = 0x0014;
    uint16 internal constant PURPOSE_RECOVERY_KEM = 0x0015;
    /// @dev The seal: a second SLH-DSA-SHAKE-256s key, distinct from the access
    ///      key, that co-signs execution-class quorum decisions. Carried by
    ///      SERVICE certificates only — a user's wallet never seals — and
    ///      optional in the schema, so a certificate without it parses
    ///      unchanged. Outside `keysHash`: a seal is operational, rotated by
    ///      issuing a new live certificate, and it must not move a wallet
    ///      address it plays no part in.
    uint16 internal constant PURPOSE_ACTIVE_SEAL = 0x0016;

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

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

    /// @notice What the chain keeps out of one certificate.
    struct Parsed {
        bytes32 certHash;
        bytes32 serial;
        /// keccak256 of the IssuerDN bytes, for the chain-issuer pin: a
        /// chain-attested certificate carries the ruled constant DN and the
        /// registry compares hashes rather than strings.
        bytes32 issuerDnHash;
        /// The SubjectDN bytes verbatim — the jurisdiction rule reads its
        /// `C=` component at issuer registration.
        bytes subjectDn;
        /// The 0x0102 Institution extension VALUE, when present; empty
        /// otherwise. Issuer registration parses jurisdiction out of it.
        bytes institutionExt;
        /// SHA3-256 of the ISSUER's public key block. Zero-length — and so
        /// `bytes32(0)` here — for exactly one certificate in the hierarchy,
        /// which is what terminates chain validation.
        bytes32 authorityKeyId;
        /// SHA3-256 of this certificate's own public key block. The child's
        /// `authorityKeyId` must equal it, which is what links the two.
        bytes32 subjectKeyId;
        uint8 depth;
        uint8 maxDelegationDepth;
        /// MILLISECONDS, converted from the schema's nanoseconds — this chain's clock.
        uint64 notBefore;
        /// Milliseconds. Zero means never expires, which the schema allows.
        uint64 notAfter;
        /// The stage's transaction-class key. ML-DSA-87 — spending, and every
        /// high-cadence protocol action.
        bytes transactionKey;
        /// The stage's access-class key. SLH-DSA-SHAKE-256s — identity,
        /// rotation, recovery-pair promotion. A different hardness assumption,
        /// so a lattice break leaves the key that governs identity standing.
        bytes accessKey;
        /// The stage's ML-KEM-1024 encapsulation key. Empty on a CA, which has
        /// no encapsulation stage, and on any v4 certificate issued without
        /// one — see `parse` for why that is tolerated rather than refused.
        bytes kemMlKem;
        /// The stage's HQC-5 encapsulation key. Carried under the SAME purpose
        /// as the lattice half and distinguished only by algorithm, which is
        /// why the parser matches on the `(purpose, algorithm)` pair.
        bytes kemHqc;
        /// The service's seal key (`PURPOSE_ACTIVE_SEAL`, SLH-DSA-SHAKE-256s).
        /// Empty on every certificate that does not carry one — a user wallet,
        /// a recovery stage, a CA.
        bytes sealKey;
        /// Where the TBS ends, so a caller holding the whole certificate can
        /// find the `SignatureBlock` without parsing forward again.
        uint256 tbsLength;
    }

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

    /**
     * @notice Parse and self-check a `TBSCertificate`.
     * @param tbs the TBS bytes, verbatim. Not the whole certificate.
     * @param txPurpose the transaction-class purpose this stage should carry.
     * @param accessPurpose the access-class purpose for the same stage.
     *
     * @dev Checking for a CAPABILITY rather than a type is the schema's own
     * rule, and the reason there is no type field to check instead. Passing the
     * LIVE purposes to a recovery certificate finds neither key and reverts —
     * which is what stops a recovery certificate being registered as a live one
     * and handing the recovery pair everyday authority.
     */
    function parse(bytes calldata tbs, uint16 txPurpose, uint16 accessPurpose, uint16 kemPurpose)
        internal
        view
        returns (Parsed memory out)
    {
        _need(tbs, 58);
        if (uint32(bytes4(tbs[0:4])) != MAGIC) revert BadMagic(uint32(bytes4(tbs[0:4])));
        // Both live generations. v4 artifacts predate chain-attested issuance
        // and still parse — supersession is handled at admission (PoP and the
        // chain-issuer pins), not by refusing to read history.
        uint32 wireVersion = uint32(bytes4(tbs[4:8]));
        if (wireVersion != VERSION && wireVersion != VERSION_V4) revert BadVersion(wireVersion);

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

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

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

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

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

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

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

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

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

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

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

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

    /// @notice Parse a LIVE-stage certificate: `activeTransaction` + `activeAccess`.
    /// @dev `external`, like the other three entry points below: the registry
    /// sits against the EIP-170 ceiling and the TBS parser is its single
    /// largest inlined dependency, so the four doors it actually calls are
    /// DEPLOY-LINKED — the library is one more contract in the plane's fixed
    /// nonce-0 deploy order (doctrine §2 of `arch/final-chain-regenesis.md`),
    /// its address baked immutably into the registry's bytecode. A linked
    /// library is code, not a key: nothing can repoint it after deployment.
    function parseLive(bytes calldata tbs) external view returns (Parsed memory) {
        return parse(tbs, PURPOSE_ACTIVE_TX, PURPOSE_ACTIVE_ACCESS, PURPOSE_ACTIVE_KEM);
    }

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

    /// @notice Parse a CA certificate, whose two keys are both cert-signing.
    /// @dev No encapsulation purpose: a CA signs and is never sealed to, so
    /// `PURPOSE_ACTIVE_KEM` is passed as a value the loop can never match. A
    /// CA certificate carrying encapsulation keys would parse them into slots
    /// `_write` then discards, which is a shape worth refusing to have.
    function parseCa(bytes calldata tbs) external view returns (Parsed memory) {
        return parse(tbs, PURPOSE_CERT_SIGNING, PURPOSE_CERT_SIGNING, NO_KEM_PURPOSE);
    }

    /**
     * @notice Verify a CA's dual signature over `tbs`.
     * @dev Both must verify, not either. Two signatures under two different
     * hardness assumptions is the entire reason the schema carries two, and
     * accepting one would collapse that to whichever family breaks first.
     */
    function verifyIssuerSignatures(
        bytes memory tbs,
        bytes memory issuerMlDsaKey,
        bytes memory issuerSlhDsaKey,
        bytes memory mlDsaSignature,
        bytes memory slhDsaSignature
    ) external view returns (bool) {
        return FinalChainPrecompiles.verifyMlDsa87(issuerMlDsaKey, tbs, mlDsaSignature)
            && FinalChainPrecompiles.verifySlhDsa(issuerSlhDsaKey, tbs, slhDsaSignature);
    }

    function _need(bytes calldata tbs, uint256 upto) private pure {
        if (tbs.length < upto) revert Truncated(upto, tbs.length);
    }

    function _skipLengthPrefixed(bytes calldata tbs, uint256 p)
        private
        pure
        returns (uint256 next, uint256 length)
    {
        _need(tbs, p + 4);
        length = uint32(bytes4(tbs[p:p + 4]));
        next = p + 4 + length;
        _need(tbs, next);
    }

    function _bytes32At(bytes calldata tbs, uint256 start, uint256 length)
        private
        pure
        returns (bytes32)
    {
        // A SubjectKeyId that is not 32 bytes is not a SHA3-256 digest, so it
        // cannot match and the comparison will fail — which is the correct
        // outcome and needs no separate error.
        if (length != 32) return bytes32(0);
        return bytes32(tbs[start:start + 32]);
    }
}

contracts/finalchain/FinalChainPrecompiles.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

contracts/finalchain/FinalChainTime.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
pragma solidity ^0.8.20;

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

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

    /// @notice This chain's clock, stated as a function so a caller reads the
    /// unit rather than remembering it.
    /// @dev No arithmetic. It exists to make `FinalChainTime.nowMs()` the thing
    /// people write, which is self-describing where `block.timestamp` is not.
    function nowMs() internal view returns (uint64) {
        return uint64(block.timestamp);
    }
}

contracts/finalchain/FinalIdentityRegistry.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

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

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

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

/// @dev D7 (ruled 2026-09-01): ISSUER records project into tree 8 under their
/// own domain — `DOMAIN_ISSUER_LEAF ‖ certHash ‖ version ‖ issuerTreeRoot` —
/// so an issuer is stapleable for offline licence verification while the
/// distinct domain keeps its leaf out of wallet admission (the gateway folds
/// with the wallet domain, so an issuer leaf can never satisfy
/// `verifyIdentityCert`). `issuerTreeRoot` is a RESERVED word, zero until an
/// issuer's own certificate-tree anchor is wired — the only clean path to
/// offline licence revocation, since the fixed-depth insertion-ordered state
/// trees cannot prove non-inclusion.
bytes32 constant DOMAIN_ISSUER_LEAF = keccak256("FINAL_ISSUER_LEAF_v01");

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

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

/// @notice The identity tree's projection door on `FinalStateTrees`. A narrow
/// interface rather than an import, because the trees contract imports this
/// file — the dependency runs that way and this is the one call that runs the
/// other. Same pattern as `IChainSource` on the trees side.
interface IIdentityLeafSink {
    function syncIdentityLeaves(address[] calldata accounts) external;
}

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

contract FinalIdentityRegistry {
    // ---------------------------------------------------------------- roles

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

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

    /// @dev One per membership mutation, so an approval to grant a role can
    /// never be replayed as one to revoke. The registry is its own verifying
    /// contract for these.
    bytes32 public constant DOMAIN_REGISTER_WALLET = keccak256("FINAL_REGISTRY_REGISTER_WALLET_v01");
    bytes32 public constant DOMAIN_REGISTER_ISSUER = keccak256("FINAL_REGISTRY_REGISTER_ISSUER_v01");
    /// @notice The admission proof-of-possession digest domain (schema §v5).
    /// The HOLDER signs `keccak256(abi.encode(domain, chainid, registry,
    /// certHash, recoveryCertHash, gateNonce))` with the live transaction key
    /// (ML-DSA-87) AND the live access key (SLH-DSA-SHAKE-256s) — both
    /// families, in the admission transaction, verified by the precompiles.
    /// Possession lives in the TRANSACTION, never in the artifact.
    bytes32 public constant DOMAIN_IDENTITY_ADMISSION = keccak256("FINAL_IDENTITY_ADMISSION_v01");
    /// @notice Root-plane global certificate revocation (D5).
    bytes32 public constant DOMAIN_REVOKE_CERTIFICATE =
        keccak256("FINAL_REGISTRY_REVOKE_CERTIFICATE_v01");
    /// @notice The ISSUING identity's certificate-revocation digest domain.
    bytes32 public constant DOMAIN_ISSUER_CERT_REVOCATION =
        keccak256("FINAL_ISSUER_CERT_REVOCATION_v01");
    bytes32 public constant DOMAIN_REGISTER_LMS_KEY = keccak256("FINAL_REGISTRY_REGISTER_LMS_KEY_v01");
    bytes32 public constant DOMAIN_SET_ROLES = keccak256("FINAL_REGISTRY_SET_ROLES_v01");
    bytes32 public constant DOMAIN_REVOKE = keccak256("FINAL_REGISTRY_REVOKE_v01");
    bytes32 public constant DOMAIN_SET_REGISTRAR_THRESHOLD =
        keccak256("FINAL_REGISTRY_SET_REGISTRAR_THRESHOLD_v01");

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

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

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

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

    /// @notice The LMS signing key for an account, if it holds one.
    /// @dev One slot per (account, chain) — LMS-01. `nextLeaf` on an
    /// authority is a complete single-use counter only while the key it names
    /// signs for ONE chain, so the roster is stored the way it is armed:
    /// the same operator is a different signer on every chain.
    mapping(address account => mapping(uint64 chainId => LmsKey)) private _lmsKey;
    /// @notice Which account a signer fingerprint belongs to. This is the
    /// lookup the whole record exists for: a gateway roster names fingerprints
    /// and nothing else, so without it the keys are unattributable.
    /// @dev What a fingerprint is bound to: the account that holds it and the
    /// chain it signs for — one slot, written once at registration and left in
    /// place when superseded (attribution is history). The chain names the
    /// (account, chain) slot `lmsSignerIsLive` resolves against.
    // NOTE: this contract sits ~13 bytes under EIP-170 (24,563 of 24,576 at
    // the pinned optimizer settings). The next feature here pays for itself
    // in bytecode first — see the LMS-binding merge and the off-chain
    // zero-chain check for what that looks like.
    struct LmsBinding {
        address account;
        uint64 chainId;
    }

    mapping(bytes32 signerId => LmsBinding) private _lmsBinding;

    /// @notice The identity record for an account.
    mapping(address account => Identity) private _identity;
    /// The four slots, verbatim. All four are stored in full because the
    /// precompiles verify against a KEY, not a commitment — and a key that
    /// arrived in calldata proves nothing about who signed.
    ///
    /// A CA has two keys, not four, and they live in the two ACTIVE slots. One
    /// storage shape rather than two, because every reader would otherwise have
    /// to know which kind of party it was looking at before it could look.
    mapping(address account => bytes) private _activeTransactionKey;
    mapping(address account => bytes) private _activeAccessKey;
    mapping(address account => bytes) private _recoveryTransactionKey;
    mapping(address account => bytes) private _recoveryAccessKey;
    /// @notice The seal key — a service's second SLH-DSA-SHAKE-256s key, which
    /// co-signs execution-class quorum decisions. Empty for every identity
    /// whose certificate carries no `PURPOSE_ACTIVE_SEAL` entry: users, CAs.
    mapping(address account => bytes) private _activeSealKey;
    /// @notice Encapsulation keys, per stage. Two algorithms each — ML-KEM-1024
    /// (lattice) and HQC-5 (code-based) — so a break in either family leaves the
    /// other standing, the same reasoning that pairs ML-DSA with SLH-DSA above.
    /// @dev Stored as the RAW keys, like the signing keys, because a registry
    /// that held only commitments could not answer "encapsulate to this party"
    /// without a second lookup somewhere less authoritative.
    mapping(address account => bytes) private _activeKemMlKem;
    mapping(address account => bytes) private _activeKemHqc;
    mapping(address account => bytes) private _recoveryKemMlKem;
    mapping(address account => bytes) private _recoveryKemHqc;
    /// @notice Reverse index. A certificate identifies exactly one account, so
    /// presenting a `certHash` is enough to find who it belongs to.
    mapping(bytes32 certHash => address account) public accountOfCertificate;
    /// @notice Revocation by certificate, independent of the account record.
    /// A certificate stays revoked even if its account is later re-registered
    /// under a new one.
    mapping(bytes32 certHash => bool) public certificateRevoked;
    /// @notice Who revoked a certificate through the ISSUER half of the lane.
    /// Scoped by the verifier: the entry binds only when the recorded revoker
    /// is the certificate's own issuer. Never gates registration.
    mapping(bytes32 certHash => address) public certificateRevokedBy;

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

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

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

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

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

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

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

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

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

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

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

    /**
     * @dev Bootstrap is a real window, not a formality: every roster in this
     * system has to be installed by someone before it can install itself, and
     * pretending otherwise produced the one roster that could not be
     * bootstrapped in `FinalRootAuthority`. It is closed by
     * `sealBootstrap`, which is irreversible.
     *
     * While it is open the admin writes alone. Once it is closed there is no
     * single-caller path left — not for a registrar, not for anyone — and
     * every mutation goes through the sealed registrar quorum.
     */
    function _requireMembershipAuthority(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) private {
        if (!bootstrapSealed && msg.sender == bootstrapAdmin) return;
        _requireRegistrarQuorum(address(this), actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /**
     * @notice The sealed registrar quorum, for the other state-plane contracts.
     * @dev `msg.sender` — the calling contract — is the verifying contract the
     * digest binds and the counter it burns, so an approval collected for the
     * trees' configuration cannot be spent on the bundle log's. The caller
     * decides its own bootstrap exemption before calling; this function knows
     * no caller's admin and applies none.
     *
     * Anyone may SUBMIT such a transaction. Authority is the approvals, not the
     * sender, which is the whole point of the quorum.
     */
    function requireRegistrarQuorum(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireRegistrarQuorum(msg.sender, actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /// @dev The digest is `FinalPqQuorum.digest(verifyingContract, actionDomain,
    /// anchorBlock, keccak256(abi.encode(nonce, payloadDigest)))`; the seal is
    /// required — membership is the hybrid class.
    function _requireRegistrarQuorum(
        address verifyingContract,
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) private {
        if (registrarThreshold == 0) revert RegistrarThresholdIsZero();
        uint64 nonce = _gateNonce[verifyingContract];
        _gateNonce[verifyingContract] = nonce + 1;
        bytes32 quorumDigest = FinalPqQuorum.digest(
            verifyingContract, actionDomain, anchorBlock, keccak256(abi.encode(nonce, payloadDigest))
        );
        uint256 valid = FinalPqQuorum.require_(
            this,
            approvals,
            quorumDigest,
            ROLE_REGISTRAR,
            registrarThreshold,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            true
        );
        emit RegistrarQuorumApproved(verifyingContract, actionDomain, nonce, valid);
    }

    /**
     * @notice Set how many sealed registrar approvals a membership mutation needs.
     * @dev Bootstrap admin while the window is open; the current registrar
     * quorum afterwards, so a registrar set that grows or shrinks can move it.
     * Refuses a threshold the sealable registrars cannot meet, and refuses zero:
     * both are a registry that can never be written to again.
     */
    function setRegistrarThreshold(
        uint256 threshold,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireMembershipAuthority(
            DOMAIN_SET_REGISTRAR_THRESHOLD, keccak256(abi.encode(threshold)), anchorBlock, approvals
        );
        if (threshold == 0) revert RegistrarThresholdIsZero();
        uint256 sealable = sealableMemberCount(ROLE_REGISTRAR);
        if (sealable < threshold) revert RegistrarThresholdUnreachable(sealable, threshold);
        registrarThreshold = threshold;
        emit RegistrarThresholdSet(threshold);
    }

    /// @notice The replay counter the next registrar approval for `caller`
    /// must be made over.
    function gateNonceOf(address caller) external view returns (uint64) {
        return _gateNonce[caller];
    }

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

    /**
     * @notice The roster identity of an LMS public key.
     * @dev Byte-identical to `FinalRootAuthority.signerId`. Restated rather
     * than imported because the two live on different chains and there is no
     * import that would make them one value — which is precisely why a test
     * pins them together. A drift here would make every lookup miss while
     * looking perfectly well-formed.
     */
    function lmsSignerId(bytes16 keyId, uint8 height, bytes32 root) public pure returns (bytes32) {
        return keccak256(abi.encode(keyId, height, root));
    }

    /**
     * @notice Record the LMS signing key an already-registered account holds.
     * @dev Membership-gated, same as every other write here.
     *
     * Deliberately NOT a certificate: an LMS key is a capability of an existing
     * identity, not an identity of its own. Binding it to an account means it
     * inherits that account's revocation, so retiring a compromised operator is
     * one action rather than one-per-key-they-hold.
     *
     * @param account Must already be registered and not revoked.
     * @param version Strictly increasing. A rotation that does not advance it
     *   is refused, so a replayed registration cannot reinstate a key the
     *   operator has moved off.
     * @param anchorBlock The block the registrars read the roster at; see
     *   `FinalPqQuorum`. Ignored while bootstrap is open.
     * @param approvals The sealed registrar quorum. Empty while bootstrap is open.
     */
    function registerLmsKey(
        address account,
        uint64 chainId,
        bytes16 keyId,
        uint8 height,
        bytes32 root,
        uint64 version,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireMembershipAuthority(
            DOMAIN_REGISTER_LMS_KEY,
            keccak256(abi.encode(account, chainId, keyId, height, root, version)),
            anchorBlock,
            approvals
        );
        Identity storage id = _identity[account];
        if (!id.registered) revert UnknownAccount(account);
        if (id.revoked) revert CertificateIsRevoked(id.certHash);
        // A zero chain id is a tooling mistake, not an attack: the slot it
        // would occupy is self-consistent and no authority consults it. The
        // publisher refuses it; EIP-170 pressure keeps the check off-chain.
        if (height == 0 || height > 24) revert LmsHeightOutOfRange(height);
        if (root == bytes32(0)) revert LmsRootIsZero();

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

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

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

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

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

    /// @notice The LMS key an account holds for one chain, if any.
    function lmsKeyOf(address account, uint64 chainId) external view returns (LmsKey memory) {
        return _lmsKey[account][chainId];
    }

    /// @notice What a fingerprint is bound to: the account that registered it
    /// and the chain it signs for. Zeroes for a fingerprint never registered.
    /// @dev The revocation log's permanence gate reads this to find the
    /// (account, chain) SLOT a fingerprint belongs to — the slot's current key
    /// is what separates a superseded fingerprint (permanent, recordable) from
    /// a merely lapsed one (expiry, temporary, refused). Attribution is
    /// history: the binding survives supersession, exactly as the mapping
    /// behind {lmsSignerIsLive} does, because it IS that mapping.
    function lmsBindingOf(bytes32 signerId) external view returns (address account, uint64 chainId) {
        LmsBinding storage binding = _lmsBinding[signerId];
        return (binding.account, binding.chainId);
    }

    /**
     * @notice Is this signer fingerprint held by a live, unrevoked account?
     * @dev The question a verifier actually has. A gateway roster names
     * fingerprints and nothing else, so "is 0x39bb… still good?" is otherwise
     * unanswerable from the state plane.
     */
    function lmsSignerIsLive(bytes32 signerId) external view returns (bool live, address account) {
        LmsBinding storage binding = _lmsBinding[signerId];
        account = binding.account;
        if (account == address(0)) return (false, address(0));
        // `isActive`, not a registered/revoked pair spelled out here. The
        // certificate validity window is part of standing: an expired identity
        // already holds no role, and a signer lookup that disagreed would leave
        // a roster satisfiable by an operator the rest of the registry has
        // stopped honouring. Spelling the condition out a second time is how
        // the two drift apart.
        if (!isActive(account)) return (false, account);
        // The CURRENT key of the fingerprint's own (account, chain) slot, not
        // merely one this account ever held: a superseded fingerprint stays
        // attributable but stops being live, and a rotation on one chain says
        // nothing about the same operator's key on another.
        LmsKey storage k = _lmsKey[account][binding.chainId];
        live = k.registered && lmsSignerId(k.keyId, k.height, k.root) == signerId;
    }

    /// @notice Close the bootstrap window. Irreversible.
    /// @dev Refuses while the registrar quorum is unset or unreachable, because
    /// sealing then would leave a registry nobody can ever write to again. The
    /// count is of registrars that can SEAL — a certificate authority carrying
    /// the role has no seal key and can never contribute an approval.
    function sealBootstrap() external {
        if (msg.sender != bootstrapAdmin) revert NotAuthorized(msg.sender);
        if (bootstrapSealed) revert BootstrapAlreadySealed();
        if (registrarThreshold == 0) revert RegistrarThresholdIsZero();
        uint256 sealable = sealableMemberCount(ROLE_REGISTRAR);
        if (sealable < registrarThreshold) {
            revert RegistrarThresholdUnreachable(sealable, registrarThreshold);
        }
        bootstrapSealed = true;
        bootstrapAdmin = address(0);
        emit BootstrapSealed(msg.sender);
    }

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

    /**
     * @notice Wire the trees and the revocation log, once, inside the
     *         bootstrap window.
     * @dev One-shot because both pointers are TRUST TOPOLOGY: the trees
     * pointer decides where the wallet-creation admission set is written, and
     * the log pointer decides where permanent standing losses are recorded. A
     * re-wireable pointer would be a key over both. It cannot be a constructor
     * argument — both contracts take THIS registry as one — so the deploy
     * tooling calls it in the same nonce-fixed block that deploys them, before
     * any identity is registered.
     */
    function wireStatePlane(address stateTrees_, address revocationLog_) external {
        if (bootstrapSealed || msg.sender != bootstrapAdmin) revert NotAuthorized(msg.sender);
        if (stateTrees != address(0) || revocationLog != address(0)) revert StatePlaneAlreadyWired();
        if (stateTrees_ == address(0) || revocationLog_ == address(0)) revert ZeroStatePlane();
        stateTrees = stateTrees_;
        revocationLog = revocationLog_;
        emit StatePlaneWired(stateTrees_, revocationLog_);
    }

    /// @dev Project `account`'s tree-8 leaf, same-tx. Skipped while the plane
    /// is unwired — the bootstrap-window state the deploy tooling closes
    /// before the first registration — and never otherwise: the leaf value is
    /// derived by the trees contract from THIS registry's post-mutation state,
    /// so there is nothing here to get wrong besides forgetting to call it.
    function _projectIdentity(address account) private {
        address trees = stateTrees;
        if (trees == address(0)) return;
        address[] memory one = new address[](1);
        one[0] = account;
        IIdentityLeafSink(trees).syncIdentityLeaves(one);
    }

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

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

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

    /**
     * @notice Register or rotate a Final Wallet identity from its two public
     *         certificates — CHAIN-ATTESTED (schema §v5, ruled 2026-09-01).
     *
     * @param account The wallet address the certificate set derives.
     * @param liveTbs `live.pub.fcert` TBS — `activeTransaction` + `activeAccess`.
     * @param recoveryTbs `recovery.pub.fcert` TBS — the pre-committed recovery pair.
     * @param proof The HOLDER's two signatures over the admission digest —
     *        the live transaction key (ML-DSA-87) and the live access key
     *        (SLH-DSA-SHAKE-256s), verified in the precompiles inside this
     *        transaction. This replaced the CA signature: issuance authority
     *        is the registrar quorum, possession is this proof, and there is
     *        no root keypair anywhere.
     * @param roles Capability bitmask. The one thing the certificates do not
     *        say, because capability is this system's decision.
     * @param version Monotonic. A rotation that does not advance it is refused.
     * @param anchorBlock The block the registrars read the roster at. Ignored
     *        while bootstrap is open.
     * @param approvals The sealed registrar quorum. Empty while bootstrap is
     *        open. The digest binds the account, both certificates' bytes,
     *        the roles and the version.
     *
     * @dev **Both stages, together.** A wallet has four keys in two stages and
     * the recovery pair is PRE-COMMITTED — written at `initialize` from the same
     * certificate set that determined the address, which is why PQ migration
     * takes no key arguments. The two must share a `SerialNumber`: a serial is
     * per certificate SET, so two stages disagreeing are two different wallets.
     *
     * **Chain-attested means pinned, per stage:** the ruled IssuerDN and
     * AuthorityKeyId constants, depth exactly 1 (directly under the chain),
     * and `maxDelegationDepth == depth` (an end entity signs nothing — the
     * same immutable pair `identityTreeLeafOf` discriminates records by).
     */
    function registerWallet(
        address account,
        bytes calldata liveTbs,
        bytes calldata recoveryTbs,
        AdmissionProof calldata proof,
        uint256 roles,
        uint64 version,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external returns (bytes32 certHash) {
        // Read BEFORE the authority check: the quorum path burns this counter
        // inside `_requireRegistrarQuorum`, and the proof must bind the value
        // the round was built over. The bootstrap path burns it explicitly in
        // `_requireAdmissionProof`, so an admission is one-shot in both regimes.
        uint64 admissionNonce = _gateNonce[address(this)];
        _requireMembershipAuthority(
            DOMAIN_REGISTER_WALLET,
            keccak256(
                abi.encode(account, keccak256(liveTbs), keccak256(recoveryTbs), roles, version)
            ),
            anchorBlock,
            approvals
        );

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

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

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

    /**
     * @notice Register or rotate an ISSUER — a third party (or our own
     *         intermediate) that signs certificates OFF-chain with the keys
     *         registered here (D2: the superCA).
     *
     * @param account The issuer's account on this chain.
     * @param tbs The single issuer certificate's TBS: two CERT_SIGNING keys
     *        (ML-DSA-87 + SLH-DSA-SHAKE-256s), no recovery stage — renewing an
     *        issuer is re-issuing, a governance act rather than a key rotation.
     * @param parent The registered parent issuer for a nested intermediate;
     *        `address(0)` for an issuer hanging directly under the chain.
     * @param proof The issuer's OWN two cert-signing keys over the admission
     *        digest (`recoveryCertHash` slot is zero — there is no recovery
     *        stage to bind).
     *
     * @dev Admission is chain-native like any identity: registrar quorum plus
     * the holder's PoP. What the v4 delegation rules said survives verbatim as
     * LINEAGE — a nested issuer's depth, delegation bound and AuthorityKeyId
     * must chain to its registered parent — but no parent SIGNS anything; the
     * chain's admission is the issuance.
     *
     * Ruling 3: a registered issuer always expires (`NotAfter` real, window
     * bounded ~2 years) — the passive liveness touchpoint; renewal re-issues
     * under the same registered keys with a version bump.
     *
     * The jurisdiction rule (ruled 2026-09-01, amended): only the trust root
     * is jurisdiction-silent. An institution MUST carry its real ISO 3166
     * `C=` in its subject DN, matching the `jurisdiction` field of its
     * `0x0102` Institution extension — CA/Browser-Forum practice, enforced at
     * the door because a verifier's legal recourse starts with knowing where
     * an issuer answers for itself.
     */
    function registerIssuer(
        address account,
        bytes calldata tbs,
        address parent,
        AdmissionProof calldata proof,
        uint256 roles,
        uint64 version,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external returns (bytes32 certHash) {
        uint64 admissionNonce = _gateNonce[address(this)];
        _requireMembershipAuthority(
            DOMAIN_REGISTER_ISSUER,
            keccak256(abi.encode(account, keccak256(tbs), parent, roles, version)),
            anchorBlock,
            approvals
        );

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

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

    /// @notice Ruling 3's validity ceiling for registered issuers, in this
    /// chain's milliseconds: two 366-day years.
    uint64 public constant MAX_ISSUER_VALIDITY_MS = 2 * 366 days * 1000;

    /// @dev The chain-attested end-entity pins, run once per stage.
    function _requireChainAttestedEndEntity(FinalCertificate.Parsed memory c) private pure {
        if (c.authorityKeyId != CHAIN_AUTHORITY_KEY_ID) revert NotChainAttested(c.authorityKeyId);
        if (c.issuerDnHash != CHAIN_ISSUER_DN_HASH) revert WrongIssuerDn(c.issuerDnHash);
        if (c.depth != 1 || c.maxDelegationDepth != c.depth) {
            revert NotAnEndEntity(c.depth, c.maxDelegationDepth);
        }
    }

    /// @dev The v4 delegation rules, surviving as lineage: a nested issuer
    /// chains to a registered, signing-capable parent one level up; a direct
    /// issuer hangs under the chain at depth 1.
    function _requireLineage(address parent, FinalCertificate.Parsed memory c) private view {
        if (parent == address(0)) {
            if (c.authorityKeyId != CHAIN_AUTHORITY_KEY_ID) {
                revert NotChainAttested(c.authorityKeyId);
            }
            if (c.depth != 1) revert WrongDepth(c.depth, 1);
            return;
        }
        Identity storage ca = _identity[parent];
        if (!hasRole(parent, ROLE_CERTIFICATE_AUTHORITY)) {
            revert IssuerNotACertificateAuthority(parent);
        }
        // Delegation is governed by depth, not by a boolean. `Depth <
        // MaxDelegationDepth` permits signing, and a child sits exactly one
        // level down — an issuer cannot skip levels to escape its own bound.
        if (ca.depth >= ca.maxDelegationDepth) {
            revert IssuerMayNotSign(parent, ca.depth, ca.maxDelegationDepth);
        }
        if (c.depth != ca.depth + 1) revert WrongDepth(c.depth, ca.depth + 1);
        if (c.maxDelegationDepth > ca.maxDelegationDepth) {
            revert DelegationWidened(c.maxDelegationDepth, ca.maxDelegationDepth);
        }
        if (c.authorityKeyId != ca.subjectKeyId) {
            revert AuthorityKeyIdMismatch(c.authorityKeyId, ca.subjectKeyId);
        }
    }

    /// @dev The jurisdiction rule: a real ISO 3166 alpha-2 `C=` in the subject
    /// DN, equal to the Institution extension's `jurisdiction` field. The DN
    /// is canonical comma-separated form, so `C=` matches at the start or
    /// right after a comma; the component value is exactly two bytes.
    function _requireJurisdiction(FinalCertificate.Parsed memory c) private pure {
        bytes memory dn = c.subjectDn;
        bytes2 country;
        bool found = false;
        for (uint256 i = 0; i + 4 <= dn.length; i++) {
            if ((i == 0 || dn[i - 1] == ",") && dn[i] == "C" && dn[i + 1] == "=") {
                // Exactly two bytes, then end-of-DN or the next component.
                if (i + 4 < dn.length && dn[i + 4] != ",") revert JurisdictionMissing();
                country = bytes2(bytes.concat(dn[i + 2], dn[i + 3]));
                found = true;
                break;
            }
        }
        if (!found) revert JurisdictionMissing();

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

    /// @dev Verify the holder's PoP: both live-stage families over the
    /// admission digest, in the precompiles, inside this transaction. Burns
    /// the gate nonce on the bootstrap path (the quorum path burned it in
    /// `_requireRegistrarQuorum` already), so an admission is one-shot in
    /// both regimes.
    function _requireAdmissionProof(
        address account,
        FinalCertificate.Parsed memory live,
        bytes32 recoveryCertHash,
        AdmissionProof calldata proof,
        uint64 admissionNonce
    ) private {
        bytes memory message = abi.encodePacked(
            keccak256(
                abi.encode(
                    DOMAIN_IDENTITY_ADMISSION,
                    block.chainid,
                    address(this),
                    live.certHash,
                    recoveryCertHash,
                    admissionNonce
                )
            )
        );
        if (
            !FinalChainPrecompiles.verifyMlDsa87(live.transactionKey, message, proof.mlDsaSignature)
                || !FinalChainPrecompiles.verifySlhDsa(live.accessKey, message, proof.slhDsaSignature)
        ) revert AdmissionProofInvalid(account);
        if (_gateNonce[address(this)] == admissionNonce) {
            _gateNonce[address(this)] = admissionNonce + 1;
        }
    }

    function _write(
        address account,
        FinalCertificate.Parsed memory live,
        FinalCertificate.Parsed memory recovery,
        uint256 roles,
        uint64 version,
        bool isCa
    ) private {
        if (account == address(0)) revert UnknownAccount(account);
        if (certificateRevoked[live.certHash]) revert CertificateIsRevoked(live.certHash);

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

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

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

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

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

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

        accountOfCertificate[live.certHash] = account;

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

    /**
     * @dev Store one stage's encapsulation pair, or clear it.
     *
     * Empty is legitimate and is not the same as absent-and-wrong: a CA has no
     * encapsulation stage, and a certificate issued before v4 carries none.
     * `FinalCertificate.parse` has already refused the half-populated case, so
     * by here the pair is both or neither.
     *
     * Cleared rather than left alone on a rotation to an empty pair. A stale
     * key surviving a rotation is a sender encapsulating to a credential the
     * account has disowned, and the intent then never decrypts — the failure
     * mode with no error attached, and the one this whole pairing exists to
     * avoid.
     */
    function _storeKemPair(address account, bool isCa, bytes memory mlKem, bytes memory hqc, bool isLive)
        private
    {
        if (isCa || mlKem.length == 0) {
            delete (isLive ? _activeKemMlKem : _recoveryKemMlKem)[account];
            delete (isLive ? _activeKemHqc : _recoveryKemHqc)[account];
            return;
        }
        if (!FinalChainPrecompiles.isWellFormedMlKem1024(mlKem)) {
            revert MalformedEncapsulationKey(account, FinalCertificate.ALG_ML_KEM_1024);
        }
        if (!FinalChainPrecompiles.isWellFormedHqc5(hqc)) {
            revert MalformedEncapsulationKey(account, FinalCertificate.ALG_HQC_5);
        }
        if (isLive) {
            _activeKemMlKem[account] = mlKem;
            _activeKemHqc[account] = hqc;
        } else {
            _recoveryKemMlKem[account] = mlKem;
            _recoveryKemHqc[account] = hqc;
        }
    }

    /// @notice Grant or withdraw capabilities without rotating keys.
    /// @dev Separate from registration because the two have different
    /// cadences: a role changes when a service's job changes, a key changes
    /// when it is compromised or aged out. Folding them together would force a
    /// key rotation to express a role change.
    function setRoles(
        address account,
        uint256 roles,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireMembershipAuthority(
            DOMAIN_SET_ROLES, keccak256(abi.encode(account, roles)), anchorBlock, approvals
        );
        Identity storage id = _identity[account];
        if (!id.registered) revert UnknownAccount(account);
        if (id.revoked) revert CertificateIsRevoked(id.certHash);
        uint256 previous = id.roles;
        id.roles = roles;
        _requireRegistrarQuorumReachable();
        emit IdentityRolesChanged(account, previous, roles);
        // Roles are not in the tree-8 leaf, so this rewrites the same value —
        // kept anyway so "every identity mutation projects" has no exceptions
        // to remember.
        _projectIdentity(account);
    }

    /// @dev Once sealed, no mutation may leave the registrar quorum unreachable
    /// — that is the one change nothing could ever undo. Checked after the
    /// write so the count reflects it.
    function _requireRegistrarQuorumReachable() private view {
        if (!bootstrapSealed) return;
        uint256 sealable = sealableMemberCount(ROLE_REGISTRAR);
        if (sealable < registrarThreshold) {
            revert RegistrarThresholdUnreachable(sealable, registrarThreshold);
        }
    }

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

    /**
     * @notice Root-plane GLOBAL certificate revocation, by `certHash` (D5).
     *
     * @dev The half of the one revocation lane that gates registration and
     * covers break-glass: any certificate — registered, off-chain-issued, or
     * never seen — can be killed by handle under the registrar quorum. When
     * the handle is a registered identity's CURRENT certificate the identity
     * falls with it (flag, roles, same-tx projection), so a break-glass by
     * handle is never weaker than {revoke} — it only skips the LMS-slot
     * enumeration, which stays permanently recordable through the revocation
     * log's permissionless door.
     */
    function revokeCertificate(
        bytes32 certHash,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireMembershipAuthority(
            DOMAIN_REVOKE_CERTIFICATE, keccak256(abi.encode(certHash)), anchorBlock, approvals
        );
        certificateRevoked[certHash] = true;
        address bound = accountOfCertificate[certHash];
        if (bound != address(0)) {
            Identity storage id = _identity[bound];
            if (!id.revoked) {
                id.revoked = true;
                id.roles = 0;
                _requireRegistrarQuorumReachable();
                emit IdentityRevoked(bound, certHash);
                _projectIdentity(bound);
            }
        }
        emit CertificateRevoked(certHash, address(0));
    }

    /**
     * @notice The ISSUING identity's half of the revocation lane: a registered
     * issuer revokes a certificate it signed OFF-chain, by `certHash`.
     *
     * @dev "Sub-issuer and us alike" (D5) — but SCOPED: this records WHO
     * revoked, and a verifier honours the entry only when the revoker is the
     * certificate's own issuer (which the verifier knows — it holds the
     * cert). It deliberately does NOT set the global `certificateRevoked`
     * flag: that flag gates registration, and letting any registered issuer
     * set it for an arbitrary handle would be a griefing lane over other
     * people's certificates.
     *
     * Anyone may SUBMIT; authority is the two signatures — the issuer's
     * registered cert-signing keys over a digest binding this registry, the
     * chain, the handle and the issuer's own gate nonce. One-way: the first
     * revoker of a handle is recorded and a second write is refused, because
     * "revoked twice by two parties" is two facts where the lane models one.
     */
    function revokeIssuedCertificate(
        address issuer,
        bytes32 certHash,
        AdmissionProof calldata proof
    ) external {
        if (!hasRole(issuer, ROLE_CERTIFICATE_AUTHORITY)) {
            revert IssuerNotACertificateAuthority(issuer);
        }
        if (certificateRevokedBy[certHash] != address(0)) revert CertificateIsRevoked(certHash);
        uint64 nonce = _gateNonce[issuer];
        _gateNonce[issuer] = nonce + 1;
        bytes memory message = abi.encodePacked(
            keccak256(
                abi.encode(
                    DOMAIN_ISSUER_CERT_REVOCATION,
                    block.chainid,
                    address(this),
                    issuer,
                    certHash,
                    nonce
                )
            )
        );
        if (
            !FinalChainPrecompiles.verifyMlDsa87(
                _activeTransactionKey[issuer], message, proof.mlDsaSignature
            )
                || !FinalChainPrecompiles.verifySlhDsa(
                    _activeAccessKey[issuer], message, proof.slhDsaSignature
                )
        ) revert AdmissionProofInvalid(issuer);
        certificateRevokedBy[certHash] = issuer;
        emit CertificateRevoked(certHash, issuer);
    }

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

    /// @notice The full identity record. `registered` is the field to branch on.
    function identityOf(address account) external view returns (Identity memory) {
        return _identity[account];
    }

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

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

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

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

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

    /// @notice The four commitments, in the order tree 1's leaf wants them.
    /// @dev keccak, not SHA3 — these feed `FinalWalletFactory.accountStateLeafHash`,
    /// which every other chain verifies with, and that one hashes with keccak.
    function keyCommitments(address account)
        external
        view
        returns (
            bytes32 liveAccess,
            bytes32 liveTransaction,
            bytes32 recoveryAccess,
            bytes32 recoveryTransaction
        )
    {
        liveAccess = keccak256(_activeAccessKey[account]);
        liveTransaction = keccak256(_activeTransactionKey[account]);
        recoveryAccess = keccak256(_recoveryAccessKey[account]);
        recoveryTransaction = keccak256(_recoveryTransactionKey[account]);
    }

    /**
     * @notice The tree-8 leaf `account` currently earns: the execution
     *         chains' identity leaf while the identity stands, zero once it
     *         does not.
     *
     * @dev The leaf VALUE is `keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖
     * keysHash)` — byte-identical to `IdentityRootModule.identityLeafHash`,
     * which is also the `certHash` inside the wallet's CREATE2 derivation —
     * with `keysHash` folded exactly as the certificate issuer folds it:
     * `keccak256(activeAccess ‖ activeTransaction ‖ recoveryAccess ‖
     * recoveryTransaction ‖ activeKem ‖ recoveryKem)`, six commitment words
     * packed in slot order (`minePqVanityCerts.cjs` is the reference encoder;
     * the parity test pins this function against the premined fixtures).
     *
     * Zero — the empty slot's own value, unprovable as a leaf because no
     * certificate hashes to it — for anything that must not admit a wallet
     * creation: a revoked identity, one outside its validity window, and any
     * CA. The CA exclusion is structural, not a role read: an end entity has
     * `depth == maxDelegationDepth` (it issues nothing), a CA never does, and
     * the depth pair is immutable per version where roles are not.
     *
     * Lives HERE rather than on `FinalStateTrees` (whose tree 8 consumes it)
     * because every input is this contract's storage and the trees contract
     * sits against EIP-170.
     */
    function identityTreeLeafOf(address account) external view returns (bytes32) {
        Identity storage id = _identity[account];
        if (!id.registered) revert UnknownAccount(account);
        if (id.revoked || !_withinValidity(id)) return bytes32(0);
        if (id.depth != id.maxDelegationDepth) {
            // D7 (ruled 2026-09-01): an ISSUER exists in tree 8 under its own
            // domain, so its record is stapleable for offline licence
            // verification. `certHash` suffices (it covers the whole TBS and
            // the verifier holds the cert), `version` makes supersession move
            // the leaf, and the third word RESERVES the issuer's own
            // certificate-tree anchor — zero until wired. The distinct domain
            // does the wallet-admission exclusion the zero projection used to
            // do; zero-on-revoke above is now load-bearing for both record
            // kinds (a fresh staple is an unrevoked statement).
            return keccak256(
                abi.encodePacked(DOMAIN_ISSUER_LEAF, id.certHash, uint64(id.version), bytes32(0))
            );
        }
        bytes32 liveKem = keccak256(
            abi.encodePacked(DOMAIN_KEM_BUNDLE, _activeKemMlKem[account], _activeKemHqc[account]));
        bytes32 recoveryKem = keccak256(
            abi.encodePacked(DOMAIN_KEM_BUNDLE, _recoveryKemMlKem[account], _recoveryKemHqc[account]));
        bytes32 keysHash = keccak256(
            abi.encodePacked(
                keccak256(_activeAccessKey[account]),
                keccak256(_activeTransactionKey[account]),
                keccak256(_recoveryAccessKey[account]),
                keccak256(_recoveryTransactionKey[account]),
                liveKem,
                recoveryKem
            )
        );
        return keccak256(abi.encodePacked(DOMAIN_IDENTITY_LEAF, id.serial, keysHash));
    }

    /// @notice Per-stage encapsulation commitments, in `AccountStateLeaf` order.
    /// @dev One word per STAGE, over both of that stage's KEM public keys. The
    /// pair is the unit — an account holds both or neither — so committing them
    /// separately would model a state the protocol does not recognise, and every
    /// downstream record would carry two words where one says the same thing.
    ///
    /// An account registered before the encapsulation slots existed hashes the
    /// empty string here rather than reverting: `syncIdentities` must keep
    /// projecting it, and a leaf that cannot be built is a party that cannot be
    /// revoked.
    function kemCommitments(address account)
        external
        view
        returns (bytes32 liveKem, bytes32 recoveryKem)
    {
        liveKem = keccak256(
            abi.encodePacked(DOMAIN_KEM_BUNDLE, _activeKemMlKem[account], _activeKemHqc[account]));
        recoveryKem = keccak256(
            abi.encodePacked(DOMAIN_KEM_BUNDLE, _recoveryKemMlKem[account], _recoveryKemHqc[account]));
    }

    /// @notice The encapsulation keys themselves, for a party composing a message.
    function kemKeysOf(address account)
        external
        view
        returns (bytes memory activeMlKem, bytes memory activeHqc)
    {
        return (_activeKemMlKem[account], _activeKemHqc[account]);
    }

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

    /**
     * @notice The Final Chain sender a transaction key produces.
     * @dev `keccak256(uint8(4) ‖ publicKey)[12:]` — byte-identical to what the
     * node derives from a type-0x46 envelope and to the backend's
     * `pqTransaction.senderOf`. Pure, so a client can compute it from a
     * certificate before the identity is registered.
     */
    function senderFor(bytes memory transactionKey) public pure returns (address) {
        return address(uint160(uint256(keccak256(abi.encodePacked(ENVELOPE_ALG_ML_DSA_87, transactionKey)))));
    }

    /// @notice The sender `account`'s transactions arrive from, or zero for an
    /// account with no transaction key on record.
    function senderOf(address account) external view returns (address) {
        bytes storage key = _activeTransactionKey[account];
        if (key.length == 0) return address(0);
        return senderFor(key);
    }

    /// @notice `hasRole` for a `msg.sender`: resolves the sender to its identity
    /// first. False for a sender no identity claims.
    function senderHasRole(address sender, uint256 roleMask) external view returns (bool) {
        address account = accountOfSender[sender];
        return account != address(0) && hasRole(account, roleMask);
    }

    /// @notice How many accounts carrying `roleMask` also hold a seal key —
    /// the members that can take part in a sealed quorum.
    function sealableMemberCount(uint256 roleMask) public view returns (uint256 sealable) {
        uint256 n = _accounts.length;
        for (uint256 i = 0; i < n; i++) {
            address a = _accounts[i];
            if (hasRole(a, roleMask) && _activeSealKey[a].length != 0) sealable++;
        }
    }

    /// @notice Number of registered accounts.
    function accountCount() external view returns (uint256) {
        return _accounts.length;
    }

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

    /// @notice Every account carrying every bit in `roleMask`.
    /// @dev A view, so the O(n) scan costs nothing. Callers that need this in a
    /// transaction should pass the member list explicitly instead — see
    /// `FinalPqQuorum`, which takes signers rather than searching for them.
    function accountsWithRole(uint256 roleMask) external view returns (address[] memory found) {
        uint256 n = _accounts.length;
        address[] memory buf = new address[](n);
        uint256 count;
        for (uint256 i = 0; i < n; i++) {
            if (hasRole(_accounts[i], roleMask)) {
                buf[count++] = _accounts[i];
            }
        }
        found = new address[](count);
        for (uint256 i = 0; i < count; i++) {
            found[i] = buf[i];
        }
    }

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

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

    /// @notice Whether `account` is registered, unrevoked and in date,
    /// regardless of capability.
    function isActive(address account) public view returns (bool) {
        Identity storage id = _identity[account];
        return id.registered && !id.revoked && _withinValidity(id);
    }

    function _withinValidity(Identity storage id) private view returns (bool) {
        if (id.notBefore != 0 && FinalChainTime.nowMs() < id.notBefore) return false;
        if (id.notAfter != 0 && FinalChainTime.nowMs() >= id.notAfter) return false;
        return true;
    }

}

contracts/finalchain/FinalPqQuorum.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

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

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

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

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

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

    error ThresholdNotMet(uint256 valid, uint256 required);
    error SignersNotAscending(address previous, address next);
    error SignerLacksRole(address signer, uint256 roleMask);
    error WrongAlgorithm(address signer, uint8 got, uint8 required);
    error BadSignature(address signer, uint8 algorithm);
    error BadSeal(address signer);
    error AnchorAhead(uint64 anchorBlock, uint256 blockNumber);
    error AnchorStale(uint64 anchorBlock, uint256 blockNumber);
    error ThresholdIsZero();

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

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

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

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

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

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

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

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

            valid++;
        }

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

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

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

    function _verify(
        FinalIdentityRegistry registry,
        Approval calldata a,
        bytes memory message
    ) private view returns (bool) {
        // The LIVE pair, always. The recovery pair authorizes rotating this
        // account's own credentials and NOTHING else — a quorum that accepted
        // it would hand the recovery keys everyday authority, which is exactly
        // the separation the two stages exist to draw.
        if (a.algorithm == ALG_ML_DSA_87) {
            return FinalChainPrecompiles.verifyMlDsa87(
                registry.activeTransactionKeyOf(a.signer), message, a.signature
            );
        }
        if (a.algorithm == ALG_SLH_DSA_SHAKE_256S) {
            return FinalChainPrecompiles.verifySlhDsa(
                registry.activeAccessKeyOf(a.signer), message, a.signature
            );
        }
        // Any other id is a refusal, never a default — including the KEM ids
        // (3, 7) and the reserved FN-DSA id (6), none of which is a signature
        // scheme this quorum verifies.
        return false;
    }
}

contracts/finalchain/FinalStateTrees.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

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

/// @notice The one question `syncIdentities` asks the asset registry.
/// @dev An interface rather than an import of `FinalAssetRegistry`, which
///      imports this file: the registry is tree 6's writer and holds the trees
///      as an immutable, so the dependency runs that way and this is the one
///      read that runs the other.
interface IChainSource {
    function enabledChainRefs() external view returns (bytes32[] memory);
}

/// @notice The one question {FinalStateTrees.syncSlotKeyLeaves} asks the
///         slot-key registry: the leaf value for one member's slot — the
///         registry's own verdict, zero when the slot holds nothing usable.
interface ISlotKeySource {
    function slotKeyLeafOf(address member, uint64 slotIndex) external view returns (bytes32);
}

/// @notice The one question {FinalStateTrees.syncEndpointLeaves} asks the
///         endpoint registry: the leaf value for one tunnel endpoint — the
///         registry's own verdict (certificate hash, status, expiry, region),
///         zero when nothing is registered under the id.
interface IEndpointSource {
    function endpointLeafOf(bytes32 endpointId) external view returns (bytes32);
}

/**
 * @title FinalStateTrees
 * @notice The eight trees. Final Chain's state plane, and the source of truth
 *         every other chain projects from.
 *
 * @dev One tree per domain, because they change at unrelated cadences and a
 * combined tree invalidates every outstanding proof on every tick:
 *
 * | # | tree | holds | cadence |
 * |---|---|---|---|
 * | 1 | accounts | every Final Wallet's public state | per rotation / creation |
 * | 2 | phi | the PHI record: per (wallet, chain) balances, the lock, exposures | per publisher round |
 * | 3 | vasset | issued vAsset supply and backing, per (asset, chain) | per settlement |
 * | 4 | oracle | published prices and their inputs | ~10 s; 1 s for morph and fee assets |
 * | 5 | settlement | chain and asset registry roots | rarely |
 * | 6 | allowlist | assets, chains, policy, price sources, DEX deployments | rarely |
 * | 7 | intents | intent status, ring-keyed over the posting sequence | per posting |
 * | 8 | identity | the wallet-creation admission set, projected from the registry | per identity mutation |
 *
 * ## The hash shape is not a choice
 *
 * Leaves hash as `keccak256(0x00 ‖ leaf)` and internal nodes as
 * `keccak256(0x01 ‖ lo ‖ hi)` with the pair sorted. That is
 * `FinalMerkle.verifyTaggedSortedProof`, verbatim, which is what
 * `FinalWalletFactory.syncAccountState` and `FinalSettlement` already run on
 * every supported chain. A proof produced here is consumed there with no
 * translation and no contract change, and tree 1's leaf preimage is exactly
 * `FinalWalletFactory.accountStateLeafHash` — same fields, same order, the
 * `deployedChains` table `abi.encode`d like every other field.
 *
 * Getting this wrong is not a compile error anywhere. It is a root every chain
 * silently rejects, with nothing pointing at the cause.
 *
 * ## Positional slots under a sorted-pair tree
 *
 * Sorted pairs make a proof position-agnostic, which is why it carries no
 * direction bits. That does not stop the TREE from being positional, and here
 * it is: every key gets a permanent slot, so a single leaf update is `DEPTH`
 * hashes instead of a rebuild over every leaf. The verifier neither knows nor
 * needs to know that a slot exists.
 *
 * ## Branches (2026-09-04)
 *
 * The slot space of every tree is cut into `BRANCH_COUNT` branches by the top
 * `BRANCH_BITS` of the slot: a branch is a subtree with a permanent place, its
 * root is one internal node, and a leaf's path to the tree root passes through
 * it. Branches hold what belongs to the same domain but not to the same rows
 * — branch 0 is the owning service's CONFIGURATION on every tree, tree 8 adds
 * the owner → wallets index and the co-signers' slot keys beside the admission
 * set — and they are chosen over more trees because a branch shares its
 * tree's authority doors and writer, while a tree would need its own. A leaf
 * proves against its branch root with `BRANCH_DEPTH` siblings, against the
 * tree root with `DEPTH`, against the round root with `ROUND_DEPTH`: one path,
 * cut at three heights, one verifier.
 *
 * ## Rounds, and why the live roots are not the product
 *
 * `setLeaves` moves a tree. It does not publish one. A consumer that fetched
 * eight roots one at a time would get a price proof from one moment and a
 * roster proof from another, and something delisted in between would still
 * verify.
 *
 * `publishRound` snapshots all eight together, and folds them into ONE round
 * root — the tree roots as the level-`DEPTH` nodes of a depth-`ROUND_DEPTH`
 * tree, tree `t` at position `t` — so a single word commits to the whole
 * plane and any leaf in it proves against that word with four more siblings.
 * A round is the unit a consumer pins, and it is the only thing this contract
 * promises is contemporaneous. The execution chains keep anchoring per-tree
 * roots (identity, account state, registry roots): those must move at their
 * own cadence, not at the oracle's.
 */
contract FinalStateTrees {
    // ---------------------------------------------------------------- trees

    /// @notice Every Final Wallet's public state. The source of truth other
    /// chains copy through `syncAccountState`.
    uint8 public constant TREE_ACCOUNTS = 1;
    /// @notice The PHI record, per `(wallet, chain)`: balances, the lock, its
    /// terms, the exposures carved from it and the accrual between reconciliations.
    uint8 public constant TREE_PHI = 2;
    /// @notice vAsset supply and backing.
    uint8 public constant TREE_VASSET = 3;
    /// @notice Oracle prices and their inputs.
    uint8 public constant TREE_ORACLE = 4;
    /// @notice Settlement chain and asset registry roots.
    uint8 public constant TREE_SETTLEMENT = 5;
    /// @notice Which assets and chains are supported.
    uint8 public constant TREE_ALLOWLIST = 6;
    /// @notice Intent status, keyed by a RING over the posting sequence.
    /// @dev The search structure beside `FinalBundleLog`'s permanent record.
    /// Written only by `FinalIntentLog` through `treeWriter[7]` — the tree-1
    /// argument verbatim: the log verified the bond, the commitment, the
    /// approval and the consume itself, and a service quorum on top would be a
    /// censorship point over posting. Slots are permanent and intents are
    /// unbounded flow, so the log recycles keys modulo `CAPACITY`: the tree is
    /// an index with a ~1M-posting retention window, never the record.
    uint8 public constant TREE_INTENTS = 7;
    /// @notice The wallet-creation admission set — the identity leaves
    /// (`keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)`) every execution
    /// chain's gateway verifies certificates against.
    /// @dev The root the gateways anchor as `currentIdentityRoot` — a
    /// CONTINUOUS root over this tree replaces the cold-set snapshot the
    /// retired `publishIdentityRoot.cjs` ceremony folded off-chain. Two
    /// feeders, one per identity plane, and NO quorum door for either:
    ///
    /// - SERVICE identities: {syncIdentityLeaves}, the permissionless
    ///   projection of `FinalIdentityRegistry`'s own verdict — the registry
    ///   calls it same-tx on every identity mutation, and anyone may call it
    ///   to retire a leaf whose standing lapsed by TIME (expiry moves no
    ///   registry storage, so only a projection pass can zero it).
    /// - USER identities: `treeWriter[8]` — `FinalAccountLedger`, which
    ///   computes the leaf from the genesis certificate fields it verified
    ///   under its opener quorum and writes it once at `openAccount`. A user
    ///   admission leaf is permanent by construction: the certificate IS the
    ///   address, rotation never changes it, and a post-rotation creation on
    ///   a new chain reads PUBLISHED account state (WAL-02), never the
    ///   certificate's genesis keys.
    ///
    /// A quorum of service signatures must not be able to state an identity
    /// neither ruler decided, so `setLeaves` refuses this tree outright.
    uint8 public constant TREE_IDENTITY = 8;
    /// @notice Count, for iteration. Trees are 1-indexed; 0 is not a tree.
    uint8 public constant TREE_COUNT = 8;

    /// @notice 2^24 slots per tree, laid out as 16 BRANCHES of 2^20: the top
    /// `BRANCH_BITS` of a slot name the branch, the rest its position in it.
    /// A million rows per branch is far past where this design gets replaced
    /// by Final Chain proper. Raising any of this later is a migration, not a
    /// parameter change: the depth is in every root.
    uint256 public constant DEPTH = 24;
    uint256 public constant BRANCH_BITS = 4;
    uint8 public constant BRANCH_COUNT = 16;
    /// @notice Height of a branch: a leaf proves against its branch root with
    /// this many siblings.
    uint256 public constant BRANCH_DEPTH = DEPTH - BRANCH_BITS;
    /// @notice Slots per branch.
    uint256 public constant BRANCH_CAPACITY = 1 << BRANCH_DEPTH;
    /// @notice Slots per tree, all branches together.
    uint256 public constant CAPACITY = 1 << DEPTH;
    /// @notice The round root is a tree over the tree roots — position `t`
    /// holds tree `t`'s root, positions 0 and 9..15 the empty tree — folded
    /// with the same node hash. It is literally the root of a depth-28 tree
    /// whose level-24 nodes are the eight tree roots, which is what lets one
    /// path prove a leaf against it.
    uint256 public constant FOREST_BITS = 4;
    uint256 public constant ROUND_DEPTH = DEPTH + FOREST_BITS;

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

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

    bytes32 private constant ACTION_SET_LEAVES = keccak256("FinalStateTrees.setLeaves.v01");
    /// @dev Registrar-quorum actions, verified by the registry with this
    /// contract as the verifying contract. See `FinalIdentityRegistry.requireRegistrarQuorum`.
    bytes32 public constant ACTION_CONFIGURE_TREE = keccak256("FINAL_STATE_TREES_CONFIGURE_TREE_v01");
    bytes32 public constant ACTION_SET_TREE_WRITER = keccak256("FINAL_STATE_TREES_SET_TREE_WRITER_v01");
    bytes32 public constant ACTION_SET_CHAIN_SOURCE = keccak256("FINAL_STATE_TREES_SET_CHAIN_SOURCE_v01");
    bytes32 public constant ACTION_SET_SLOT_KEY_SOURCE = keccak256("FINAL_STATE_TREES_SET_SLOT_KEY_SOURCE_v01");
    bytes32 public constant ACTION_SET_ENDPOINT_SOURCE = keccak256("FINAL_STATE_TREES_SET_ENDPOINT_SOURCE_v01");
    bytes32 public constant ACTION_SEED_COUNTERS = keccak256("FINAL_STATE_TREES_SEED_COUNTERS_v01");
    bytes32 public constant ACTION_SET_TYPED_WRITER = keccak256("FINAL_STATE_TREES_SET_TYPED_WRITER_v01");
    bytes32 public constant ACTION_SET_CONFIG = keccak256("FINAL_STATE_TREES_SET_CONFIG_v01");

    /// @dev Key domains. Both are full-width hashes rather than the packed
    /// address they came from, which matters: an address key occupies only the
    /// low 160 bits, so a hashed key colliding with one needs ~2^96 work rather
    /// than a full collision. That is expensive but not comfortable, and the
    /// consequence would be a service identity landing in a wallet's slot.
    bytes32 private constant DOMAIN_ACCOUNT_KEY = keccak256("FinalStateTrees.key.account.v01");
    bytes32 private constant DOMAIN_IDENTITY_TREE_KEY = keccak256("FinalStateTrees.key.identity.v01");
    /// @dev Tree 8, branches 2 and 3, and branch 0 of every tree. Each is its
    ///      own domain so a key can never land in another branch's slot by
    ///      construction — `_set` refuses a key whose slot sits in a different
    ///      branch, and the domain is what makes that refusal unreachable.
    bytes32 private constant DOMAIN_OWNER_INDEX_KEY = keccak256("FinalStateTrees.key.ownerIndex.v01");
    bytes32 private constant DOMAIN_SLOT_KEY = keccak256("FinalStateTrees.key.slotKey.v01");
    bytes32 private constant DOMAIN_ENDPOINT_KEY = keccak256("FinalStateTrees.key.endpoint.v01");
    bytes32 private constant DOMAIN_CONFIG_KEY = keccak256("FinalStateTrees.key.config.v01");

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

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

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

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

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

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

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

    /// @notice A contemporaneous snapshot of all eight roots, and the one
    /// round root that folds them.
    struct Round {
        bytes32[TREE_COUNT + 1] roots;
        bytes32 roundRoot;
        uint64 blockNumber;
        uint64 timestamp;
    }

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

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

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

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

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

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

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

    error UnknownTree(uint8 treeId);
    error LengthMismatch(uint256 keys, uint256 leaves);
    error BranchFull(uint8 treeId, uint8 branch);
    error UnknownBranch(uint8 branch);
    /// @notice A key already holds a slot in another branch of this tree.
    error BranchMismatch(uint8 treeId, bytes32 key, uint8 have, uint8 want);
    /// @notice Branch 0 is written by `setConfig` alone.
    error ConfigBranchReserved(uint8 treeId);
    error SlotKeySourceUnset();
    error EndpointSourceUnset();
    /// @notice Counters can be seeded only into a plane that has published nothing.
    error NotFresh();
    error VersionCountMismatch(uint256 given);
    error TreeNotConfigured(uint8 treeId);
    error NothingToPublish();
    error UnknownKey(uint8 treeId, bytes32 key);
    error NotAuthorized(address caller);
    error NoRounds();
    error ThresholdUnreachable(uint8 treeId, uint256 live, uint256 required);
    /// @notice Trees 7 and 8 take no quorum writes — only their writer
    /// contract (and, for tree 8, the registry projection).
    error WriterOnlyTree(uint8 treeId);
    /// @notice `setLeaves` was called on a tree that has a typed writer.
    /// @dev Trees 2, 3 and 4 keep the leaf's preimage beside its hash so a
    ///      consumer can read the VALUE. An untyped write sets the hash and
    ///      cannot set the preimage — the pair would disagree, and the stored
    ///      value would look authoritative while committing to nothing. The
    ///      typed entrypoint is not a convenience over this one; it is the
    ///      only door.
    error TypedTreeOnly(uint8 treeId);
    /// @notice A `deployedChains` row names the zero chain or the zero account,
    ///         or repeats a chain. A table with either proves nothing about
    ///         where the account exists.
    error InvalidChainAccount(bytes32 chainRef, bytes32 account);

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

    /**
     * @param registry_ The identity registry. Every signer, key and role is
     *        resolved through it.
     * @dev The empty-subtree table is built here rather than as constants
     * because it depends on the tagging, and a constant table that drifted from
     * the tagging would produce roots nothing can verify — silently, since both
     * sides would still be self-consistent.
     */
    constructor(FinalIdentityRegistry registry_) {
        registry = registry_;

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

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

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

    /**
     * @dev The configuration gate: the registry's bootstrap admin alone while
     * its window is open, the sealed `ROLE_REGISTRAR` quorum afterwards. The
     * same window the registry uses, for the same reason — every roster has to
     * be installed by someone before it can install itself — and the same
     * quorum, because a threshold is membership by another name: whoever can
     * set K to one owns the tree.
     */
    function _requireConfigurationAuthority(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) private {
        if (!registry.bootstrapSealed() && msg.sender == registry.bootstrapAdmin()) return;
        registry.requireRegistrarQuorum(actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /**
     * @notice Set which role may write a tree and how many approvals it needs.
     * @param k Approvals a write needs; `0` leaves the tree unconfigured.
     * @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
     * @param approvals The sealed registrar quorum. Empty during bootstrap.
     */
    function configureTree(
        uint8 treeId,
        uint256 role,
        uint256 k,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _assertTree(treeId);
        _requireConfigurationAuthority(
            ACTION_CONFIGURE_TREE, keccak256(abi.encode(treeId, role, k)), anchorBlock, approvals
        );
        // Refuse a threshold nobody can meet. Register the members first; that
        // ordering is the point, not an inconvenience. A 4-of-5 configured
        // against three registered co-signers is a tree that reverts on every
        // write, and the revert names the threshold rather than the roster.
        if (k != 0) {
            uint256 live = registry.liveMemberCount(role);
            if (live < k) revert ThresholdUnreachable(treeId, live, k);
        }
        writerRole[treeId] = role;
        threshold[treeId] = k;
        emit TreeConfigured(treeId, role, k);
    }

    /**
     * @notice Point a tree at the contract allowed to write it directly.
     * @dev Same gate as `configureTree`, for the same reason. Setting it to the
     * zero address removes the path entirely and leaves the tree quorum-only.
     */
    function setTreeWriter(
        uint8 treeId,
        address writer,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _assertTree(treeId);
        _requireConfigurationAuthority(
            ACTION_SET_TREE_WRITER, keccak256(abi.encode(treeId, writer)), anchorBlock, approvals
        );
        treeWriter[treeId] = writer;
        emit TreeWriterSet(treeId, writer);
    }

    /**
     * @notice Point `syncIdentities` at the contract that knows the chain set.
     * @dev Same gate as `setTreeWriter`. Zero removes the source, after which
     * service leaves carry an empty `deployedChains` table.
     */
    function setChainSource(
        address source,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_CHAIN_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
        );
        chainSource = source;
        emit ChainSourceSet(source);
    }

    /// @notice Point tree 8's branch 3 at the slot-key registry it projects.
    function setSlotKeySource(
        address source,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_SLOT_KEY_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
        );
        slotKeySource = source;
        emit SlotKeySourceSet(source);
    }

    /// @notice Point tree 8's branch 4 at the endpoint registry it projects.
    function setEndpointSource(
        address source,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_ENDPOINT_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
        );
        endpointSource = source;
        emit EndpointSourceSet(source);
    }

    /**
     * @notice Take over the previous plane's counters — one `treeVersion` per
     *         tree (index = treeId, 0 unused) and the published `round` — so a
     *         redeploy is monotonic for every consumer that compares them
     *         (rings, explorers, the round feed). NO-WIPE redeploy, ruled
     *         2026-09-03. Past rounds' roots stay on the old plane:
     *         `roundRootAt` below the seed answers zero.
     * @dev Configuration authority (bootstrap admin before the seal, registrar
     *      quorum after), and only while this plane has published nothing.
     */
    function seedCounters(
        uint64[] calldata versions,
        uint64 round_,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SEED_COUNTERS, keccak256(abi.encode(versions, round_)), anchorBlock, approvals
        );
        if (versions.length != TREE_COUNT + 1) revert VersionCountMismatch(versions.length);
        if (round != 0) revert NotFresh();
        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            if (treeVersion[t] != 0) revert NotFresh();
        }
        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            treeVersion[t] = versions[t];
        }
        round = round_;
        emit CountersSeeded(round_, versions);
    }

    /// @notice Install the records contract that writes the typed trees.
    function setTypedWriter(
        address writer,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_TYPED_WRITER, keccak256(abi.encode(writer)), anchorBlock, approvals
        );
        typedWriter = writer;
        emit TypedWriterSet(writer);
    }

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

    /// @notice `FinalWalletFactory.AccountStateLeaf`, field for field.
    struct AccountStateLeaf {
        address wallet;
        bytes32 liveAccess;
        bytes32 liveTransaction;
        bytes32 recoveryAccess;
        bytes32 recoveryTransaction;
        /// @dev Active-stage encapsulation commitment and its pre-committed
        /// successor. Field-for-field with `FinalWalletFactory.AccountStateLeaf`;
        /// a field added on one side and not the other is a root every execution
        /// chain rejects, with nothing pointing at the cause.
        bytes32 liveKem;
        bytes32 recoveryKem;
        address owner;
        bool pqEnabled;
        bool frozen;
        /// @dev The chains this account exists on, and its account on each —
        /// including chains whose accounts are not EVM addresses. Decided HERE
        /// (set by the holder through the ledger) and enforced there: an
        /// execution chain refuses to create the account unless the table has a
        /// row for it, and a settlement toward a chain with no row is refused at
        /// the source. This is what a zero beneficiary resolves through; it
        /// replaced a bitmask over registry slots that could only say "may
        /// exist", never "as what".
        ChainAccount[] deployedChains;
        /// @dev Per-chain dormancy verdict, one bit per asset-registry chain
        /// slot. Keeps the slot space the bitmask had.
        uint32 dormantChains;
        uint64 version;
    }

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

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

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

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

        _bump(TREE_ACCOUNTS, leaves.length);
    }

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

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

    /// @notice The leaf hash `FinalWalletFactory.accountStateLeafHash` computes.
    /// @dev Identical `abi.encode`, identical field order, identical domain.
    /// Pinned against the factory by test. `deployedChains` rides through
    /// `abi.encode` like every other field — head offset, then length and
    /// rows — so the table is committed whole and in order.
    function accountStateLeafHash(AccountStateLeaf memory leaf) public pure returns (bytes32) {
        _assertChainAccounts(leaf.deployedChains);
        return keccak256(
            abi.encode(
                DOMAIN_ACCOUNT_STATE_LEAF,
                leaf.wallet,
                leaf.liveAccess,
                leaf.liveTransaction,
                leaf.recoveryAccess,
                leaf.recoveryTransaction,
                leaf.liveKem,
                leaf.recoveryKem,
                leaf.owner,
                leaf.pqEnabled,
                leaf.frozen,
                leaf.deployedChains,
                leaf.dormantChains,
                leaf.version
            )
        );
    }

    /// @dev A well-formed table: no zero chain, no zero account, no chain twice.
    ///      Checked where the leaf is hashed so no door — quorum, writer
    ///      contract, identity projection — can publish a table a resolver
    ///      would read two ways.
    function _assertChainAccounts(ChainAccount[] memory rows) private pure {
        for (uint256 i = 0; i < rows.length; i++) {
            if (rows[i].chainRef == bytes32(0) || rows[i].account == bytes32(0)) {
                revert InvalidChainAccount(rows[i].chainRef, rows[i].account);
            }
            for (uint256 j = 0; j < i; j++) {
                if (rows[j].chainRef == rows[i].chainRef) {
                    revert InvalidChainAccount(rows[i].chainRef, rows[i].account);
                }
            }
        }
    }

    /// @notice The account `wallet`'s published table names on `chainRef`, or
    ///         zero if it has no row there.
    /// @dev A convenience over `accountStateLeafHash`'s input for readers on
    /// this chain; execution chains answer the same question from their synced
    /// record (`FinalWalletFactory.addressOn`).
    function accountOn(AccountStateLeaf memory leaf, bytes32 chainRef) public pure returns (bytes32) {
        for (uint256 i = 0; i < leaf.deployedChains.length; i++) {
            if (leaf.deployedChains[i].chainRef == chainRef) return leaf.deployedChains[i].account;
        }
        return bytes32(0);
    }

    /**
     * @notice The typed trees' write door — `FinalStateRecords` alone.
     * @dev The quorum, the nonce and the write, shared by every typed record.
     * The records contract computed the keys and hashes from the structs it
     * stores; this contract admits nobody else to trees 2, 3 and 4
     * (`setLeaves` refuses them), so the value there can never drift from
     * the commitment here.
     *
     * The digest is byte-identical to `setLeaves`' over the same keys and
     * hashes, deliberately: the typed entrypoints choose the PREIMAGE, not the
     * authorization. A member recomputes one digest whichever door the batch
     * came through, and there is no second approval shape to get wrong.
     */
    function writeTyped(
        uint8 treeId,
        bytes32[] memory keys,
        bytes32[] memory hashes,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (msg.sender != typedWriter) revert NotAuthorized(msg.sender);
        uint256 k = threshold[treeId];
        if (k == 0) revert TreeNotConfigured(treeId);

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

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

        _bump(treeId, keys.length);
    }

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

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

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

    /// @notice Every root from one round. Index by the `TREE_*` constants;
    /// index 0 is unused.
    function rootsAt(uint64 which) external view returns (bytes32[TREE_COUNT + 1] memory) {
        return _rounds[which].roots;
    }

    /// @notice One tree's root at one round.
    function rootAt(uint64 which, uint8 treeId) external view returns (bytes32) {
        _assertTree(treeId);
        return _rounds[which].roots[treeId];
    }

    /// @notice The one word that commits to every tree at one round.
    function roundRootAt(uint64 which) external view returns (bytes32) {
        return _rounds[which].roundRoot;
    }

    /**
     * @notice The `FOREST_BITS` siblings that take a tree's root at one round
     *         up to that round's root — appended to `proofFor`, they make a
     *         leaf provable against `roundRootAt(which)` by the same verifier.
     */
    function roundProofFor(uint64 which, uint8 treeId) external view returns (bytes32[] memory path) {
        _assertTree(treeId);
        if (which == 0 || which > round) revert NoRounds();
        bytes32[] memory level = _forestLeaves(_rounds[which].roots);
        path = new bytes32[](FOREST_BITS);
        uint256 idx = treeId;
        uint256 n = level.length;
        for (uint256 l = 0; l < FOREST_BITS; l++) {
            path[l] = level[idx ^ 1];
            n >>= 1;
            for (uint256 i = 0; i < n; i++) {
                level[i] = _pair(level[2 * i], level[2 * i + 1]);
            }
            idx >>= 1;
        }
    }

    /// @notice The latest round's roots, with the block it was taken at.
    function latestRound()
        external
        view
        returns (uint64 which, bytes32[TREE_COUNT + 1] memory roots, uint64 blockNumber, uint64 timestamp)
    {
        which = round;
        if (which == 0) revert NoRounds();
        Round storage r = _rounds[which];
        return (which, r.roots, r.blockNumber, r.timestamp);
    }

    /// @notice The raw leaf stored for a key, and whether it has a slot.
    function leafOf(uint8 treeId, bytes32 key) external view returns (bytes32 leaf, bool present) {
        uint256 s = _slotPlusOne[treeId][key];
        if (s == 0) return (bytes32(0), false);
        return (_leaf[treeId][s - 1], true);
    }

    /// @notice The permanent slot for a key. Reverts if it has none. The
    /// slot's top `BRANCH_BITS` are its branch.
    function slotOf(uint8 treeId, bytes32 key) public view returns (uint256) {
        uint256 s = _slotPlusOne[treeId][key];
        if (s == 0) revert UnknownKey(treeId, key);
        return s - 1;
    }

    /// @notice The key a slot was handed to, or zero if it is still free —
    /// the enumeration every branch offers: slots `branch << BRANCH_DEPTH`
    /// through `+ branchSlotsUsed(treeId, branch) - 1`.
    function keyAt(uint8 treeId, uint256 slot) external view returns (bytes32) {
        return _keyAt[treeId][slot];
    }

    /// @notice Slots handed out in one branch.
    function branchSlotsUsed(uint8 treeId, uint8 branch) external view returns (uint256) {
        return _branchSlotsUsed[treeId][branch];
    }

    /// @notice One branch's root: the level-`BRANCH_DEPTH` node at its position.
    function branchRoot(uint8 treeId, uint8 branch) external view returns (bytes32) {
        _assertTree(treeId);
        _assertBranch(branch);
        return _nodeAt(treeId, BRANCH_DEPTH, branch);
    }

    /// @notice The first `BRANCH_DEPTH` siblings of `proofFor` — a proof
    /// against the leaf's branch root rather than the tree root.
    function branchProofFor(uint8 treeId, bytes32 key) external view returns (bytes32[] memory) {
        _assertTree(treeId);
        return _path(treeId, slotOf(treeId, key), BRANCH_DEPTH);
    }

    /// @notice A configuration row's value, and whether the row exists.
    function configValue(uint8 treeId, bytes32 key) external view returns (bytes32 value, bool present) {
        present = _slotPlusOne[treeId][key] != 0;
        value = _configValue[treeId][key];
    }

    /// @notice The branch-0 key of a configuration row: a name the owning
    /// service defines, and a sub-key (a chain reference, an asset, zero).
    function configKey(bytes32 name, bytes32 sub) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_CONFIG_KEY, name, sub));
    }

    /// @notice The leaf a configuration row hashes to.
    function configLeafHash(uint8 treeId, bytes32 key, bytes32 value) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_CONFIG_LEAF, treeId, key, value));
    }

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

    /// @notice The owner-index leaf: a commitment to the ledger's ordered
    /// `walletsByOwner(owner)`.
    function ownerIndexLeafHash(address owner, address[] memory wallets) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_OWNER_INDEX_LEAF, owner, wallets));
    }

    /// @notice The tree-8 branch-3 key of one member's slot — a ring position.
    function slotKeyFor(address member, uint64 slotIndex) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_SLOT_KEY, member, slotIndex % SLOT_KEY_RING));
    }

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

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

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

    /**
     * @notice The sibling path for a key, ready for
     *         `FinalMerkle.verifyTaggedSortedProof` on any chain.
     * @dev A view, so the backend fetches a proof with one `eth_call` instead of
     * rebuilding the tree off chain. Rebuilding is where a divergence between
     * what the chain holds and what a service believes it holds would come
     * from, and this removes the second implementation entirely.
     */
    function proofFor(uint8 treeId, bytes32 key) external view returns (bytes32[] memory) {
        _assertTree(treeId);
        return _path(treeId, slotOf(treeId, key), DEPTH);
    }

    /// @notice The empty-subtree hash at a level. Level `DEPTH` is the root of
    /// a tree with nothing in it.
    function emptyRoot(uint256 level) external view returns (bytes32) {
        return _zero[level];
    }

    /// @notice The tree-1 key a wallet occupies.
    function accountKeyFor(address wallet) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ACCOUNT_KEY, wallet));
    }

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

    /// @notice The tree-8 slot key an identity occupies.
    function identityKeyFor(address account) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_IDENTITY_TREE_KEY, account));
    }

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

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

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

    /// @dev The enabled chain references `chainSource` knows, or none if it is
    ///      unset. Read through the narrow interface so this contract need not
    ///      import the registry that imports it.
    function _enabledChainRefs() private view returns (bytes32[] memory) {
        address source = chainSource;
        if (source == address(0)) return new bytes32[](0);
        return IChainSource(source).enabledChainRefs();
    }

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

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

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

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

    /// @dev `keccak256(0x01 ‖ lo ‖ hi)`, the pair sorted — the one node hash.
    function _pair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        (bytes32 lo, bytes32 hi) = a < b ? (a, b) : (b, a);
        return keccak256(abi.encodePacked(bytes1(0x01), lo, hi));
    }

    /// @dev The sibling path from a slot up `height` levels.
    function _path(uint8 treeId, uint256 idx, uint256 height) private view returns (bytes32[] memory path) {
        path = new bytes32[](height);
        for (uint256 l = 0; l < height; l++) {
            path[l] = _nodeAt(treeId, l, idx ^ 1);
            idx >>= 1;
        }
    }

    /// @dev The forest's leaves: the tree roots at their positions, the
    ///      empty tree at the rest.
    function _forestLeaves(bytes32[TREE_COUNT + 1] memory roots) private view returns (bytes32[] memory level) {
        level = new bytes32[](1 << FOREST_BITS);
        for (uint256 p = 0; p < level.length; p++) {
            level[p] = (p >= 1 && p <= TREE_COUNT) ? roots[p] : _zero[DEPTH];
        }
    }

    /// @dev Fold a power-of-two level to its root, in place.
    function _foldForest(bytes32[] memory level) private pure returns (bytes32) {
        for (uint256 n = level.length; n > 1; n >>= 1) {
            for (uint256 i = 0; i < n / 2; i++) {
                level[i] = _pair(level[2 * i], level[2 * i + 1]);
            }
        }
        return level[0];
    }

    function _set(uint8 treeId, uint8 branch, bytes32 key, bytes32 leaf) private {
        uint256 s = _slotPlusOne[treeId][key];
        uint256 idx;
        if (s == 0) {
            uint256 used = _branchSlotsUsed[treeId][branch];
            if (used >= BRANCH_CAPACITY) revert BranchFull(treeId, branch);
            idx = (uint256(branch) << BRANCH_DEPTH) | used;
            _branchSlotsUsed[treeId][branch] = used + 1;
            slotsUsed[treeId] += 1;
            _slotPlusOne[treeId][key] = idx + 1;
            _keyAt[treeId][idx] = key;
        } else {
            idx = s - 1;
            uint8 have = uint8(idx >> BRANCH_DEPTH);
            if (have != branch) revert BranchMismatch(treeId, key, have, branch);
        }

        _leaf[treeId][idx] = leaf;

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

    /// @dev Level 0 is derived from the leaf store rather than duplicated into
    /// `_node`, so there is one place a leaf lives and no way for the two to
    /// disagree. Unset positions fall through to the empty-subtree hash.
    function _nodeAt(uint8 treeId, uint256 level, uint256 index) private view returns (bytes32) {
        if (level == 0) {
            return keccak256(abi.encodePacked(bytes1(0x00), _leaf[treeId][index]));
        }
        bytes32 v = _node[treeId][level][index];
        return v == bytes32(0) ? _zero[level] : v;
    }
}

abi

[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "registry_",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      },
      {
        "name": "trees_",
        "type": "address",
        "internalType": "contract FinalStateTrees"
      }
    ],
    "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": "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": "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": "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": "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": "NoChainGranted",
    "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": "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 · 25,951 bytes

0x60806040526004361015610011575f80fd5b5f5f3560e01c806303a2ce341461368d578063178bcc931461364957806323a647341461362a57806329b57c691461360d5780632cd0ef57146135ee5780632f7c88f5146135cb5780632fecac301461359357806333a7c02f1461356e57806334c209d91461257d57806338eb0788146125615780634217f75714612512578063499326fb146124ea57806351510e4a146124af5780635575e6831461247057806357f736951461242157806358137dff146123e45780635d419257146123c85780635e732005146123a857806365e84331146122c25780636b75b6a1146120405780636edb17dc14611ffd578063795e8d4a14611f455780637b10399914611f005780638086b8ba14611e9857806381c0dd5414611e7a57806382a4484714611e3f578063840b625b14611dff5780638af1bee614611db05780638f48dc3f14611d52578063909473a914611d1757806392880ad014611cfb57806394bc4e961461197557806396f191d414611129578063a7ce270314611102578063b19f4805146110c7578063b580a787146110a1578063bbc7ba351461105f578063bd252f1314611020578063bf6bff17146104ba578063c067611114610493578063c06ac6a31461034a578063c2eeb6701461032b578063e3b5908a146102f7578063f487885e146102da578063f698da25146102b7578063fc72c4ce1461029b5763fd6bc5471461021f575f80fd5b34610298576020366003190112610298576102386136ac565b90610241613c54565b506001600160a01b0382168082526003602052604082205460ff16156102855761028161026d84614e29565b604051918291602083526020830190613996565b0390f35b633131bf7960e21b825260045260249150fd5b80fd5b50346102985780600319360112610298576020604051600f8152f35b503461029857806003193601126102985760206102d2614a76565b604051908152f35b503461029857806003193601126102985760209054604051908152f35b50346102985760203660031901126102985761016061031c6103176136ac565b614a0a565b6103296040518092613966565bf35b50346102985780600319360112610298576040516236ee808152602090f35b5034610298576020366003190112610298576103646136ac565b9061036d6141da565b5061016060405161037e8282613b63565b3690376001600160a01b0382168082526003602052604082205490919060ff161561048157610435926104728284610281945260036020526040812085825260046020526104646104586040842097808552600560205261044961042861042261041c61041661041060406103f5818d209e614a0a565b9b8881526008602052818120988152600a60205220996142a2565b9d613b84565b9a613b84565b93613d74565b95613e2f565b976040519b8c809c6137de565b6105606103808c01526105608b01906136d6565b908982036103a08b01526136d6565b926103c0880190613966565b858203610520870152613742565b90838203610540850152613775565b602491633131bf7960e21b8252600452fd5b503461029857806003193601126102985760206001600160401b0360025416604051908152f35b5034610298576060366003190112610298576004356001600160401b03811161101c576104eb903690600401613712565b6104f69291926137b4565b6044356001600160401b03811161086257610515903690600401613712565b9091600154801561100d57600254936001600160401b038516936040518760608201876020840152604080840152526080810160808960051b830101908b908b8d6101de1990360301905b8c8110610ec05750505050926106459592826105926001600160401b0399979461063f9703601f198101835282613b63565b6020815191012060405160208101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527f1d2159d826062d6d8bb06b1f7449d53275f95106855af24febedc5e55574135860808301528a871660a083015260c082015260c0815261061260e082613b63565b519020908b54927f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d54061516a565b50613bff565b16906001600160401b0319161760025561065e81613c3d565b9261066c6040519485613b63565b818452601f1961067b83613c3d565b01835b818110610ea957505061069082613ea1565b61069983613ea1565b906236ee80855b85811061086657508592919050867f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03163b1561086257836106fd9160405180938192632728f27160e21b835260048301613d15565b0381837f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03165af1908115610857578491610842575b50507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03163b156108335760405163abf1570d60e01b81529183918391829161078e9190600484016147fd565b0381837f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03165af180156108375761081e575b50506107d382613ea1565b91835b8181106107ea57846107e78561555f565b80f35b806108046101006107fe60019486886149e7565b01613bd7565b61080e8287613d01565b90838060a01b03169052016107d6565b8161082891613b63565b6108335782846107c8565b8280fd5b6040513d84823e3d90fd5b8161084c91613b63565b61083357828761073a565b6040513d86823e3d90fd5b8380fd5b93909594916108768584846149e7565b9761087f613c54565b506001600160a01b036108918a613bd7565b1687526003602052604087209788549060ff8216610e85576101608b016001600160401b036108bf82613beb565b16610e7657506305265c00915b816001600160401b038416108015610e60575b610e44576108f16101408d018d61476f565b905015610e205760e08c0135908115610dfc579060016101a094939261098f8f8e6101c082019061ffff61092483615762565b1615159050610deb575061095661093f610180830183614681565b905061ffff61094f8b8501615762565b911661608d565b9788915b61098961096682613bd7565b91610981610978610180830183614681565b95909201615762565b933691614dc1565b90615b9c565b60ff1916178c5560208d013560018d015560408d013560028d015560608d013560038d015560808d013560048d015560058c015560a08c013560068c015560c08c013560078c015560088b01600161ffff198254161790556109f46101008d01613bd7565b60098c0180546001600160a01b0319166001600160a01b0392909216919091179055610a236101208d01615771565b60098c01805460ff60a01b191691151560a01b60ff60a01b16919091179055895b610a526101408e018e61476f565b9050811015610aa857600190610aa28e610a6b81613bd7565b906020610a9a85610a87610a8d82610a8761014088018861476f565b906147a4565b359461014081019061476f565b0135916153dc565b01610a44565b50949793959899906001600160401b03600a610b3594939d959d600160b01b8360b01b196009830154161760098201550191166001600160401b0319825416178155610b16610afa6101a08601615762565b825461ffff60401b191660409190911b61ffff60401b16178255565b805461ffff60501b191660509290921b61ffff60501b16919091179055565b610b43610180820182614681565b906001600160a01b03610b5584613bd7565b168a52600460205260408a20906001600160401b038311610dd757610b7a83836146e6565b908a5260208a208a5b838110610dbc5750505050610c2981610ba6610ba1610c2e94613bd7565b6147b4565b610bc5610bb282613bd7565b610bbf6101008401613bd7565b90615509565b610bce81613bd7565b610bdb6101008301613bd7565b7f568403fd429f133b4cc18a945d220c328c59a445a8122f240f1d74fd55fb69376020610c0b6101208601615771565b60405190151581526001600160a01b039384169490931692a3613bd7565b614e29565b610c38828a613d01565b52610c438189613d01565b50610c57610c528288886149e7565b613bd7565b6040516382edfbd960e01b81526001600160a01b0391821660048201529190602090839060249082907f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8165afa8015610db1578890610d7b575b60019250610cbf8286613d01565b52610ccb8188886149e7565b60405160208101906020830135825260408301356040820152606083013560608201526080830135608082015260a083013560a082015260c083013560c082015260c08152610d1b60e082613b63565b5190206040519060e060208301937fcc25d3fea88291f95ddfb5590a6b760f02245a0e4ca7c0b69285c6cd26543afd855201356040830152606082015260608152610d67608082613b63565b519020610d748287613d01565b52016106a0565b506020823d8211610da9575b81610d9460209383613b63565b81010312610da55760019151610cb1565b5f80fd5b3d9150610d87565b6040513d8a823e3d90fd5b6001906020610dca85613bd7565b9401938184015501610b83565b634e487b7160e01b8b52604160045260248bfd5b610df490615762565b97889161095a565b60248b610e088f613bd7565b6316efda7d60e21b82526001600160a01b0316600452fd5b60248a610e2c8e613bd7565b633aa293db60e11b82526001600160a01b0316600452fd5b6310b0f87560e11b8a526001600160401b03831660045260248afd5b50639a7ec8006001600160401b038416116108df565b610e7f90613beb565b916108cc565b602489610e918d613bd7565b633b49009360e11b82526001600160a01b0316600452fd5b602090610eb4613c54565b8282890101520161067e565b90919293607f19868203018452843583811215611009578f01906001600160a01b03610eeb836136c2565b1681526020820135602082015260408201356040820152606082013560608201526080820135608082015260a082013560a082015260c082013560c082015260e082013560e082015260018060a01b03610f4861010084016136c2565b16610100820152610120820135801515809103611005576001928260209392610120859401526101c061ffff610ff582610fd9610fa0610f8c610140890189614614565b6101e06101408a01526101e0890191614648565b6001600160401b03610fb56101608a016137ca565b16610160880152610fca6101808901896145a1565b908883036101808a01526145d5565b9583610fe86101a083016149d8565b166101a0870152016149d8565b1691015296019401929101610560565b8f80fd5b8e80fd5b631d087e6160e21b8652600486fd5b5080fd5b50346102985760203660031901126102985760043560ff811680910361101c576040826001600160401b03926020945260078452205416604051908152f35b50346102985760203660031901126102985760209061ffff906008906040906001600160a01b0361108e6136ac565b1681526003855220015416604051908152f35b5034610298576040366003190112610298576107e76110be6136ac565b60243590614833565b503461029857806003193601126102985760206040517f3154287b2470d9f05573ebd18908404f28e212134930a0f6df0005bc02e1c5158152f35b503461029857806003193601126102985760206001600160401b03600e5416604051908152f35b5034610298576060366003190112610298576001600160401b03600435116102985761040060043536036003190112610298576111646137b4565b6044356001600160401b03811161083357611183903690600401613712565b91600e5460ff8160401c166119665760015493841561100d57604080516001600160401b038416602082015280820191909152926001600160a01b036111cc60048035016136c2565b166060850152602460043501956101e0876080870137610204600435019561123b6112136111ff896004356004016145a1565b6104006102608b01526104608a01916145d5565b611228610224600435016004356004016145a1565b898303605f19016102808b0152906145d5565b93600435610244018a6102a089015b600b82106119405750505061126a6103a4600435016004356004016145a1565b888703605f19016104008a015280875290956001600160fb1b03821161193c576113a5966112ce9260051b8091602084013760206112b36103c460043501600435600401614614565b939092018b81038201605f19016104208d0152019190614648565b966112f1816103e46004350135998a61044083015203601f198101835282613b63565b6020815191012060405160208101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527f8ff45d05bf7eaecf1e3489de0ad3d898e5ab54735cd0ca116506a6c8a7438c9560808301526001600160401b03871660a083015260c082015260c0815261137860e082613b63565b519020908a54927f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d54061516a565b506001600160401b036113b9818316613bff565b16906001600160401b03191617600e556113d7600435600401613bd7565b6001600160a01b0381169290919083156118db578386526003602052604086209460ff86541661192857865b600f811061191057505060ff855416158015611904575b80156118ef575b6118db5761143490600435600401614681565b90848752600460205260408720906001600160401b0383116118ac5761145a83836146e6565b90875260208720875b8381106118c0575050505061148361022460043501600435600401614681565b90848752600560205260408720906001600160401b0383116118ac576114a983836146e6565b90875260208720875b8381106118915750505050845b60ff8116600b8110156115215760ff91816114ea6114e56001946102446004350161472a565b613beb565b90878a52600660205260408a20905f526020526001600160401b0360405f2091166001600160401b031982541617905501166114bf565b50509290845b61153c6103a460043501600435600401614681565b90508110156115c757806115676001926115616103a460043501600435600401614681565b90613f70565b3585885260096020526040882081895260205260ff6040892054166115c1576115bb90868952600960205260408920818a52602052604089208460ff1982541617905586895260086020526040892061473b565b01611527565b506115bb565b50838593845b856115e36103c46004350160043560040161476f565b905082101561163757508061163161160c600193610a876103c46004350160043560040161476f565b35602061162884610a876103c46004350160043560040161476f565b013590876153dc565b016115cd565b80949150600986611647846147b4565b018054909290611660906001600160a01b031682615509565b8254847fcfe82510d1c464fb22d59e8531313b14d3894bb1dfdec9de06b77b65afa87a766020604051936001600160401b038160b01c16855260018060a01b031693a36116b46116ae613cb4565b91614e29565b6116bd82613cf4565b526116c781613cf4565b507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b031690813b1561083357826117199160405180938192632728f27160e21b835260048301613d15565b038183865af1908115611886578391611871575b505060409384519061173f8683613b63565b60018252601f198601968736602085013786519161175d8884613b63565b60018352883660208501378751906382edfbd960e01b82526004820152602081602481885afa908115611867578691611832575b5061179b84613cf4565b526117a582613cf4565b52823b15610862576117cf92849283885180968195829463abf1570d60e01b8452600484016147fd565b03925af1801561182857611813575b50506107e7926117f083519384613b63565b60018352366020840137546001600160a01b031661180d82613cf4565b5261555f565b8161181d91613b63565b6108625783856117de565b84513d84823e3d90fd5b9550506020853d60201161185f575b8161184e60209383613b63565b81010312610da5578894518a611791565b3d9150611841565b88513d88823e3d90fd5b8161187b91613b63565b61101c57818761172d565b6040513d85823e3d90fd5b600190602061189f85613bd7565b94019381840155016114b2565b634e487b7160e01b88526041600452602488fd5b60019060206118ce85613bd7565b9401938184015501611463565b6308e1960960e41b86526004849052602486fd5b5060098501546001600160a01b031615611421565b5060058501541561141a565b8061191d600192846141c9565b358189015501611403565b633b49009360e11b87526004859052602487fd5b8b80fd5b6020806001926001600160401b03611957876137ca565b1681520193019101909161124a565b630eace7db60e21b8552600485fd5b5034610298576080366003190112610298576004356024356044356001600160401b038116809103610862576064356001600160401b038111611cf7576119c0903690600401613712565b6040516328305db160e21b81527f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b0316939290602081600481885afa908115610db1578891611cc8575b508015611c62575b611adf575b50505081611a62575b50816040917f4bd8fc893261db17fa12018122920c62b43b0dc5e8b6b08723911b819f53b1189385558060015582519182526020820152a180f35b60206024916040519283809263342f616360e01b82528760048301525afa908115610857578491611aad575b5081811015611a2757633770da3360e11b845260045260245250604490fd5b90506020813d602011611ad7575b81611ac860209383613b63565b81010312610da557515f611a8e565b3d9150611abb565b604051602081019087825286604082015260408152611aff606082613b63565b519020843b15611c5e57908288949392604051946322f3f44760e11b865260848601917f3154287b2470d9f05573ebd18908404f28e212134930a0f6df0005bc02e1c515600488015260248701526044860152608060648601525260a4830160a060048460051b8601010192828690607e19813603015b838310611bb557505050505050818082859350038183875af1801561083757611ba0575b80611a1e565b81611baa91613b63565b61086257835f611b9a565b919395909294969750609f1960031989830301018652863582811215611c5a576001916020918291611c47918701906001600160a01b03611bf5836136c2565b16815260ff611c05858401613958565b1684820152611c39611c2e611c1d6040850185613f1f565b608060408601526080850191613f50565b926060810190613f1f565b916060818503910152613f50565b98019601930190918b9796959492611b76565b8c80fd5b8780fd5b5060405163f5778b0360e01b8152602081600481885afa908115610db1578891611c99575b506001600160a01b0316331415611a19565b611cbb915060203d602011611cc1575b611cb38183613b63565b810190613f00565b5f611c87565b503d611ca9565b611cea915060203d602011611cf0575b611ce28183613b63565b810190613ee8565b5f611a11565b503d611cd8565b8480fd5b5034610298578060031936011261029857602060405160058152f35b503461029857806003193601126102985760206040517f720d0d938a4dc8953abf8fbbdb7f8551022f52b67ff459f9772a21aa44137c0f8152f35b503461029857604036600319011261029857611d6c6136ac565b906024359160ff8316830361101c5760ff9160409160018060a01b0316815260066020522091165f5260205260206001600160401b0360405f205416604051908152f35b50346102985760203660031901126102985761028190611deb906040906001600160a01b03611ddd6136ac565b168152600560205220613b84565b6040519182916020835260208301906136d6565b503461029857602036600319011261029857600435906001600160401b0382116102985760a060031983360301126102985760206102d28360040161449d565b50346102985760203660031901126102985761028190611deb906040906001600160a01b03611e6c6136ac565b168152600460205220613b84565b50346102985780600319360112610298576020600154604051908152f35b503461029857602036600319011261029857611eb26136ac565b611eba6141da565b506001600160a01b03168082526003602052604082205490919060ff16156104815760408161038093611ef393526003602052206142a2565b61032960405180926137de565b50346102985780600319360112610298576040517f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b03168152602090f35b503461029857602036600319011261029857611f5f6136ac565b6040516101e09291611f718483613b63565b833683376001600160a01b03168083526003602052604083205460ff1615611feb578252600360205260408220825b600f8110611fd357505060405191825b600f8210611fbd57505050f35b6020806001928551815201930191019091611fb0565b80600191830154611fe482866141c9565b5201611fa0565b633131bf7960e21b8352600452602482fd5b5034610298576040366003190112610298576120176136ac565b602435906001600160401b0382116108335761203a6107e7923690600401613712565b91613f8e565b503461029857604036600319011261029857600435906001600160401b038216809203610298576024356001600160401b03811161101c57612086903690600401613712565b6040516328305db160e21b81527f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b03169290602081600481875afa9081156122985785916122a3575b508015612242575b612123575b83600160401b68ff000000000000000019600e541617600e557f9488b39bb791360e870cc4e5329751ad18c820062978074594e27a8fdf4944fc8180a180f35b60405160208101903082526020815261213d604082613b63565b519020833b15611cf7578290604051966322f3f44760e11b885260848801917fa6ed41d5df38aac26edd8244ab692c2c45399b5e8d5e222559b3ef5e6703b85b60048a015260248901526044880152608060648801525260a4850160a060048460051b8801010192828690607e19813603015b8383106121f357505050505050838092818580978582965003925af18015610837576121de575b80806120e3565b816121e891613b63565b61029857805f6121d7565b909192939495609f196003198b83030101865286358281121561223e576001916020918291612230918701906001600160a01b03611bf5836136c2565b9801960194930191906121b0565b8980fd5b5060405163f5778b0360e01b8152602081600481875afa908115612298578591612279575b506001600160a01b03163314156120de565b612292915060203d602011611cc157611cb38183613b63565b5f612267565b6040513d87823e3d90fd5b6122bc915060203d602011611cf057611ce28183613b63565b5f6120d6565b503461029857604036600319011261029857600435602435908091600b548082116123a0575b50809111612398575b6123036122fe8383613e94565b613ea1565b9180600b54905b8381106123275760405160208082528190610281908201886136d6565b8181101561238457600b86527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db981015460019190600388901b1c6001600160a01b031661237d6123778684613e94565b88613d01565b520161230a565b634e487b7160e01b86526032600452602486fd5b9050806122f1565b90505f6122e8565b503461029857806003193601126102985760206040516446494e414c8152f35b50346102985780600319360112610298576020604051600b8152f35b5034610298576020366003190112610298576020906005906040906001600160a01b0361240f6136ac565b16815260038452200154604051908152f35b5034610298576020366003190112610298576102819061245c906040906001600160a01b0361244e6136ac565b168152600a60205220613e2f565b604051918291602083526020830190613775565b50346102985760203660031901126102985760209060ff906040906001600160a01b0361249b6136ac565b168152600384522054166040519015158152f35b503461029857806003193601126102985760206040517fa6ed41d5df38aac26edd8244ab692c2c45399b5e8d5e222559b3ef5e6703b85b8152f35b50346102985760403660031901126102985760206102d26125096136ac565b60243590613dd5565b5034610298576020366003190112610298576102819061254d906040906001600160a01b0361253f6136ac565b168152600860205220613d74565b604051918291602083526020830190613742565b5034610298578060031936011261029857602060405160028152f35b5034610da5576060366003190112610da557600435906001600160401b038211610da557816004019060a06003198436030112610da557602435906001600160401b038211610da55760406003198336030112610da5576044356001600160401b038111610da5576125f3903690600401613712565b90949093906001600160a01b0361260983613bd7565b165f52600360205260405f2060ff81541615613545576001600160a01b0361263084613bd7565b165f52600660205260405f2094602483013595600b871015610da55760ff87165f526020526001600160401b0360405f205416966044840198886001600160401b0361267b8c613beb565b160361351c576001600160401b039899506064850161269981613beb565b8a42169a8b911611156134f4576001600160401b036126b88b92613beb565b16036001600160401b038111612bb55763240c8400906001600160401b03168181116134df575050906126fa92916126ef8761449d565b926004018786614b28565b90965f938761293e57505061271c9061ffff600a84015460401c169088615a9b565b600981019081549060ff8260a81c166129075760ff60a81b19909116600160a81b17909155612791906009905b019661278c6127646001600160401b038a5460b01c16613bff565b895467ffffffffffffffff60b01b191660b09190911b67ffffffffffffffff60b01b16178955565b613bff565b906001600160a01b036127a384613bd7565b165f52600660205260405f2090610da55760ff85165f526020526001600160401b0360405f2091166001600160401b03198254161790556127e2613cb4565b6127ee610c2983613bd7565b6127f782613cf4565b5261280181613cf4565b507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b0316803b15610da557604051632728f27160e21b8152915f9183918290849082906128589060048301613d15565b03925af180156128fc576128e5575b506128796001600160401b0391613bd7565b945460b01c16926040519160048210156128d1575081526020818101849052936001600160a01b0316907f564e8f9360b1445a420f5373633054ebbd6fed89fbb8c83cf1f33ee7269f573890604090a3604051908152f35b634e487b7160e01b81526021600452602490fd5b6128f29192505f90613b63565b5f90612879612867565b6040513d5f823e3d90fd5b60405163517a083560e11b815260206004820152600e60248201526d30b63932b0b23c90333937bd32b760911b6044820152606490fd5b5f945090600188036129b25750505061295686615b50565b600981019081549060ff8260a81c161561297f576127919260099260ff60a81b19169055612749565b60405163517a083560e11b815260206004820152600a6024820152693737ba10333937bd32b760b11b6044820152606490fd5b919350915f93600288145f14612b2357506129cc88615b50565b600a8101805460ff8160601c16612add576129ec6084606096018861441a565b9080969181010312610da5578435936040602087013596013585158015612ad5575b8015612acd575b612a7b5761279196600996612a6b94600160601b9060ff60601b1916178655600b870155600c860155600d85015582908154906001600160401b0360701b9060701b16906001600160401b0360701b1916179055565b805460ff60681b19169055612749565b60405163517a083560e11b8152602060048201526024808201527f726f746174696f6e206e6565647320616c6c20746872656520636f6d6d69746d604482015263656e747360e01b6064820152608490fd5b508015612a15565b508615612a0e565b60405163517a083560e11b815260206004820152601860248201527f726f746174696f6e20616c72656164792070656e64696e6700000000000000006044820152606490fd5b5f9450919260038803612c625750600a810191612b4b83549161ffff8360501c16908b615a9b565b60ff8160601c1615612c265760681c60ff166002811015612bc95760010160ff8111612bb557825468ffffffffffffffffff60681b191660689190911b60ff60681b161760709390931b67ffffffffffffffff60701b16929092179055612791906009905b612749565b634e487b7160e01b5f52601160045260245ffd5b60405163517a083560e11b815260206004820152602e60248201527f63616e63656c20627564676574206578686175737465643b2074686520726f7460448201526d6174696f6e2070726f636565647360901b6064820152608490fd5b60405163517a083560e11b81526020600482015260136024820152726e6f20726f746174696f6e2070656e64696e6760681b6044820152606490fd5b935091905f93600488145f14612d44575050600a820190815460ff8160601c1615612c2657612ca1816001600160401b03808094169160701c16613c1d565b16808210612d2f575050816009915f6003612791950180546001850155816004850180546002870155816007870193845460068901556008880161ffff612cea81835416614e15565b825461ffff19169116179055600b88018054909155600c88018054909355600d88018054909555865460ff60601b19168755555555805460ff60681b19169055612749565b63be20035760e01b5f5260045260245260445ffd5b5f9450919060058803612f60575050612d5c87615b50565b60ff600a83015460601c16612f0857600e82019060ff825416612ec2576084612d8691018561441a565b8101606082820312610da55781356001600160401b038111610da55782019080601f83011215610da557816020612dbf93359101614dc1565b90612dd86040612dd1602084016149d8565b92016149d8565b91612ded838383612de88b613bd7565b615b9c565b6001600160a01b03612dfe88613bd7565b165f52600560205260405f208151916001600160401b038311612eae57602090612e2884846146e6565b01905f5260205f205f5b838110612e915750505050916001612791949264ffff000000600995845462ffff0060ff199260081b16906cffffffffffffffffffffffff00191617169160181b16176cffffffffffffffff00000000004260281b1617179055612749565b82516001600160a01b031681830155602090920191600101612e32565b634e487b7160e01b5f52604160045260245ffd5b60405163517a083560e11b815260206004820152601f60248201527f677561726469616e206368616e676520616c72656164792070656e64696e67006044820152606490fd5b60405163517a083560e11b815260206004820152602960248201527f677561726469616e207365742070696e6e656420627920612070656e64696e67604482015268103937ba30ba34b7b760b91b6064820152608490fd5b5f9450919060068803613012575050612f869061ffff600a84015460501c169088615a9b565b600e81019081549060ff821615612fcc5760ff19909116909155612791906009906001600160a01b03612fb886613bd7565b165f526005602052612bb060405f206146b6565b60405163517a083560e11b815260206004820152601a60248201527f6e6f20677561726469616e206368616e67652070656e64696e670000000000006044820152606490fd5b5f94509150600787036131095750600e82019081549060ff821615612fcc57600a840191825460ff8160601c16612f08576001600160401b0391828061305d93169160281c16613c1d565b16808210612d2f575050916009916130f06127919460018060a01b0361308289613bd7565b165f9081526005602052604090206130b6906001600160a01b036130a58b613bd7565b165f52600460205260405f20614d5d565b8254815461ffff60401b191660389190911b61ffff60401b161781558254815461ffff60501b191660389190911b61ffff60501b16179055565b805460ff191690556001600160a01b03612fb886613bd7565b5f9350919050600886036133045760048710156132f057600287036132a057600981019081549260ff8460a81c166132475761314a6084602092018761441a565b9080929181010312610da557356001600160a01b0381169390849003610da557831561320d5782546001600160a01b0319166001600160a01b0385811691909117909355909116906131a48261319f87613bd7565b6159b2565b6131b6836131b187613bd7565b615509565b604051916131c5606084613b63565b6002835260403660208501376131da83613cf4565b528151600110156131f957612bb082612791946040600995015261555f565b634e487b7160e01b5f52603260045260245ffd5b60405163517a083560e11b81526020600482015260116024820152701b995dd3dddb995c881c995c5d5a5c9959607a1b6044820152606490fd5b60405163517a083560e11b815260206004820152602a60248201527f612066726f7a656e206163636f756e742063616e6e6f74207472616e736665726044820152690206f776e6572736869760b41b6064820152608490fd5b60405163765a8bc960e11b815260206004820152602160248201527f6f776e6572207472616e73666572206e6565647320746865206c697665206b656044820152607960f81b6064820152608490fd5b634e487b7160e01b5f52602160045260245ffd5b5f925090600a860361340c5760048710156132f057600287036133c75760ff600983015460a81c1661336a5761333f6084604092018561441a565b9080929181010312610da55761279191612bb08260206009940135903561336588613bd7565b6153dc565b60405163517a083560e11b815260206004820152602e60248201527f612066726f7a656e206163636f756e742063616e6e6f74206368616e6765206960448201526d747320636861696e207461626c6560901b6064820152608490fd5b606460405163765a8bc960e11b815260206004820152602060248201527f636861696e206163636f756e74206e6565647320746865206c697665206b65796044820152fd5b5060048610156132f0576002860361349957600981019081549060ff8260a01c166134665761279192740100000000000000000000000000000046494e414c6009936affffffffffffffffffffff60a81b16179055612749565b60405163517a083560e11b815260206004820152600a602482015269616c726561647920505160b01b6044820152606490fd5b60405163765a8bc960e11b815260206004820152601f60248201527f5051206d6967726174696f6e206e6565647320746865206c697665206b6579006044820152606490fd5b630181da8960e31b5f5260045260245260445ffd5b6001600160401b036135068b92613beb565b6365d3805160e11b5f521660045260245260445ffd5b6001600160401b038961352e8c613beb565b90631b3b434760e21b5f526004521660245260445ffd5b61354e83613bd7565b633131bf7960e21b5f9081526001600160a01b0391909116600452602490fd5b34610da5575f366003190112610da557602060ff600e5460401c166040519015158152f35b34610da5576020366003190112610da5576001600160a01b036135b46136ac565b165f52600c602052610281611deb60405f20613b84565b34610da5576020366003190112610da5576135ec6135e76136ac565b613a52565b005b34610da5575f366003190112610da557604051639a7ec8008152602090f35b34610da5575f366003190112610da5576020600b54604051908152f35b34610da5575f366003190112610da5576040516305265c008152602090f35b34610da5575f366003190112610da5576040517f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03168152602090f35b34610da5575f366003190112610da55760405163240c84008152602090f35b600435906001600160a01b0382168203610da557565b35906001600160a01b0382168203610da557565b90602080835192838152019201905f5b8181106136f35750505090565b82516001600160a01b03168452602093840193909201916001016136e6565b9181601f84011215610da5578235916001600160401b038311610da5576020808501948460051b010111610da557565b90602080835192838152019201905f5b81811061375f5750505090565b8251845260209384019390920191600101613752565b90602080835192838152019201905f5b8181106137925750505090565b8251805185526020908101518186015260409094019390920191600101613785565b602435906001600160401b0382168203610da557565b35906001600160401b0382168203610da557565b6001600160401b0361036080928051151585526020810151602086015260408101516040860152606081015160608601526080810151608086015260a081015160a086015260c081015160c086015260e081015160e086015261ffff6101008201511661010086015263ffffffff61012082015116610120860152826101408201511661014086015260018060a01b036101608201511661016086015261018081015115156101808601526101a081015115156101a0860152826101c0820151166101c0860152826101e0820151166101e086015261ffff6102008201511661020086015261ffff61022082015116610220860152610240810151151561024086015260ff6102608201511661026086015282610280820151166102808601526102a08101516102a08601526102c08101516102c08601526102e08101516102e0860152610300810151151561030086015261ffff6103208201511661032086015261ffff61034082015116610340860152015116910152565b359060ff82168203610da557565b905f905b600b821061397757505050565b6020806001926001600160401b0386511681520193019101909161396a565b9060018060a01b0382511681526020820151602082015260408201516040820152606082015160608201526080820151608082015260a082015160a082015260c082015160c082015260018060a01b0360e08301511660e0820152610100820151151561010082015261012082015115156101208201526101806001600160401b0381613a366101408601516101a06101408701526101a0860190613775565b9463ffffffff6101608201511661016086015201511691015290565b6001600160a01b03165f818152600360205260409020805460ff1615613afd5760080180546001600160401b0342166001600160401b038260301c161015613af8577fbc7135101c9542d8a73b8e35f3dc1d780bd3932fb2c6f7f7b40b27b32d5b0eb8916020916dffffffffffffffff0000000000004260301b16906dffffffffffffffff0000000000001916178091556001600160401b036040519160301c168152a2565b505050565b50633131bf7960e21b5f5260045260245ffd5b6101a081019081106001600160401b03821117612eae57604052565b604081019081106001600160401b03821117612eae57604052565b61038081019081106001600160401b03821117612eae57604052565b90601f801991011681019081106001600160401b03821117612eae57604052565b90604051918281549182825260208201905f5260205f20925f5b818110613bb5575050613bb392500383613b63565b565b84546001600160a01b0316835260019485019487945060209093019201613b9e565b356001600160a01b0381168103610da55790565b356001600160401b0381168103610da55790565b6001600160401b036001911601906001600160401b038211612bb557565b906001600160401b03809116911601906001600160401b038211612bb557565b6001600160401b038111612eae5760051b60200190565b60405190613c6182613b10565b5f610180838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152826101208201526060610140820152826101608201520152565b60408051909190613cc58382613b63565b6001815291601f1901825f5b828110613cdd57505050565b602090613ce8613c54565b82828501015201613cd1565b8051156131f95760200190565b80518210156131f95760209160051b010190565b602081016020825282518091526040820191602060408360051b8301019401925f915b838310613d4757505050505090565b9091929394602080613d65600193603f198682030187528951613996565b97019301930191939290613d38565b90604051918281549182825260208201905f5260205f20925f5b818110613da3575050613bb392500383613b63565b8454835260019485019487945060209093019201613d8e565b80548210156131f9575f5260205f209060011b01905f90565b6001600160a01b03165f908152600a6020526040812080549290915b838110613e0057505050505f90565b81613e0b8285613dbc565b505414613e1a57600101613df1565b905060019250613e2991613dbc565b50015490565b908154613e3b81613c3d565b92613e496040519485613b63565b81845260208401905f5260205f205f915b838310613e675750505050565b60026020600192604051613e7a81613b2c565b855481528486015483820152815201920192019190613e5a565b91908203918211612bb557565b90613eab82613c3d565b613eb86040519182613b63565b8281528092613ec9601f1991613c3d565b0190602036910137565b80548210156131f9575f5260205f2001905f90565b90816020910312610da557518015158103610da55790565b90816020910312610da557516001600160a01b0381168103610da55790565b9035601e1982360301811215610da55701602081359101916001600160401b038211610da5578136038313610da557565b908060209392818452848401375f828201840152601f01601f1916010190565b91908110156131f95760051b0190565b3560ff81168103610da55790565b6001600160a01b0381165f81815260036020526040902080549194939160ff16156141b657600881018054601081901c63ffffffff169586945f915b8083106141245750505063ffffffff841695861461411b5765ffffffff000060099460101b169065ffffffff0000191617905501916140406140186001600160401b03855460b01c16613bff565b845467ffffffffffffffff60b01b191660b09190911b67ffffffffffffffff60b01b16178455565b61405161404b613cb4565b92614e29565b61405a83613cf4565b5261406482613cf4565b507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b0316803b15610da557604051632728f27160e21b8152925f9184918290849082906140bb9060048301613d15565b03925af180156128fc577f1b02e1ac9b990e23331c3ec991471a8235ccf6f92c91382bf942bd6d047a5358936040936001600160401b039261410b575b505460b01c1682519182526020820152a2565b5f61411591613b63565b5f6140f8565b50505050505050565b90919560ff61413c614137898587613f70565b613f80565b1690815f52600760205263ffffffff60016001600160401b0360405f205416931b16908215158061418a575b600193501561417c57175b96019190613fca565b63ffffffff91191616614173565b506001600160401b038660301c16928301809311612bb5576001926001600160401b0342161015614168565b84633131bf7960e21b5f5260045260245ffd5b90600f8110156131f95760051b0190565b604051906141e782613b47565b5f610360838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e08201528261020082015282610220820152826102408201528261026082015282610280820152826102a0820152826102c0820152826102e08201528261030082015282610320820152826103408201520152565b906040516142af81613b47565b6103606001600160401b03600e839560ff8154161515855260018101546020860152600281015460408601526003810154606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e086015282600882015461ffff811661010088015263ffffffff8160101c1661012088015260301c1661014086015282600982015460018060a01b03811661016088015260ff8160a01c16151561018088015260ff8160a81c1615156101a088015260b01c166101c086015282600a8201548181166101e088015261ffff8160401c1661020088015261ffff8160501c1661022088015260ff8160601c16151561024088015260ff8160681c1661026088015260701c16610280860152600b8101546102a0860152600c8101546102c0860152600d8101546102e0860152015460ff8116151561030085015261ffff8160081c1661032085015261ffff8160181c1661034085015260281c16910152565b903590601e1981360301821215610da557018035906001600160401b038211610da557602001918136038313610da557565b6001600160401b038111612eae57601f01601f191660200190565b9291926144738261444c565b916144816040519384613b63565b829481845281830111610da5578281602093845f960137010152565b6144a681613bd7565b906020810135600b811015610da5576144c66001600160401b0391614f0a565b6020815191012091816144e66144df608084018461441a565b3691614467565b602081519101209161450660606144ff60408401613beb565b9201613beb565b926040519560208701977fb898ee3e5af9db61371c27766584da3118def5d64a9cc094018ecc9465856c75895260018060a01b03166040880152606087015260808601521660a08401521660c082015260c0815261456560e082613b63565b519020614570614a76565b9060405190602082019261190160f01b8452602283015260428201526042815261459b606282613b63565b51902090565b9035601e1982360301811215610da55701602081359101916001600160401b038211610da5578160051b36038313610da557565b916020908281520191905f5b8181106145ee5750505090565b909192602080600192838060a01b03614606886136c2565b1681520194019291016145e1565b9035601e1982360301811215610da55701602081359101916001600160401b038211610da5578160061b36038313610da557565b916020908281520191905f5b8181106146615750505090565b823584526020808401359085015260409384019390920191600101614654565b903590601e1981360301821215610da557018035906001600160401b038211610da557602001918160051b36038313610da557565b8054905f8155816146c5575050565b5f5260205f205f5b8281106146d957505050565b5f828201556001016146cd565b90600160401b8111612eae5781549181815582821061470457505050565b5f528060205f20019103905f5b82811061471d57505050565b5f82820155600101614711565b90600b8110156131f95760051b0190565b8054600160401b811015612eae5761475891600182018155613ed3565b819291549060031b91821b915f19901b1916179055565b903590601e1981360301821215610da557018035906001600160401b038211610da557602001918160061b36038313610da557565b91908110156131f95760061b0190565b600b5490600160401b821015612eae576147d9826001613bb39401600b55600b613ed3565b81546001600160a01b0393841660039290921b91821b9390911b1916919091179055565b9091614822614830936008845260016020850152608060408501526080840190613742565b916060818403910152613742565b90565b604051630827e01160e21b81527f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b031690602081600481855afa9081156128fc575f916149a4575b5060405163b3c2628360e01b815233600482015260248101919091529060209082908180604481015b03915afa9081156128fc575f91614985575b5015614972576001600160a01b03165f8181526003602052604090205460ff161561496057805f52600960205260405f20825f5260205260ff60405f20541661495c57805f52600960205260405f20825f5260205260405f20600160ff19825416179055805f5260086020526149368260405f2061473b565b7f01dd03ab26e4cf04b9c9b50f382960c7f90d82ca1dd926bbc6a375c9f7c364ee5f80a3565b5050565b633131bf7960e21b5f5260045260245ffd5b634a0bfec160e01b5f523360045260245ffd5b61499e915060203d602011611cf057611ce28183613b63565b5f6148bd565b90506020813d6020116149d0575b816149bf60209383613b63565b81010312610da557516148ab614882565b3d91506149b2565b359061ffff82168203610da557565b91908110156131f95760051b810135906101de1981360301821215610da5570190565b6040519091610160614a1c8184613b63565b36833781925f5b60ff8116600b811015614a6f5760ff91600191828060a01b0385165f52600660205260405f20815f52602052614a676001600160401b0360405f205416918861472a565b520116614a23565b5050509050565b60405160208101907fa604fff5a27d5951f334ccda7abff3286a8af29caeeb196a6f2b40a1dce7612b82527fc619505002d5007634d27320f0048d5d1c5afce34ccad1dd6375aa12660719e660408201527f2fc2a6c36092b11f026ace43ab546acdba21ec549e096cc237386bf84f6963ae60608201524660808201527f720d0d938a4dc8953abf8fbbdb7f8551022f52b67ff459f9772a21aa44137c0f60a082015260a0815261459b60c082613b63565b94939192906020810135600b811015610da557614b449061577e565b60048110156132f0578015614d5057600314614bac575050614b6e57614b699261582a565b905f90565b60405163765a8bc960e11b81526020600482015260156024820152743737ba10309033bab0b93234b0b71030b1ba34b7b760591b6044820152606490fd5b91939592909450614bbd818061441a565b158015929150614d37575b50614cff57614bd690613bd7565b6001600160a01b03165f90815260046020526040812090949093614bf984613ea1565b915f96865494605e1984360301915b878a1015614cf0578960051b85013583811215610da55784614c2b9187016160d4565b6001600160a01b03165f805b828c8b8310614cbb575b50505015614ca9575f5b828110614c745750614c5d8288613d01565b5260018101809111612bb557600190990198614c08565b816001600160a01b03614c87838b613d01565b511614614c9657600101614c4b565b50636c2d22d760e01b5f5260045260245ffd5b6302333ca160e51b5f5260045260245ffd5b82614cc591613ed3565b905460039190911b1c6001600160a01b031614614ce457600101614c37565b505060015f828c614c41565b60039950975095505050505050565b60405163765a8bc960e11b815260206004820152600f60248201526e33bab0b93234b0b71030b1ba34b7b760891b6044820152606490fd5b614d469150602081019061441a565b905015155f614bc8565b505050505050505f905f90565b81811461495c578154916001600160401b038311612eae57614d7f83836146e6565b5f5260205f20905f5260205f208154915f925b848410614da0575050505050565b600191820180546001600160a01b0390921684860155939091019290614d92565b929190614dcd81613c3d565b93614ddb6040519586613b63565b602085838152019160051b8101928311610da557905b828210614dfd57505050565b60208091614e0a846136c2565b815201910190614df1565b61ffff60019116019061ffff8211612bb557565b614e31613c54565b5060018060a01b0316805f5260036020526001600160401b0360405f206001810154614ef1600283015492600381015460048201546006830154906007840154926009850154978a5f52600a60205263ffffffff600860405f2097015460101c16976040519b614ea08d613b10565b8c5260208c015260408b015260608a0152608089015260a088015260c087015260018060a01b03841660e087015260ff8460a01c16151561010087015260ff8460a81c161515610120870152613e2f565b61014085015261016084015260b01c1661018082015290565b600b8110156132f0578015615145576001811461511e57600281146150ee57600381146150c057600481146150905760058114615054576006811461501f5760078114614fe35760088114614fb657600914614f8f57604051614f6e604082613b63565b60118152701cd95d0b58da185a5b8b5858d8dbdd5b9d607a1b602082015290565b604051614f9d604082613b63565b6009815268656e61626c652d707160b81b602082015290565b50604051614fc5604082613b63565b600e81526d3a3930b739b332b916b7bbb732b960911b602082015290565b50604051614ff2604082613b63565b601881527f66696e616c697a652d677561726469616e2d6368616e67650000000000000000602082015290565b5060405161502e604082613b63565b601681527563616e63656c2d677561726469616e2d6368616e676560501b602082015290565b50604051615063604082613b63565b601881527f696e6974696174652d677561726469616e2d6368616e67650000000000000000602082015290565b5060405161509f604082613b63565b60118152703334b730b634bd3296b937ba30ba34b7b760791b602082015290565b506040516150cf604082613b63565b600f81526e31b0b731b2b616b937ba30ba34b7b760891b602082015290565b506040516150fd604082613b63565b601181527034b734ba34b0ba3296b937ba30ba34b7b760791b602082015290565b5060405161512d604082613b63565b6008815267756e667265657a6560c01b602082015290565b50604051615154604082613b63565b6006815265667265657a6560d01b602082015290565b94939195965f9785156153cd576001600160401b03164381116153b7576102586151948243613e94565b116153a15750604051936020850152602084526151b2604085613b63565b5f945f985b888a1015615377578960051b840135607e1985360301811215610da5578401966151e088613bd7565b6001600160a01b03918216911681101561534b57506151fe87613bd7565b9661523f60208761520e84613bd7565b604051632e4bfa5160e11b81526001600160a01b039091166004820152602481019190915291829081906044820190565b03816001600160a01b038e165afa9081156128fc575f9161532d575b5015615306576020810190600460ff61527384613f80565b16036152d35761528488828c615f52565b1561529f5750505f198114612bb5576001998a0199016151b7565b906152b46152ae60ff93613bd7565b91613f80565b9063bbf82ba360e01b5f5260018060a01b03166004521660245260445ffd5b906152e26152ae60ff93613bd7565b9063587548c360e11b5f5260018060a01b031660045216602452600460445260645ffd5b6153108691613bd7565b63ae8bb03960e01b5f5260018060a01b031660045260245260445ffd5b615345915060203d8111611cf057611ce28183613b63565b5f61525b565b61535488613bd7565b6311641feb60e21b5f9081526004929092526001600160a01b0316602452604490fd5b9850955095505050505080831061538b5750565b826305bc216760e51b5f5260045260245260445ffd5b630ed38fd160e41b5f526004524360245260445ffd5b637b51505560e01b5f526004524360245260445ffd5b631fc460bf60e11b5f5260045ffd5b81158015615501575b6154eb576001600160a01b03165f818152600a6020526040812093905b845481101561546457836154168287613dbc565b50541461542557600101615402565b908060016154586020947fdad5443e7da4ee0fe09d9ddadad5781fdbcdced9d75ef0e3ca82c3c231c9c3e5969798613dbc565b500155604051908152a3565b509091926040519161547583613b2c565b84835260208301918183528054600160401b811015612eae5761549d91600182018155613dbc565b6154d85760016020937fdad5443e7da4ee0fe09d9ddadad5781fdbcdced9d75ef0e3ca82c3c231c9c3e59551835551910155604051908152a3565b634e487b7160e01b5f525f60045260245ffd5b5063483f415960e01b5f5260045260245260445ffd5b5082156153e5565b6001600160a01b039091165f908152600c602052604090208054600160401b811015612eae57826147d98260016155439401855584613ed3565b546001600160a01b039091165f908152600d6020526040902055565b9061556a8251613ea1565b6155748351613ea1565b5f935b80518510156156c2576001600160a01b036155928683613d01565b511660405160208101917fd9678eabd6141fe616532b9df9bfdaedfa09cd81dd3444e4a52f5d3dc6e8872a83526040820152604081526155d3606082613b63565b5190206155e08685613d01565b526001600160a01b036155f38683613d01565b5116946001600160a01b036156088284613d01565b51165f52600c60205260405f2095604051602081019160808201907f9c57ef476e1983208f620f721cb1c8eb1297e8e0315ae8704308bcddc11b864e84526040830152606080830152885480915260a08201985f5260205f20905f905b8082106156a057505050615688816001959697989903601f198101835282613b63565b5190206156958286613d01565b520193929190615577565b82546001600160a01b03168b526020909a019960019283019290910190615665565b509192507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b031691823b15610da557615747926157355f80946040519687958694859363abf1570d60e01b85526008600486015260026024860152608060448601526084850190613742565b83810360031901606485015290613742565b03925af180156128fc576157585750565b5f613bb391613b63565b3561ffff81168103610da55790565b358015158103610da55790565b600b811015806132f0578115801561581e575b81811561580e575b5061580757806132f0576008821480156157fb575b8181156157eb575b506157e4576132f057600481149081156157d9575b506157d557600190565b5f90565b60079150145f6157cb565b5050600290565b90506132f057600a8214816157b6565b50505f600982146157ae565b5050600390565b90506132f0576006821481615799565b50505f60038214615791565b9190615836818061441a565b9050158061599c575b61598d5760208101615851818361441a565b905061591557506158658161586b9261441a565b90616359565b90928351602085012060018201549060038301548083146159065781036158cb575050506158b4916001935b604051916020830152602082526158af604083613b63565b6163eb565b156158bc5790565b6337e8456b60e01b5f5260045ffd5b1490816158f4575b50156158e5576158b491600293615897565b6385f83a9760e01b5f5260045ffd5b60ff91506009015460a01c165f6158d3565b637154496160e11b5f5260045ffd5b9260099092919201549260ff8460a01c16615957576144df61593a916159409461441a565b906162bf565b6001600160a01b039182169116036158bc57600290565b60405163765a8bc960e11b815260206004820152600d60248201526c6163636f756e7420697320505160981b6044820152606490fd5b63a8bd36f560e01b5f5260045ffd5b506159aa602082018261441a565b90501561583f565b6001600160a01b03165f818152600d602052604090205490918115613af8576001600160a01b03165f908152600c6020526040902080549091905f198101908111612bb5575f19820190828211612bb557808203615a5b575b50505080548015615a47575f190190615a248282613ed3565b81549060018060a01b039060031b1b19169055555f52600d6020525f6040812055565b634e487b7160e01b5f52603160045260245ffd5b6147d991615a6c615a879286613ed3565b905460039190911b1c6001600160a01b031692839186613ed3565b5f52600d60205260405f20555f8080615a0b565b91909160048110156132f057600303615b195761ffff16908115615ad35710615ac057565b6302333ca160e51b5f525f60045260245ffd5b60405163765a8bc960e11b815260206004820152601a60248201527f6e6f20677561726469616e2073657420636f6e666967757265640000000000006044820152606490fd5b60405163765a8bc960e11b815260206004820152600e60248201526d677561726469616e73206f6e6c7960901b6044820152606490fd5b60048110156132f057600103615b6257565b60405163765a8bc960e11b81526020600482015260116024820152707265636f76657279206b6579206f6e6c7960781b6044820152606490fd5b90919261ffff83519416908115615e8f57818510615e3d57600385101580615e33575b615dce5761ffff1690811115615d6e578310615d14575f5b838110615be45750505050565b6001600160a01b03615bf68285613d01565b51168015615cce576001600160a01b0383168114615c885760018201808311612bb5575b858110615c2b575050600101615bd7565b816001600160a01b03615c3e8388613d01565b511614615c4d57600101615c1a565b604051631a3e09c760e01b8152602060048201526012602482015271323ab83634b1b0ba329033bab0b93234b0b760711b6044820152606490fd5b604051631a3e09c760e01b815260206004820152601c60248201527f612077616c6c65742063616e6e6f7420677561726420697473656c66000000006044820152606490fd5b604051631a3e09c760e01b815260206004820152601e60248201527f7a65726f2061646472657373206973206e6f74206120677561726469616e00006044820152606490fd5b604051631a3e09c760e01b815260206004820152602b60248201527f63616e63656c207468726573686f6c642065786365656473207468652067756160448201526a1c991a585b8818dbdd5b9d60aa1b6064820152608490fd5b604051631a3e09c760e01b815260206004820152603160248201527f63616e63656c207468726573686f6c64206d757374206578636565642074686560448201527008199c99595e99481d1a1c995cda1bdb19607a1b6064820152608490fd5b604051631a3e09c760e01b815260206004820152603660248201527f7468726565206f72206d6f726520677561726469616e73206e65656420612074604482015275343932b9b437b6321037b31030ba103632b0b9ba101960511b6064820152608490fd5b5060028210615bbf565b604051631a3e09c760e01b8152602060048201526024808201527f7468726573686f6c6420657863656564732074686520677561726469616e20636044820152631bdd5b9d60e21b6064820152608490fd5b50505050615e9957565b604051631a3e09c760e01b815260206004820152602860248201527f677561726469616e7320737570706c69656420776974682061207a65726f20746044820152671a1c995cda1bdb1960c21b6064820152608490fd5b602081830312610da5578051906001600160401b038211610da5570181601f82011215610da557805190615f238261444c565b92615f316040519485613b63565b82845260208383010111610da557815f9260208093018386015e8301015290565b9160208201600460ff615f6483613f80565b161461600c5760ff615f77600592613f80565b1614615f84575050505f90565b5f615f8e83613bd7565b604051639e5adaeb60e01b81526001600160a01b0391821660048201529485916024918391165afa9182156128fc57614830935f93615fe0575b506144df816040615fda93019061441a565b916163eb565b615fda9193506160046144df913d805f833e615ffc8183613b63565b810190615ef0565b939150615fc8565b505f61601783613bd7565b60405163b7af85d760e01b81526001600160a01b0391821660048201529485916024918391165afa9182156128fc57614830935f93616069575b506144df81604061606393019061441a565b916164da565b6160639193506160856144df913d805f833e615ffc8183613b63565b939150616051565b61ffff8116156160ce57617fff6160a79160011c16614e15565b9061ffff6160b482614e15565b1661ffff831611156160c4575090565b6148309150614e15565b50505f90565b906001600160a01b036160e683613bd7565b161561629a576001600160a01b036160fd83613bd7565b165f52600360205260405f209060ff825416156135455760098201549160ff8360a81c1661625f5760208401616133818661441a565b90506161cc575050604083019061614a828561441a565b905061615f5763a8bd36f560e01b5f5260045ffd5b60ff8360a01c166161955761593a6144df61617a938661441a565b6001600160a01b039182169116036158bc5761483090613bd7565b60405163765a8bc960e11b815260206004820152600e60248201526d677561726469616e20697320505160901b6044820152606490fd5b909260a01c60ff1615616224576158656161e6918561441a565b919092600184516020860120910154036158e55761621692604051916020830152602082526158af604083613b63565b156158bc5761483090613bd7565b60405163765a8bc960e11b8152602060048201526012602482015271677561726469616e206973206e6f7420505160701b6044820152606490fd5b60405163765a8bc960e11b815260206004820152601260248201527133bab0b93234b0b71034b990333937bd32b760711b6044820152606490fd5b604082016162a8818461441a565b90501561598d576144df61593a916148309461441a565b60418251036158bc57602082015190606060408401519301515f1a907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116158bc576020935f93608093604051938452868401526040830152606082015282805260015afa156128fc575f516001600160a01b038116156158bc5790565b9080601f83011215610da55781602061483093359101614467565b9190606081106163dc57820191606081840312610da55761637981613958565b9260208201356001600160401b038111610da5578161639991840161633e565b916040810135916001600160401b038311610da55760ff926163bb920161633e565b9316600581036163ca57509190565b630a6b407960e41b5f5260045260245ffd5b6398f4350560e01b5f5260045ffd5b60408151148015906164cd575b6164c657602061644b5f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282613b63565b51906102055afa3d156164bf573d6164628161444c565b906164706040519283613b63565b81523d5f602083013e5b816164b3575b81616489575090565b90506020815191015190602081106164a2575b50151590565b5f199060200360031b1b165f61649c565b80516020149150616480565b606061647a565b5050505f90565b50617460835114156163f8565b610a20815114801590616552575b6164c657602061653b5f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282613b63565b51906102045afa3d156164bf573d6164628161444c565b50611213835114156164e856
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
001fPUSH20x368d
0022JUMPI
0023DUP1
0024PUSH40x178bcc93
0029EQ
002aPUSH20x3649
002dJUMPI
002eDUP1
002fPUSH40x23a64734
0034EQ
0035PUSH20x362a
0038JUMPI
0039DUP1
003aPUSH40x29b57c69
003fEQ
0040PUSH20x360d
0043JUMPI
0044DUP1
0045PUSH40x2cd0ef57
004aEQ
004bPUSH20x35ee
004eJUMPI
004fDUP1
0050PUSH40x2f7c88f5
0055EQ
0056PUSH20x35cb
0059JUMPI
005aDUP1
005bPUSH40x2fecac30
0060EQ
0061PUSH20x3593
0064JUMPI
0065DUP1
0066PUSH40x33a7c02f
006bEQ
006cPUSH20x356e
006fJUMPI
0070DUP1
0071PUSH40x34c209d9
0076EQ
0077PUSH20x257d
007aJUMPI
007bDUP1
007cPUSH40x38eb0788
0081EQ
0082PUSH20x2561
0085JUMPI
0086DUP1
0087PUSH40x4217f757
008cEQ
008dPUSH20x2512
0090JUMPI
0091DUP1
0092PUSH40x499326fb
0097EQ
0098PUSH20x24ea
009bJUMPI
009cDUP1
009dPUSH40x51510e4a
00a2EQ
00a3PUSH20x24af
00a6JUMPI
00a7DUP1
00a8PUSH40x5575e683
00adEQ
00aePUSH20x2470
00b1JUMPI
00b2DUP1
00b3PUSH40x57f73695
00b8EQ
00b9PUSH20x2421
00bcJUMPI
00bdDUP1
00bePUSH40x58137dff
00c3EQ
00c4PUSH20x23e4
00c7JUMPI
00c8DUP1
00c9PUSH40x5d419257
00ceEQ
00cfPUSH20x23c8
00d2JUMPI
00d3DUP1
00d4PUSH40x5e732005
00d9EQ
00daPUSH20x23a8
00ddJUMPI
00deDUP1
00dfPUSH40x65e84331
00e4EQ
00e5PUSH20x22c2
00e8JUMPI
00e9DUP1
00eaPUSH40x6b75b6a1
00efEQ
00f0PUSH20x2040
00f3JUMPI
00f4DUP1
00f5PUSH40x6edb17dc
00faEQ
00fbPUSH20x1ffd
00feJUMPI
00ffDUP1
0100PUSH40x795e8d4a
0105EQ
0106PUSH20x1f45
0109JUMPI
010aDUP1
010bPUSH40x7b103999
0110EQ
0111PUSH20x1f00
0114JUMPI
0115DUP1
0116PUSH40x8086b8ba
011bEQ
011cPUSH20x1e98
011fJUMPI
0120DUP1
0121PUSH40x81c0dd54
0126EQ
0127PUSH20x1e7a
012aJUMPI
012bDUP1
012cPUSH40x82a44847
0131EQ
0132PUSH20x1e3f
0135JUMPI
0136DUP1
0137PUSH40x840b625b
013cEQ
013dPUSH20x1dff
0140JUMPI
0141DUP1
0142PUSH40x8af1bee6
0147EQ
0148PUSH20x1db0
014bJUMPI
014cDUP1
014dPUSH40x8f48dc3f
0152EQ
0153PUSH20x1d52
0156JUMPI
0157DUP1
0158PUSH40x909473a9
015dEQ
015ePUSH20x1d17
0161JUMPI
0162DUP1
0163PUSH40x92880ad0
0168EQ
0169PUSH20x1cfb
016cJUMPI
016dDUP1
016ePUSH40x94bc4e96
0173EQ
0174PUSH20x1975
0177JUMPI
0178DUP1
0179PUSH40x96f191d4
017eEQ
017fPUSH20x1129
0182JUMPI
0183DUP1
0184PUSH40xa7ce2703
0189EQ
018aPUSH20x1102
018dJUMPI
018eDUP1
018fPUSH40xb19f4805
0194EQ
0195PUSH20x10c7
0198JUMPI
0199DUP1
019aPUSH40xb580a787
019fEQ
01a0PUSH20x10a1
01a3JUMPI
01a4DUP1
01a5PUSH40xbbc7ba35
01aaEQ
01abPUSH20x105f
01aeJUMPI
01afDUP1
01b0PUSH40xbd252f13
01b5EQ
01b6PUSH20x1020
01b9JUMPI
01baDUP1
01bbPUSH40xbf6bff17
01c0EQ
01c1PUSH20x04ba
01c4JUMPI
01c5DUP1
01c6PUSH40xc0676111
01cbEQ
01ccPUSH20x0493
01cfJUMPI
01d0DUP1
01d1PUSH40xc06ac6a3
01d6EQ
01d7PUSH20x034a
01daJUMPI
01dbDUP1
01dcPUSH40xc2eeb670
01e1EQ
01e2PUSH20x032b
01e5JUMPI
01e6DUP1
01e7PUSH40xe3b5908a
01ecEQ
01edPUSH20x02f7
01f0JUMPI
01f1DUP1
01f2PUSH40xf487885e
01f7EQ
01f8PUSH20x02da
01fbJUMPI
01fcDUP1
01fdPUSH40xf698da25
0202EQ
0203PUSH20x02b7
0206JUMPI
0207DUP1
0208PUSH40xfc72c4ce
020dEQ
020ePUSH20x029b
0211JUMPI
0212PUSH40xfd6bc547
0217EQ
0218PUSH20x021f
021bJUMPI
021cPUSH0
021dDUP1
021eREVERT
021fJUMPDEST
0220CALLVALUE
0221PUSH20x0298
0224JUMPI
0225PUSH10x20
0227CALLDATASIZE
0228PUSH10x03
022aNOT
022bADD
022cSLT
022dPUSH20x0298
0230JUMPI
0231PUSH20x0238
0234PUSH20x36ac
0237JUMP
0238JUMPDEST
0239SWAP1
023aPUSH20x0241
023dPUSH20x3c54
0240JUMP
0241JUMPDEST
0242POP
0243PUSH10x01
0245PUSH10x01
0247PUSH10xa0
0249SHL
024aSUB
024bDUP3
024cAND
024dDUP1
024eDUP3
024fMSTORE
0250PUSH10x03
0252PUSH10x20
0254MSTORE
0255PUSH10x40
0257DUP3
0258KECCAK256
0259SLOAD
025aPUSH10xff
025cAND
025dISZERO
025ePUSH20x0285
0261JUMPI
0262PUSH20x0281
0265PUSH20x026d
0268DUP5
0269PUSH20x4e29
026cJUMP
026dJUMPDEST
026ePUSH10x40
0270MLOAD
0271SWAP2
0272DUP3
0273SWAP2
0274PUSH10x20
0276DUP4
0277MSTORE
0278PUSH10x20
027aDUP4
027bADD
027cSWAP1
027dPUSH20x3996
0280JUMP
0281JUMPDEST
0282SUB
0283SWAP1
0284RETURN
0285JUMPDEST
0286PUSH40x3131bf79
028bPUSH10xe2
028dSHL
028eDUP3
028fMSTORE
0290PUSH10x04
0292MSTORE
0293PUSH10x24
0295SWAP2
0296POP
0297REVERT
0298JUMPDEST
0299DUP1
029aREVERT
029bJUMPDEST
029cPOP
029dCALLVALUE
029ePUSH20x0298
02a1JUMPI
02a2DUP1
02a3PUSH10x03
02a5NOT
02a6CALLDATASIZE
02a7ADD
02a8SLT
02a9PUSH20x0298
02acJUMPI
02adPUSH10x20
02afPUSH10x40
02b1MLOAD
02b2PUSH10x0f
02b4DUP2
02b5MSTORE
02b6RETURN
02b7JUMPDEST
02b8POP
02b9CALLVALUE
02baPUSH20x0298
02bdJUMPI
02beDUP1
02bfPUSH10x03
02c1NOT
02c2CALLDATASIZE
02c3ADD
02c4SLT
02c5PUSH20x0298
02c8JUMPI
02c9PUSH10x20
02cbPUSH20x02d2
02cePUSH20x4a76
02d1JUMP
02d2JUMPDEST
02d3PUSH10x40
02d5MLOAD
02d6SWAP1
02d7DUP2
02d8MSTORE
02d9RETURN
02daJUMPDEST
02dbPOP
02dcCALLVALUE
02ddPUSH20x0298
02e0JUMPI
02e1DUP1
02e2PUSH10x03
02e4NOT
02e5CALLDATASIZE
02e6ADD
02e7SLT
02e8PUSH20x0298
02ebJUMPI
02ecPUSH10x20
02eeSWAP1
02efSLOAD
02f0PUSH10x40
02f2MLOAD
02f3SWAP1
02f4DUP2
02f5MSTORE
02f6RETURN
02f7JUMPDEST
02f8POP
02f9CALLVALUE
02faPUSH20x0298
02fdJUMPI
02fePUSH10x20
0300CALLDATASIZE
0301PUSH10x03
0303NOT
0304ADD
0305SLT
0306PUSH20x0298
0309JUMPI
030aPUSH20x0160
030dPUSH20x031c
0310PUSH20x0317
0313PUSH20x36ac
0316JUMP
0317JUMPDEST
0318PUSH20x4a0a
031bJUMP
031cJUMPDEST
031dPUSH20x0329
0320PUSH10x40
0322MLOAD
0323DUP1
0324SWAP3
0325PUSH20x3966
0328JUMP
0329JUMPDEST
032aRETURN
032bJUMPDEST
032cPOP
032dCALLVALUE
032ePUSH20x0298
0331JUMPI
0332DUP1
0333PUSH10x03
0335NOT
0336CALLDATASIZE
0337ADD
0338SLT
0339PUSH20x0298
033cJUMPI
033dPUSH10x40
033fMLOAD
0340PUSH30x36ee80
0344DUP2
0345MSTORE
0346PUSH10x20
0348SWAP1
0349RETURN
034aJUMPDEST
034bPOP
034cCALLVALUE
034dPUSH20x0298
0350JUMPI
0351PUSH10x20
0353CALLDATASIZE
0354PUSH10x03
0356NOT
0357ADD
0358SLT
0359PUSH20x0298
035cJUMPI
035dPUSH20x0364
0360PUSH20x36ac
0363JUMP
0364JUMPDEST
0365SWAP1
0366PUSH20x036d
0369PUSH20x41da
036cJUMP
036dJUMPDEST
036ePOP
036fPUSH20x0160
0372PUSH10x40
0374MLOAD
0375PUSH20x037e
0378DUP3
0379DUP3
037aPUSH20x3b63
037dJUMP
037eJUMPDEST
037fCALLDATASIZE
0380SWAP1
0381CALLDATACOPY
0382PUSH10x01
0384PUSH10x01
0386PUSH10xa0
0388SHL
0389SUB
038aDUP3
038bAND
038cDUP1
038dDUP3
038eMSTORE
038fPUSH10x03
0391PUSH10x20
0393MSTORE
0394PUSH10x40
0396DUP3
0397KECCAK256
0398SLOAD
0399SWAP1
039aSWAP2
039bSWAP1
039cPUSH10xff
039eAND
039fISZERO
03a0PUSH20x0481
03a3JUMPI
03a4PUSH20x0435
03a7SWAP3
03a8PUSH20x0472
03abDUP3
03acDUP5
03adPUSH20x0281
03b0SWAP5
03b1MSTORE
03b2PUSH10x03
03b4PUSH10x20
03b6MSTORE
03b7PUSH10x40
03b9DUP2
03baKECCAK256
03bbDUP6
03bcDUP3
03bdMSTORE
03bePUSH10x04
03c0PUSH10x20
03c2MSTORE
03c3PUSH20x0464
03c6PUSH20x0458
03c9PUSH10x40
03cbDUP5
03ccKECCAK256
03cdSWAP8
03ceDUP1
03cfDUP6
03d0MSTORE
03d1PUSH10x05
03d3PUSH10x20
03d5MSTORE
03d6PUSH20x0449
03d9PUSH20x0428
03dcPUSH20x0422
03dfPUSH20x041c
03e2PUSH20x0416
03e5PUSH20x0410
03e8PUSH10x40
03eaPUSH20x03f5
03edDUP2
03eeDUP14
03efKECCAK256
03f0SWAP15
03f1PUSH20x4a0a
03f4JUMP
03f5JUMPDEST
03f6SWAP12
03f7DUP9
03f8DUP2
03f9MSTORE
03faPUSH10x08
03fcPUSH10x20
03feMSTORE
03ffDUP2
0400DUP2
0401KECCAK256
0402SWAP9
0403DUP2
0404MSTORE
0405PUSH10x0a
0407PUSH10x20
0409MSTORE
040aKECCAK256
040bSWAP10
040cPUSH20x42a2
040fJUMP
0410JUMPDEST
0411SWAP14
0412PUSH20x3b84
0415JUMP
0416JUMPDEST
0417SWAP11
0418PUSH20x3b84
041bJUMP
041cJUMPDEST
041dSWAP4
041ePUSH20x3d74
0421JUMP
0422JUMPDEST
0423SWAP6
0424PUSH20x3e2f
0427JUMP
0428JUMPDEST
0429SWAP8
042aPUSH10x40
042cMLOAD
042dSWAP12
042eDUP13
042fDUP1
0430SWAP13
0431PUSH20x37de
0434JUMP
0435JUMPDEST
0436PUSH20x0560
0439PUSH20x0380
043cDUP13
043dADD
043eMSTORE
043fPUSH20x0560
0442DUP12
0443ADD
0444SWAP1
0445PUSH20x36d6
0448JUMP
0449JUMPDEST
044aSWAP1
044bDUP10
044cDUP3
044dSUB
044ePUSH20x03a0
0451DUP12
0452ADD
0453MSTORE
0454PUSH20x36d6
0457JUMP
0458JUMPDEST
0459SWAP3
045aPUSH20x03c0
045dDUP9
045eADD
045fSWAP1
0460PUSH20x3966
0463JUMP
0464JUMPDEST
0465DUP6
0466DUP3
0467SUB
0468PUSH20x0520
046bDUP8
046cADD
046dMSTORE
046ePUSH20x3742
0471JUMP
0472JUMPDEST
0473SWAP1
0474DUP4
0475DUP3
0476SUB
0477PUSH20x0540
047aDUP6
047bADD
047cMSTORE
047dPUSH20x3775
0480JUMP
0481JUMPDEST
0482PUSH10x24
0484SWAP2
0485PUSH40x3131bf79
048aPUSH10xe2
048cSHL
048dDUP3
048eMSTORE
048fPUSH10x04
0491MSTORE
0492REVERT
0493JUMPDEST
0494POP
0495CALLVALUE
0496PUSH20x0298
0499JUMPI
049aDUP1
049bPUSH10x03
049dNOT
049eCALLDATASIZE
049fADD
04a0SLT
04a1PUSH20x0298
04a4JUMPI
04a5PUSH10x20
04a7PUSH10x01
04a9PUSH10x01
04abPUSH10x40
04adSHL
04aeSUB
04afPUSH10x02
04b1SLOAD
04b2AND
04b3PUSH10x40
04b5MLOAD
04b6SWAP1
04b7DUP2
04b8MSTORE
04b9RETURN
04baJUMPDEST
04bbPOP
04bcCALLVALUE
04bdPUSH20x0298
04c0JUMPI
04c1PUSH10x60
04c3CALLDATASIZE
04c4PUSH10x03
04c6NOT
04c7ADD
04c8SLT
04c9PUSH20x0298
04ccJUMPI
04cdPUSH10x04
04cfCALLDATALOAD
04d0PUSH10x01
04d2PUSH10x01
04d4PUSH10x40
04d6SHL
04d7SUB
04d8DUP2
04d9GT
04daPUSH20x101c
04ddJUMPI
04dePUSH20x04eb
04e1SWAP1
04e2CALLDATASIZE
04e3SWAP1
04e4PUSH10x04
04e6ADD
04e7PUSH20x3712
04eaJUMP
04ebJUMPDEST
04ecPUSH20x04f6
04efSWAP3
04f0SWAP2
04f1SWAP3
04f2PUSH20x37b4
04f5JUMP
04f6JUMPDEST
04f7PUSH10x44
04f9CALLDATALOAD
04faPUSH10x01
04fcPUSH10x01
04fePUSH10x40
0500SHL
0501SUB
0502DUP2
0503GT
0504PUSH20x0862
0507JUMPI
0508PUSH20x0515
050bSWAP1
050cCALLDATASIZE
050dSWAP1
050ePUSH10x04
0510ADD
0511PUSH20x3712
0514JUMP
0515JUMPDEST
0516SWAP1
0517SWAP2
0518PUSH10x01
051aSLOAD
051bDUP1
051cISZERO
051dPUSH20x100d
0520JUMPI
0521PUSH10x02
0523SLOAD
0524SWAP4
0525PUSH10x01
0527PUSH10x01
0529PUSH10x40
052bSHL
052cSUB
052dDUP6
052eAND
052fSWAP4
0530PUSH10x40
0532MLOAD
0533DUP8
0534PUSH10x60
0536DUP3
0537ADD
0538DUP8
0539PUSH10x20
053bDUP5
053cADD
053dMSTORE
053ePUSH10x40
0540DUP1
0541DUP5
0542ADD
0543MSTORE
0544MSTORE
0545PUSH10x80
0547DUP2
0548ADD
0549PUSH10x80
054bDUP10
054cPUSH10x05
054eSHL
054fDUP4
0550ADD
0551ADD
0552SWAP1
0553DUP12
0554SWAP1
0555DUP12
0556DUP14
0557PUSH20x01de
055aNOT
055bSWAP1
055cCALLDATASIZE
055dSUB
055eADD
055fSWAP1
0560JUMPDEST
0561DUP13
0562DUP2
0563LT
0564PUSH20x0ec0
0567JUMPI
0568POP
0569POP
056aPOP
056bPOP
056cSWAP3
056dPUSH20x0645
0570SWAP6
0571SWAP3
0572DUP3
0573PUSH20x0592
0576PUSH10x01
0578PUSH10x01
057aPUSH10x40
057cSHL
057dSUB
057eSWAP10
057fSWAP8
0580SWAP5
0581PUSH20x063f
0584SWAP8
0585SUB
0586PUSH10x1f
0588NOT
0589DUP2
058aADD
058bDUP4
058cMSTORE
058dDUP3
058ePUSH20x3b63
0591JUMP
0592JUMPDEST
0593PUSH10x20
0595DUP2
0596MLOAD
0597SWAP2
0598ADD
0599KECCAK256
059aPUSH10x40
059cMLOAD
059dPUSH10x20
059fDUP2
05a0ADD
05a1SWAP2
05a2PUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
05c3DUP4
05c4MSTORE
05c5CHAINID
05c6PUSH10x40
05c8DUP4
05c9ADD
05caMSTORE
05cbADDRESS
05ccPUSH10x60
05ceDUP4
05cfADD
05d0MSTORE
05d1PUSH320x1d2159d826062d6d8bb06b1f7449d53275f95106855af24febedc5e555741358
05f2PUSH10x80
05f4DUP4
05f5ADD
05f6MSTORE
05f7DUP11
05f8DUP8
05f9AND
05faPUSH10xa0
05fcDUP4
05fdADD
05feMSTORE
05ffPUSH10xc0
0601DUP3
0602ADD
0603MSTORE
0604PUSH10xc0
0606DUP2
0607MSTORE
0608PUSH20x0612
060bPUSH10xe0
060dDUP3
060ePUSH20x3b63
0611JUMP
0612JUMPDEST
0613MLOAD
0614SWAP1
0615KECCAK256
0616SWAP1
0617DUP12
0618SLOAD
0619SWAP3
061aPUSH320x0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d540
063bPUSH20x516a
063eJUMP
063fJUMPDEST
0640POP
0641PUSH20x3bff
0644JUMP
0645JUMPDEST
0646AND
0647SWAP1
0648PUSH10x01
064aPUSH10x01
064cPUSH10x40
064eSHL
064fSUB
0650NOT
0651AND
0652OR
0653PUSH10x02
0655SSTORE
0656PUSH20x065e
0659DUP2
065aPUSH20x3c3d
065dJUMP
065eJUMPDEST
065fSWAP3
0660PUSH20x066c
0663PUSH10x40
0665MLOAD
0666SWAP5
0667DUP6
0668PUSH20x3b63
066bJUMP
066cJUMPDEST
066dDUP2
066eDUP5
066fMSTORE
0670PUSH10x1f
0672NOT
0673PUSH20x067b
0676DUP4
0677PUSH20x3c3d
067aJUMP
067bJUMPDEST
067cADD
067dDUP4
067eJUMPDEST
067fDUP2
0680DUP2
0681LT
0682PUSH20x0ea9
0685JUMPI
0686POP
0687POP
0688PUSH20x0690
068bDUP3
068cPUSH20x3ea1
068fJUMP
0690JUMPDEST
0691PUSH20x0699
0694DUP4
0695PUSH20x3ea1
0698JUMP
0699JUMPDEST
069aSWAP1
069bPUSH30x36ee80
069fDUP6
06a0JUMPDEST
06a1DUP6
06a2DUP2
06a3LT
06a4PUSH20x0866
06a7JUMPI
06a8POP
06a9DUP6
06aaSWAP3
06abSWAP2
06acSWAP1
06adPOP
06aeDUP7
06afPUSH320x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8
06d0PUSH10x01
06d2PUSH10x01
06d4PUSH10xa0
06d6SHL
06d7SUB
06d8AND
06d9EXTCODESIZE
06daISZERO
06dbPUSH20x0862
06deJUMPI
06dfDUP4
06e0PUSH20x06fd
06e3SWAP2
06e4PUSH10x40
06e6MLOAD
06e7DUP1
06e8SWAP4
06e9DUP2
06eaSWAP3
06ebPUSH40x2728f271
06f0PUSH10xe2
06f2SHL
06f3DUP4
06f4MSTORE
06f5PUSH10x04
06f7DUP4
06f8ADD
06f9PUSH20x3d15
06fcJUMP
06fdJUMPDEST
06feSUB
06ffDUP2
0700DUP4
0701PUSH320x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8
0722PUSH10x01
0724PUSH10x01
0726PUSH10xa0
0728SHL
0729SUB
072aAND
072bGAS
072cCALL
072dSWAP1
072eDUP2
072fISZERO
0730PUSH20x0857
0733JUMPI
0734DUP5
0735SWAP2
0736PUSH20x0842
0739JUMPI
073aJUMPDEST
073bPOP
073cPOP
073dPUSH320x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8
075ePUSH10x01
0760PUSH10x01
0762PUSH10xa0
0764SHL
0765SUB
0766AND
0767EXTCODESIZE
0768ISZERO
0769PUSH20x0833
076cJUMPI
076dPUSH10x40
076fMLOAD
0770PUSH40xabf1570d
0775PUSH10xe0
0777SHL
0778DUP2
0779MSTORE
077aSWAP2
077bDUP4
077cSWAP2
077dDUP4
077eSWAP2
077fDUP3
0780SWAP2
0781PUSH20x078e
0784SWAP2
0785SWAP1
0786PUSH10x04
0788DUP5
0789ADD
078aPUSH20x47fd
078dJUMP
078eJUMPDEST
078fSUB
0790DUP2
0791DUP4
0792PUSH320x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8
07b3PUSH10x01
07b5PUSH10x01
07b7PUSH10xa0
07b9SHL
07baSUB
07bbAND
07bcGAS
07bdCALL
07beDUP1
07bfISZERO
07c0PUSH20x0837
07c3JUMPI
07c4PUSH20x081e
07c7JUMPI
07c8JUMPDEST
07c9POP
07caPOP
07cbPUSH20x07d3
07ceDUP3
07cfPUSH20x3ea1
07d2JUMP
07d3JUMPDEST
07d4SWAP2
07d5DUP4
07d6JUMPDEST
07d7DUP2
07d8DUP2
07d9LT
07daPUSH20x07ea
07ddJUMPI
07deDUP5
07dfPUSH20x07e7
07e2DUP6
07e3PUSH20x555f
07e6JUMP
07e7JUMPDEST
07e8DUP1
07e9RETURN
07eaJUMPDEST
07ebDUP1
07ecPUSH20x0804
07efPUSH20x0100
07f2PUSH20x07fe
07f5PUSH10x01
07f7SWAP5
07f8DUP7
07f9DUP9
07faPUSH20x49e7
07fdJUMP
07feJUMPDEST
07ffADD
0800PUSH20x3bd7
0803JUMP
0804JUMPDEST
0805PUSH20x080e
0808DUP3
0809DUP8
080aPUSH20x3d01
080dJUMP
080eJUMPDEST
080fSWAP1
0810DUP4
0811DUP1
0812PUSH10xa0
0814SHL
0815SUB
0816AND
0817SWAP1
0818MSTORE
0819ADD
081aPUSH20x07d6
081dJUMP
081eJUMPDEST
081fDUP2
0820PUSH20x0828
0823SWAP2
0824PUSH20x3b63
0827JUMP
0828JUMPDEST
0829PUSH20x0833
082cJUMPI
082dDUP3
082eDUP5
082fPUSH20x07c8
0832JUMP
0833JUMPDEST
0834DUP3
0835DUP1
0836REVERT
0837JUMPDEST
0838PUSH10x40
083aMLOAD
083bRETURNDATASIZE
083cDUP5
083dDUP3
083eRETURNDATACOPY
083fRETURNDATASIZE
0840SWAP1
0841REVERT
0842JUMPDEST
0843DUP2
0844PUSH20x084c
0847SWAP2
0848PUSH20x3b63
084bJUMP
084cJUMPDEST
084dPUSH20x0833
0850JUMPI
0851DUP3
0852DUP8
0853PUSH20x073a
0856JUMP
0857JUMPDEST
0858PUSH10x40
085aMLOAD
085bRETURNDATASIZE
085cDUP7
085dDUP3
085eRETURNDATACOPY
085fRETURNDATASIZE
0860SWAP1
0861REVERT
0862JUMPDEST
0863DUP4
0864DUP1
0865REVERT
0866JUMPDEST
0867SWAP4
0868SWAP1
0869SWAP6
086aSWAP5
086bSWAP2
086cPUSH20x0876
086fDUP6
0870DUP5
0871DUP5
0872PUSH20x49e7
0875JUMP
0876JUMPDEST
0877SWAP8
0878PUSH20x087f
087bPUSH20x3c54
087eJUMP
087fJUMPDEST
0880POP
0881PUSH10x01
0883PUSH10x01
0885PUSH10xa0
0887SHL
0888SUB
0889PUSH20x0891
088cDUP11
088dPUSH20x3bd7
0890JUMP
0891JUMPDEST
0892AND
0893DUP8
0894MSTORE
0895PUSH10x03
0897PUSH10x20
0899MSTORE
089aPUSH10x40
089cDUP8
089dKECCAK256
089eSWAP8
089fDUP9
08a0SLOAD
08a1SWAP1
08a2PUSH10xff
08a4DUP3
08a5AND
08a6PUSH20x0e85
08a9JUMPI
08aaPUSH20x0160
08adDUP12
08aeADD
08afPUSH10x01
08b1PUSH10x01
08b3PUSH10x40
08b5SHL
08b6SUB
08b7PUSH20x08bf
08baDUP3
08bbPUSH20x3beb
08beJUMP
08bfJUMPDEST
08c0AND
08c1PUSH20x0e76
08c4JUMPI
08c5POP
08c6PUSH40x05265c00
08cbSWAP2
08ccJUMPDEST
08cdDUP2
08cePUSH10x01
08d0PUSH10x01
08d2PUSH10x40
08d4SHL
08d5SUB
08d6DUP5
08d7AND
08d8LT
08d9DUP1
08daISZERO
08dbPUSH20x0e60
08deJUMPI
08dfJUMPDEST
08e0PUSH20x0e44
08e3JUMPI
08e4PUSH20x08f1
08e7PUSH20x0140
08eaDUP14
08ebADD
08ecDUP14
08edPUSH20x476f
08f0JUMP
08f1JUMPDEST
08f2SWAP1
08f3POP
08f4ISZERO
08f5PUSH20x0e20
08f8JUMPI
08f9PUSH10xe0
08fbDUP13
08fcADD
08fdCALLDATALOAD
08feSWAP1
08ffDUP2
0900ISZERO
0901PUSH20x0dfc
0904JUMPI
0905SWAP1
0906PUSH10x01
0908PUSH20x01a0
090bSWAP5
090cSWAP4
090dSWAP3
090ePUSH20x098f
0911DUP16
0912DUP15
0913PUSH20x01c0
0916DUP3
0917ADD
0918SWAP1
0919PUSH20xffff
091cPUSH20x0924
091fDUP4
0920PUSH20x5762
0923JUMP
0924JUMPDEST
0925AND
0926ISZERO
0927ISZERO
0928SWAP1
0929POP
092aPUSH20x0deb
092dJUMPI
092ePOP
092fPUSH20x0956
0932PUSH20x093f
0935PUSH20x0180
0938DUP4
0939ADD
093aDUP4
093bPUSH20x4681
093eJUMP
093fJUMPDEST
0940SWAP1
0941POP
0942PUSH20xffff
0945PUSH20x094f
0948DUP12
0949DUP6
094aADD
094bPUSH20x5762
094eJUMP
094fJUMPDEST
0950SWAP2
0951AND
0952PUSH20x608d
0955JUMP
0956JUMPDEST
0957SWAP8
0958DUP9
0959SWAP2
095aJUMPDEST
095bPUSH20x0989
095ePUSH20x0966
0961DUP3
0962PUSH20x3bd7
0965JUMP
0966JUMPDEST
0967SWAP2
0968PUSH20x0981
096bPUSH20x0978
096ePUSH20x0180
0971DUP4
0972ADD
0973DUP4
0974PUSH20x4681
0977JUMP
0978JUMPDEST
0979SWAP6
097aSWAP1
097bSWAP3
097cADD
097dPUSH20x5762
0980JUMP
0981JUMPDEST
0982SWAP4
0983CALLDATASIZE
0984SWAP2
0985PUSH20x4dc1
0988JUMP
0989JUMPDEST
098aSWAP1
098bPUSH20x5b9c
098eJUMP
098fJUMPDEST
0990PUSH10xff
0992NOT
0993AND
0994OR
0995DUP13
0996SSTORE
0997PUSH10x20
0999DUP14
099aADD
099bCALLDATALOAD
099cPUSH10x01
099eDUP14
099fADD
09a0SSTORE
09a1PUSH10x40
09a3DUP14
09a4ADD
09a5CALLDATALOAD
09a6PUSH10x02
09a8DUP14
09a9ADD
09aaSSTORE
09abPUSH10x60
09adDUP14
09aeADD
09afCALLDATALOAD
09b0PUSH10x03
09b2DUP14
09b3ADD
09b4SSTORE
09b5PUSH10x80
09b7DUP14
09b8ADD
09b9CALLDATALOAD
09baPUSH10x04
09bcDUP14
09bdADD
09beSSTORE
09bfPUSH10x05
09c1DUP13
09c2ADD
09c3SSTORE
09c4PUSH10xa0
09c6DUP13
09c7ADD
09c8CALLDATALOAD
09c9PUSH10x06
09cbDUP13
09ccADD
09cdSSTORE
09cePUSH10xc0
09d0DUP13
09d1ADD
09d2CALLDATALOAD
09d3PUSH10x07
09d5DUP13
09d6ADD
09d7SSTORE
09d8PUSH10x08
09daDUP12
09dbADD
09dcPUSH10x01
09dePUSH20xffff
09e1NOT
09e2DUP3
09e3SLOAD
09e4AND
09e5OR
09e6SWAP1
09e7SSTORE
09e8PUSH20x09f4
09ebPUSH20x0100
09eeDUP14
09efADD
09f0PUSH20x3bd7
09f3JUMP
09f4JUMPDEST
09f5PUSH10x09
09f7DUP13
09f8ADD
09f9DUP1
09faSLOAD
09fbPUSH10x01
09fdPUSH10x01
09ffPUSH10xa0
0a01SHL
0a02SUB
0a03NOT
0a04AND
0a05PUSH10x01
0a07PUSH10x01
0a09PUSH10xa0
0a0bSHL
0a0cSUB
0a0dSWAP3
0a0eSWAP1
0a0fSWAP3
0a10AND
0a11SWAP2
0a12SWAP1
0a13SWAP2
0a14OR
0a15SWAP1
0a16SSTORE
0a17PUSH20x0a23
0a1aPUSH20x0120
0a1dDUP14
0a1eADD
0a1fPUSH20x5771
0a22JUMP
0a23JUMPDEST
0a24PUSH10x09
0a26DUP13
0a27ADD
0a28DUP1
0a29SLOAD
0a2aPUSH10xff
0a2cPUSH10xa0
0a2eSHL
0a2fNOT
0a30AND
0a31SWAP2
0a32ISZERO
0a33ISZERO
0a34PUSH10xa0
0a36SHL
0a37PUSH10xff
0a39PUSH10xa0
0a3bSHL
0a3cAND
0a3dSWAP2
0a3eSWAP1
0a3fSWAP2
0a40OR
0a41SWAP1
0a42SSTORE
0a43DUP10
0a44JUMPDEST
0a45PUSH20x0a52
0a48PUSH20x0140
0a4bDUP15
0a4cADD
0a4dDUP15
0a4ePUSH20x476f
0a51JUMP
0a52JUMPDEST
0a53SWAP1
0a54POP
0a55DUP2
0a56LT
0a57ISZERO
0a58PUSH20x0aa8
0a5bJUMPI
0a5cPUSH10x01
0a5eSWAP1
0a5fPUSH20x0aa2
0a62DUP15
0a63PUSH20x0a6b
0a66DUP2
0a67PUSH20x3bd7
0a6aJUMP
0a6bJUMPDEST
0a6cSWAP1
0a6dPUSH10x20
0a6fPUSH20x0a9a
0a72DUP6
0a73PUSH20x0a87
0a76PUSH20x0a8d
0a79DUP3
0a7aPUSH20x0a87
0a7dPUSH20x0140
0a80DUP9
0a81ADD
0a82DUP9
0a83PUSH20x476f
0a86JUMP
0a87JUMPDEST
0a88SWAP1
0a89PUSH20x47a4
0a8cJUMP
0a8dJUMPDEST
0a8eCALLDATALOAD
0a8fSWAP5
0a90PUSH20x0140
0a93DUP2
0a94ADD
0a95SWAP1
0a96PUSH20x476f
0a99JUMP
0a9aJUMPDEST
0a9bADD
0a9cCALLDATALOAD
0a9dSWAP2
0a9ePUSH20x53dc
0aa1JUMP
0aa2JUMPDEST
0aa3ADD
0aa4PUSH20x0a44
0aa7JUMP
0aa8JUMPDEST
0aa9POP
0aaaSWAP5
0aabSWAP8
0aacSWAP4
0aadSWAP6
0aaeSWAP9
0aafSWAP10
0ab0SWAP1
0ab1PUSH10x01
0ab3PUSH10x01
0ab5PUSH10x40
0ab7SHL
0ab8SUB
0ab9PUSH10x0a
0abbPUSH20x0b35
0abeSWAP5
0abfSWAP4
0ac0SWAP14
0ac1SWAP6
0ac2SWAP14
0ac3PUSH10x01
0ac5PUSH10xb0
0ac7SHL
0ac8DUP4
0ac9PUSH10xb0
0acbSHL
0accNOT
0acdPUSH10x09
0acfDUP4
0ad0ADD
0ad1SLOAD
0ad2AND
0ad3OR
0ad4PUSH10x09
0ad6DUP3
0ad7ADD
0ad8SSTORE
0ad9ADD
0adaSWAP2
0adbAND
0adcPUSH10x01
0adePUSH10x01
0ae0PUSH10x40
0ae2SHL
0ae3SUB
0ae4NOT
0ae5DUP3
0ae6SLOAD
0ae7AND
0ae8OR
0ae9DUP2
0aeaSSTORE
0aebPUSH20x0b16
0aeePUSH20x0afa
0af1PUSH20x01a0
0af4DUP7
0af5ADD
0af6PUSH20x5762
0af9JUMP
0afaJUMPDEST
0afbDUP3
0afcSLOAD
0afdPUSH20xffff
0b00PUSH10x40
0b02SHL
0b03NOT
0b04AND
0b05PUSH10x40
0b07SWAP2
0b08SWAP1
0b09SWAP2
0b0aSHL
0b0bPUSH20xffff
0b0ePUSH10x40
0b10SHL
0b11AND
0b12OR
0b13DUP3
0b14SSTORE
0b15JUMP
0b16JUMPDEST
0b17DUP1
0b18SLOAD
0b19PUSH20xffff
0b1cPUSH10x50
0b1eSHL
0b1fNOT
0b20AND
0b21PUSH10x50
0b23SWAP3
0b24SWAP1
0b25SWAP3
0b26SHL
0b27PUSH20xffff
0b2aPUSH10x50
0b2cSHL
0b2dAND
0b2eSWAP2
0b2fSWAP1
0b30SWAP2
0b31OR
0b32SWAP1
0b33SSTORE
0b34JUMP
0b35JUMPDEST
0b36PUSH20x0b43
0b39PUSH20x0180
0b3cDUP3
0b3dADD
0b3eDUP3
0b3fPUSH20x4681
0b42JUMP
0b43JUMPDEST
0b44SWAP1
0b45PUSH10x01
0b47PUSH10x01
0b49PUSH10xa0
0b4bSHL
0b4cSUB
0b4dPUSH20x0b55
0b50DUP5
0b51PUSH20x3bd7
0b54JUMP
0b55JUMPDEST
0b56AND
0b57DUP11
0b58MSTORE
0b59PUSH10x04
0b5bPUSH10x20
0b5dMSTORE
0b5ePUSH10x40
0b60DUP11
0b61KECCAK256
0b62SWAP1
0b63PUSH10x01
0b65PUSH10x01
0b67PUSH10x40
0b69SHL
0b6aSUB
0b6bDUP4
0b6cGT
0b6dPUSH20x0dd7
0b70JUMPI
0b71PUSH20x0b7a
0b74DUP4
0b75DUP4
0b76PUSH20x46e6
0b79JUMP
0b7aJUMPDEST
0b7bSWAP1
0b7cDUP11
0b7dMSTORE
0b7ePUSH10x20
0b80DUP11
0b81KECCAK256
0b82DUP11
0b83JUMPDEST
0b84DUP4
0b85DUP2
0b86LT
0b87PUSH20x0dbc
0b8aJUMPI
0b8bPOP
0b8cPOP
0b8dPOP
0b8ePOP
0b8fPUSH20x0c29
0b92DUP2
0b93PUSH20x0ba6
0b96PUSH20x0ba1
0b99PUSH20x0c2e
0b9cSWAP5
0b9dPUSH20x3bd7
0ba0JUMP
0ba1JUMPDEST
0ba2PUSH20x47b4
0ba5JUMP
0ba6JUMPDEST
0ba7PUSH20x0bc5
0baaPUSH20x0bb2
0badDUP3
0baePUSH20x3bd7
0bb1JUMP
0bb2JUMPDEST
0bb3PUSH20x0bbf
0bb6PUSH20x0100
0bb9DUP5
0bbaADD
0bbbPUSH20x3bd7
0bbeJUMP
0bbfJUMPDEST
0bc0SWAP1
0bc1PUSH20x5509
0bc4JUMP
0bc5JUMPDEST
0bc6PUSH20x0bce
0bc9DUP2
0bcaPUSH20x3bd7
0bcdJUMP
0bceJUMPDEST
0bcfPUSH20x0bdb
0bd2PUSH20x0100
0bd5DUP4
0bd6ADD
0bd7PUSH20x3bd7
0bdaJUMP
0bdbJUMPDEST
0bdcPUSH320x568403fd429f133b4cc18a945d220c328c59a445a8122f240f1d74fd55fb6937
0bfdPUSH10x20
0bffPUSH20x0c0b
0c02PUSH20x0120
0c05DUP7
0c06ADD
0c07PUSH20x5771
0c0aJUMP
0c0bJUMPDEST
0c0cPUSH10x40
0c0eMLOAD
0c0fSWAP1
0c10ISZERO
0c11ISZERO
0c12DUP2
0c13MSTORE
0c14PUSH10x01
0c16PUSH10x01
0c18PUSH10xa0
0c1aSHL
0c1bSUB
0c1cSWAP4
0c1dDUP5
0c1eAND
0c1fSWAP5
0c20SWAP1
0c21SWAP4
0c22AND
0c23SWAP3
0c24LOG3
0c25PUSH20x3bd7
0c28JUMP
0c29JUMPDEST
0c2aPUSH20x4e29
0c2dJUMP
0c2eJUMPDEST
0c2fPUSH20x0c38
0c32DUP3
0c33DUP11
0c34PUSH20x3d01
0c37JUMP
0c38JUMPDEST
0c39MSTORE
0c3aPUSH20x0c43
0c3dDUP2
0c3eDUP10
0c3fPUSH20x3d01
0c42JUMP
0c43JUMPDEST
0c44POP
0c45PUSH20x0c57
0c48PUSH20x0c52
0c4bDUP3
0c4cDUP9
0c4dDUP9
0c4ePUSH20x49e7
0c51JUMP
0c52JUMPDEST
0c53PUSH20x3bd7
0c56JUMP
0c57JUMPDEST
0c58PUSH10x40
0c5aMLOAD
0c5bPUSH40x82edfbd9
0c60PUSH10xe0
0c62SHL
0c63DUP2
0c64MSTORE
0c65PUSH10x01
0c67PUSH10x01
0c69PUSH10xa0
0c6bSHL
0c6cSUB
0c6dSWAP2
0c6eDUP3
0c6fAND
0c70PUSH10x04
0c72DUP3
0c73ADD
0c74MSTORE
0c75SWAP2
0c76SWAP1
0c77PUSH10x20
0c79SWAP1
0c7aDUP4
0c7bSWAP1
0c7cPUSH10x24
0c7eSWAP1
0c7fDUP3
0c80SWAP1
0c81PUSH320x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8
0ca2AND
0ca3GAS
0ca4STATICCALL
0ca5DUP1
0ca6ISZERO
0ca7PUSH20x0db1
0caaJUMPI
0cabDUP9
0cacSWAP1
0cadPUSH20x0d7b
0cb0JUMPI
0cb1JUMPDEST
0cb2PUSH10x01
0cb4SWAP3
0cb5POP
0cb6PUSH20x0cbf
0cb9DUP3
0cbaDUP7
0cbbPUSH20x3d01
0cbeJUMP
0cbfJUMPDEST
0cc0MSTORE
0cc1PUSH20x0ccb
0cc4DUP2
0cc5DUP9
0cc6DUP9
0cc7PUSH20x49e7
0ccaJUMP
0ccbJUMPDEST
0cccPUSH10x40
0cceMLOAD
0ccfPUSH10x20
0cd1DUP2
0cd2ADD
0cd3SWAP1
0cd4PUSH10x20
0cd6DUP4
0cd7ADD
0cd8CALLDATALOAD
0cd9DUP3
0cdaMSTORE
0cdbPUSH10x40
0cddDUP4
0cdeADD
0cdfCALLDATALOAD
0ce0PUSH10x40
0ce2DUP3
0ce3ADD
0ce4MSTORE
0ce5PUSH10x60
0ce7DUP4
0ce8ADD
0ce9CALLDATALOAD
0ceaPUSH10x60
0cecDUP3
0cedADD
0ceeMSTORE
0cefPUSH10x80
0cf1DUP4
0cf2ADD
0cf3CALLDATALOAD
0cf4PUSH10x80
0cf6DUP3
0cf7ADD
0cf8MSTORE
0cf9PUSH10xa0
0cfbDUP4
0cfcADD
0cfdCALLDATALOAD
0cfePUSH10xa0
0d00DUP3
0d01ADD
0d02MSTORE
0d03PUSH10xc0
0d05DUP4
0d06ADD
0d07CALLDATALOAD
0d08PUSH10xc0
0d0aDUP3
0d0bADD
0d0cMSTORE
0d0dPUSH10xc0
0d0fDUP2
0d10MSTORE
0d11PUSH20x0d1b
0d14PUSH10xe0
0d16DUP3
0d17PUSH20x3b63
0d1aJUMP
0d1bJUMPDEST
0d1cMLOAD
0d1dSWAP1
0d1eKECCAK256
0d1fPUSH10x40
0d21MLOAD
0d22SWAP1
0d23PUSH10xe0
0d25PUSH10x20
0d27DUP4
0d28ADD
0d29SWAP4
0d2aPUSH320xcc25d3fea88291f95ddfb5590a6b760f02245a0e4ca7c0b69285c6cd26543afd
0d4bDUP6
0d4cMSTORE
0d4dADD
0d4eCALLDATALOAD
0d4fPUSH10x40
0d51DUP4
0d52ADD
0d53MSTORE
0d54PUSH10x60
0d56DUP3
0d57ADD
0d58MSTORE
0d59PUSH10x60
0d5bDUP2
0d5cMSTORE
0d5dPUSH20x0d67
0d60PUSH10x80
0d62DUP3
0d63PUSH20x3b63
0d66JUMP
0d67JUMPDEST
0d68MLOAD
0d69SWAP1
0d6aKECCAK256
0d6bPUSH20x0d74
0d6eDUP3
0d6fDUP8
0d70PUSH20x3d01
0d73JUMP
0d74JUMPDEST
0d75MSTORE
0d76ADD
0d77PUSH20x06a0
0d7aJUMP
0d7bJUMPDEST
0d7cPOP
0d7dPUSH10x20
0d7fDUP3
0d80RETURNDATASIZE
0d81DUP3
0d82GT
0d83PUSH20x0da9
0d86JUMPI
0d87JUMPDEST
0d88DUP2
0d89PUSH20x0d94
0d8cPUSH10x20
0d8eSWAP4
0d8fDUP4
0d90PUSH20x3b63
0d93JUMP
0d94JUMPDEST
0d95DUP2
0d96ADD
0d97SUB
0d98SLT
0d99PUSH20x0da5
0d9cJUMPI
0d9dPUSH10x01
0d9fSWAP2
0da0MLOAD
0da1PUSH20x0cb1
0da4JUMP
0da5JUMPDEST
0da6PUSH0
0da7DUP1
0da8REVERT
0da9JUMPDEST
0daaRETURNDATASIZE
0dabSWAP2
0dacPOP
0dadPUSH20x0d87
0db0JUMP
0db1JUMPDEST
0db2PUSH10x40
0db4MLOAD
0db5RETURNDATASIZE
0db6DUP11
0db7DUP3
0db8RETURNDATACOPY
0db9RETURNDATASIZE
0dbaSWAP1
0dbbREVERT
0dbcJUMPDEST
0dbdPUSH10x01
0dbfSWAP1
0dc0PUSH10x20
0dc2PUSH20x0dca
0dc5DUP6
0dc6PUSH20x3bd7
0dc9JUMP
0dcaJUMPDEST
0dcbSWAP5
0dccADD
0dcdSWAP4
0dceDUP2
0dcfDUP5
0dd0ADD
0dd1SSTORE
0dd2ADD
0dd3PUSH20x0b83
0dd6JUMP
0dd7JUMPDEST
0dd8PUSH40x4e487b71
0dddPUSH10xe0
0ddfSHL
0de0DUP12
0de1MSTORE
0de2PUSH10x41
0de4PUSH10x04
0de6MSTORE
0de7PUSH10x24
0de9DUP12
0deaREVERT
0debJUMPDEST
0decPUSH20x0df4
0defSWAP1
0df0PUSH20x5762
0df3JUMP
0df4JUMPDEST
0df5SWAP8
0df6DUP9
0df7SWAP2
0df8PUSH20x095a
0dfbJUMP
0dfcJUMPDEST
0dfdPUSH10x24
0dffDUP12
0e00PUSH20x0e08
0e03DUP16
0e04PUSH20x3bd7
0e07JUMP
0e08JUMPDEST
0e09PUSH40x16efda7d
0e0ePUSH10xe2
0e10SHL
0e11DUP3
0e12MSTORE
0e13PUSH10x01
0e15PUSH10x01
0e17PUSH10xa0
0e19SHL
0e1aSUB
0e1bAND
0e1cPUSH10x04
0e1eMSTORE
0e1fREVERT
0e20JUMPDEST
0e21PUSH10x24
0e23DUP11
0e24PUSH20x0e2c
0e27DUP15
0e28PUSH20x3bd7
0e2bJUMP
0e2cJUMPDEST
0e2dPUSH40x3aa293db
0e32PUSH10xe1
0e34SHL
0e35DUP3
0e36MSTORE
0e37PUSH10x01
0e39PUSH10x01
0e3bPUSH10xa0
0e3dSHL
0e3eSUB
0e3fAND
0e40PUSH10x04
0e42MSTORE
0e43REVERT
0e44JUMPDEST
0e45PUSH40x10b0f875
0e4aPUSH10xe1
0e4cSHL
0e4dDUP11
0e4eMSTORE
0e4fPUSH10x01
0e51PUSH10x01
0e53PUSH10x40
0e55SHL
0e56SUB
0e57DUP4
0e58AND
0e59PUSH10x04
0e5bMSTORE
0e5cPUSH10x24
0e5eDUP11
0e5fREVERT
0e60JUMPDEST
0e61POP
0e62PUSH40x9a7ec800
0e67PUSH10x01
0e69PUSH10x01
0e6bPUSH10x40
0e6dSHL
0e6eSUB
0e6fDUP5
0e70AND
0e71GT
0e72PUSH20x08df
0e75JUMP
0e76JUMPDEST
0e77PUSH20x0e7f
0e7aSWAP1
0e7bPUSH20x3beb
0e7eJUMP
0e7fJUMPDEST
0e80SWAP2
0e81PUSH20x08cc
0e84JUMP
0e85JUMPDEST
0e86PUSH10x24
0e88DUP10
0e89PUSH20x0e91
0e8cDUP14
0e8dPUSH20x3bd7
0e90JUMP
0e91JUMPDEST
0e92PUSH40x3b490093
0e97PUSH10xe1
0e99SHL
0e9aDUP3
0e9bMSTORE
0e9cPUSH10x01
0e9ePUSH10x01
0ea0PUSH10xa0
0ea2SHL
0ea3SUB
0ea4AND
0ea5PUSH10x04
0ea7MSTORE
0ea8REVERT
0ea9JUMPDEST
0eaaPUSH10x20
0eacSWAP1
0eadPUSH20x0eb4
0eb0PUSH20x3c54
0eb3JUMP
0eb4JUMPDEST
0eb5DUP3
0eb6DUP3
0eb7DUP10
0eb8ADD
0eb9ADD
0ebaMSTORE
0ebbADD
0ebcPUSH20x067e
0ebfJUMP
0ec0JUMPDEST
0ec1SWAP1
0ec2SWAP2
0ec3SWAP3
0ec4SWAP4
0ec5PUSH10x7f
0ec7NOT
0ec8DUP7
0ec9DUP3
0ecaSUB
0ecbADD
0eccDUP5
0ecdMSTORE
0eceDUP5
0ecfCALLDATALOAD
0ed0DUP4
0ed1DUP2
0ed2SLT
0ed3ISZERO
0ed4PUSH20x1009
0ed7JUMPI
0ed8DUP16
0ed9ADD
0edaSWAP1
0edbPUSH10x01
0eddPUSH10x01
0edfPUSH10xa0
0ee1SHL
0ee2SUB
0ee3PUSH20x0eeb
0ee6DUP4
0ee7PUSH20x36c2
0eeaJUMP
0eebJUMPDEST
0eecAND
0eedDUP2
0eeeMSTORE
0eefPUSH10x20
0ef1DUP3
0ef2ADD
0ef3CALLDATALOAD
0ef4PUSH10x20
0ef6DUP3
0ef7ADD
0ef8MSTORE
0ef9PUSH10x40
0efbDUP3
0efcADD
0efdCALLDATALOAD
0efePUSH10x40
0f00DUP3
0f01ADD
0f02MSTORE
0f03PUSH10x60
0f05DUP3
0f06ADD
0f07CALLDATALOAD
0f08PUSH10x60
0f0aDUP3
0f0bADD
0f0cMSTORE
0f0dPUSH10x80
0f0fDUP3
0f10ADD
0f11CALLDATALOAD
0f12PUSH10x80
0f14DUP3
0f15ADD
0f16MSTORE
0f17PUSH10xa0
0f19DUP3
0f1aADD
0f1bCALLDATALOAD
0f1cPUSH10xa0
0f1eDUP3
0f1fADD
0f20MSTORE
0f21PUSH10xc0
0f23DUP3
0f24ADD
0f25CALLDATALOAD
0f26PUSH10xc0
0f28DUP3
0f29ADD
0f2aMSTORE
0f2bPUSH10xe0
0f2dDUP3
0f2eADD
0f2fCALLDATALOAD
0f30PUSH10xe0
0f32DUP3
0f33ADD
0f34MSTORE
0f35PUSH10x01
0f37DUP1
0f38PUSH10xa0
0f3aSHL
0f3bSUB
0f3cPUSH20x0f48
0f3fPUSH20x0100
0f42DUP5
0f43ADD
0f44PUSH20x36c2
0f47JUMP
0f48JUMPDEST
0f49AND
0f4aPUSH20x0100
0f4dDUP3
0f4eADD
0f4fMSTORE
0f50PUSH20x0120
0f53DUP3
0f54ADD
0f55CALLDATALOAD
0f56DUP1
0f57ISZERO
0f58ISZERO
0f59DUP1
0f5aSWAP2
0f5bSUB
0f5cPUSH20x1005
0f5fJUMPI
0f60PUSH10x01
0f62SWAP3
0f63DUP3
0f64PUSH10x20
0f66SWAP4
0f67SWAP3
0f68PUSH20x0120
0f6bDUP6
0f6cSWAP5
0f6dADD
0f6eMSTORE
0f6fPUSH20x01c0
0f72PUSH20xffff
0f75PUSH20x0ff5
0f78DUP3
0f79PUSH20x0fd9
0f7cPUSH20x0fa0
0f7fPUSH20x0f8c
0f82PUSH20x0140
0f85DUP10
0f86ADD
0f87DUP10
0f88PUSH20x4614
0f8bJUMP
0f8cJUMPDEST
0f8dPUSH20x01e0
0f90PUSH20x0140
0f93DUP11
0f94ADD
0f95MSTORE
0f96PUSH20x01e0
0f99DUP10
0f9aADD
0f9bSWAP2
0f9cPUSH20x4648
0f9fJUMP
0fa0JUMPDEST
0fa1PUSH10x01
0fa3PUSH10x01
0fa5PUSH10x40
0fa7SHL
0fa8SUB
0fa9PUSH20x0fb5
0facPUSH20x0160
0fafDUP11
0fb0ADD
0fb1PUSH20x37ca
0fb4JUMP
0fb5JUMPDEST
0fb6AND
0fb7PUSH20x0160
0fbaDUP9
0fbbADD
0fbcMSTORE
0fbdPUSH20x0fca
0fc0PUSH20x0180
0fc3DUP10
0fc4ADD
0fc5DUP10
0fc6PUSH20x45a1
0fc9JUMP
0fcaJUMPDEST
0fcbSWAP1
0fccDUP9
0fcdDUP4
0fceSUB
0fcfPUSH20x0180
0fd2DUP11
0fd3ADD
0fd4MSTORE
0fd5PUSH20x45d5
0fd8JUMP
0fd9JUMPDEST
0fdaSWAP6
0fdbDUP4
0fdcPUSH20x0fe8
0fdfPUSH20x01a0
0fe2DUP4
0fe3ADD
0fe4PUSH20x49d8
0fe7JUMP
0fe8JUMPDEST
0fe9AND
0feaPUSH20x01a0
0fedDUP8
0feeADD
0fefMSTORE
0ff0ADD
0ff1PUSH20x49d8
0ff4JUMP
0ff5JUMPDEST
0ff6AND
0ff7SWAP2
0ff8ADD
0ff9MSTORE
0ffaSWAP7
0ffbADD
0ffcSWAP5
0ffdADD
0ffeSWAP3
0fffSWAP2
1000ADD
1001PUSH20x0560
1004JUMP
1005JUMPDEST
1006DUP16
1007DUP1
1008REVERT
1009JUMPDEST
100aDUP15
100bDUP1
100cREVERT
100dJUMPDEST
100ePUSH40x1d087e61
1013PUSH10xe2
1015SHL
1016DUP7
1017MSTORE
1018PUSH10x04
101aDUP7
101bREVERT
101cJUMPDEST
101dPOP
101eDUP1
101fREVERT
1020JUMPDEST
1021POP
1022CALLVALUE
1023PUSH20x0298
1026JUMPI
1027PUSH10x20
1029CALLDATASIZE
102aPUSH10x03
102cNOT
102dADD
102eSLT
102fPUSH20x0298
1032JUMPI
1033PUSH10x04
1035CALLDATALOAD
1036PUSH10xff
1038DUP2
1039AND
103aDUP1
103bSWAP2
103cSUB
103dPUSH20x101c
1040JUMPI
1041PUSH10x40
1043DUP3
1044PUSH10x01
1046PUSH10x01
1048PUSH10x40
104aSHL
104bSUB
104cSWAP3
104dPUSH10x20
104fSWAP5
1050MSTORE
1051PUSH10x07
1053DUP5
1054MSTORE
1055KECCAK256
1056SLOAD
1057AND
1058PUSH10x40
105aMLOAD
105bSWAP1
105cDUP2
105dMSTORE
105eRETURN
105fJUMPDEST
1060POP
1061CALLVALUE
1062PUSH20x0298
1065JUMPI
1066PUSH10x20
1068CALLDATASIZE
1069PUSH10x03
106bNOT
106cADD
106dSLT
106ePUSH20x0298
1071JUMPI
1072PUSH10x20
1074SWAP1
1075PUSH20xffff
1078SWAP1
1079PUSH10x08
107bSWAP1
107cPUSH10x40
107eSWAP1
107fPUSH10x01
1081PUSH10x01
1083PUSH10xa0
1085SHL
1086SUB
1087PUSH20x108e
108aPUSH20x36ac
108dJUMP
108eJUMPDEST
108fAND
1090DUP2
1091MSTORE
1092PUSH10x03
1094DUP6
1095MSTORE
1096KECCAK256
1097ADD
1098SLOAD
1099AND
109aPUSH10x40
109cMLOAD
109dSWAP1
109eDUP2
109fMSTORE
10a0RETURN
10a1JUMPDEST
10a2POP
10a3CALLVALUE
10a4PUSH20x0298
10a7JUMPI
10a8PUSH10x40
10aaCALLDATASIZE
10abPUSH10x03
10adNOT
10aeADD
10afSLT
10b0PUSH20x0298
10b3JUMPI
10b4PUSH20x07e7
10b7PUSH20x10be
10baPUSH20x36ac
10bdJUMP
10beJUMPDEST
10bfPUSH10x24
10c1CALLDATALOAD
10c2SWAP1
10c3PUSH20x4833
10c6JUMP
10c7JUMPDEST
10c8POP
10c9CALLVALUE
10caPUSH20x0298
10cdJUMPI
10ceDUP1
10cfPUSH10x03
10d1NOT
10d2CALLDATASIZE
10d3ADD
10d4SLT
10d5PUSH20x0298
10d8JUMPI
10d9PUSH10x20
10dbPUSH10x40
10ddMLOAD
10dePUSH320x3154287b2470d9f05573ebd18908404f28e212134930a0f6df0005bc02e1c515
10ffDUP2
1100MSTORE
1101RETURN
1102JUMPDEST
1103POP
1104CALLVALUE
1105PUSH20x0298
1108JUMPI
1109DUP1
110aPUSH10x03
110cNOT
110dCALLDATASIZE
110eADD
110fSLT
1110PUSH20x0298
1113JUMPI
1114PUSH10x20
1116PUSH10x01
1118PUSH10x01
111aPUSH10x40
111cSHL
111dSUB
111ePUSH10x0e
1120SLOAD
1121AND
1122PUSH10x40
1124MLOAD
1125SWAP1
1126DUP2
1127MSTORE
1128RETURN
1129JUMPDEST
112aPOP
112bCALLVALUE
112cPUSH20x0298
112fJUMPI
1130PUSH10x60
1132CALLDATASIZE
1133PUSH10x03
1135NOT
1136ADD
1137SLT
1138PUSH20x0298
113bJUMPI
113cPUSH10x01
113ePUSH10x01
1140PUSH10x40
1142SHL
1143SUB
1144PUSH10x04
1146CALLDATALOAD
1147GT
1148PUSH20x0298
114bJUMPI
114cPUSH20x0400
114fPUSH10x04
1151CALLDATALOAD
1152CALLDATASIZE
1153SUB
1154PUSH10x03
1156NOT
1157ADD
1158SLT
1159PUSH20x0298
115cJUMPI
115dPUSH20x1164
1160PUSH20x37b4
1163JUMP
1164JUMPDEST
1165PUSH10x44
1167CALLDATALOAD
1168PUSH10x01
116aPUSH10x01
116cPUSH10x40
116eSHL
116fSUB
1170DUP2
1171GT
1172PUSH20x0833
1175JUMPI
1176PUSH20x1183
1179SWAP1
117aCALLDATASIZE
117bSWAP1
117cPUSH10x04
117eADD
117fPUSH20x3712
1182JUMP
1183JUMPDEST
1184SWAP2
1185PUSH10x0e
1187SLOAD
1188PUSH10xff
118aDUP2
118bPUSH10x40
118dSHR
118eAND
118fPUSH20x1966
1192JUMPI
1193PUSH10x01
1195SLOAD
1196SWAP4
1197DUP5
1198ISZERO
1199PUSH20x100d
119cJUMPI
119dPUSH10x40
119fDUP1
11a0MLOAD
11a1PUSH10x01
11a3PUSH10x01
11a5PUSH10x40
11a7SHL
11a8SUB
11a9DUP5
11aaAND
11abPUSH10x20
11adDUP3
11aeADD
11afMSTORE
11b0DUP1
11b1DUP3
11b2ADD
11b3SWAP2
11b4SWAP1
11b5SWAP2
11b6MSTORE
11b7SWAP3
11b8PUSH10x01
11baPUSH10x01
11bcPUSH10xa0
11beSHL
11bfSUB
11c0PUSH20x11cc
11c3PUSH10x04
11c5DUP1
11c6CALLDATALOAD
11c7ADD
11c8PUSH20x36c2
11cbJUMP
11ccJUMPDEST
11cdAND
11cePUSH10x60
11d0DUP6
11d1ADD
11d2MSTORE
11d3PUSH10x24
11d5PUSH10x04
11d7CALLDATALOAD
11d8ADD
11d9SWAP6
11daPUSH20x01e0
11ddDUP8
11dePUSH10x80
11e0DUP8
11e1ADD
11e2CALLDATACOPY
11e3PUSH20x0204
11e6PUSH10x04
11e8CALLDATALOAD
11e9ADD
11eaSWAP6
11ebPUSH20x123b
11eePUSH20x1213
11f1PUSH20x11ff
11f4DUP10
11f5PUSH10x04
11f7CALLDATALOAD
11f8PUSH10x04
11faADD
11fbPUSH20x45a1
11feJUMP
11ffJUMPDEST
1200PUSH20x0400
1203PUSH20x0260
1206DUP12
1207ADD
1208MSTORE
1209PUSH20x0460
120cDUP11
120dADD
120eSWAP2
120fPUSH20x45d5
1212JUMP
1213JUMPDEST
1214PUSH20x1228
1217PUSH20x0224
121aPUSH10x04
121cCALLDATALOAD
121dADD
121ePUSH10x04
1220CALLDATALOAD
1221PUSH10x04
1223ADD
1224PUSH20x45a1
1227JUMP
1228JUMPDEST
1229DUP10
122aDUP4
122bSUB
122cPUSH10x5f
122eNOT
122fADD
1230PUSH20x0280
1233DUP12
1234ADD
1235MSTORE
1236SWAP1
1237PUSH20x45d5
123aJUMP
123bJUMPDEST
123cSWAP4
123dPUSH10x04
123fCALLDATALOAD
1240PUSH20x0244
1243ADD
1244DUP11
1245PUSH20x02a0
1248DUP10
1249ADD
124aJUMPDEST
124bPUSH10x0b
124dDUP3
124eLT
124fPUSH20x1940
1252JUMPI
1253POP
1254POP
1255POP
1256PUSH20x126a
1259PUSH20x03a4
125cPUSH10x04
125eCALLDATALOAD
125fADD
1260PUSH10x04
1262CALLDATALOAD
1263PUSH10x04
1265ADD
1266PUSH20x45a1
1269JUMP
126aJUMPDEST
126bDUP9
126cDUP8
126dSUB
126ePUSH10x5f
1270NOT
1271ADD
1272PUSH20x0400
1275DUP11
1276ADD
1277MSTORE
1278DUP1
1279DUP8
127aMSTORE
127bSWAP1
127cSWAP6
127dPUSH10x01
127fPUSH10x01
1281PUSH10xfb
1283SHL
1284SUB
1285DUP3
1286GT
1287PUSH20x193c
128aJUMPI
128bPUSH20x13a5
128eSWAP7
128fPUSH20x12ce
1292SWAP3
1293PUSH10x05
1295SHL
1296DUP1
1297SWAP2
1298PUSH10x20
129aDUP5
129bADD
129cCALLDATACOPY
129dPUSH10x20
129fPUSH20x12b3
12a2PUSH20x03c4
12a5PUSH10x04
12a7CALLDATALOAD
12a8ADD
12a9PUSH10x04
12abCALLDATALOAD
12acPUSH10x04
12aeADD
12afPUSH20x4614
12b2JUMP
12b3JUMPDEST
12b4SWAP4
12b5SWAP1
12b6SWAP3
12b7ADD
12b8DUP12
12b9DUP2
12baSUB
12bbDUP3
12bcADD
12bdPUSH10x5f
12bfNOT
12c0ADD
12c1PUSH20x0420
12c4DUP14
12c5ADD
12c6MSTORE
12c7ADD
12c8SWAP2
12c9SWAP1
12caPUSH20x4648
12cdJUMP
12ceJUMPDEST
12cfSWAP7
12d0PUSH20x12f1
12d3DUP2
12d4PUSH20x03e4
12d7PUSH10x04
12d9CALLDATALOAD
12daADD
12dbCALLDATALOAD
12dcSWAP10
12ddDUP11
12dePUSH20x0440
12e1DUP4
12e2ADD
12e3MSTORE
12e4SUB
12e5PUSH10x1f
12e7NOT
12e8DUP2
12e9ADD
12eaDUP4
12ebMSTORE
12ecDUP3
12edPUSH20x3b63
12f0JUMP
12f1JUMPDEST
12f2PUSH10x20
12f4DUP2
12f5MLOAD
12f6SWAP2
12f7ADD
12f8KECCAK256
12f9PUSH10x40
12fbMLOAD
12fcPUSH10x20
12feDUP2
12ffADD
1300SWAP2
1301PUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
1322DUP4
1323MSTORE
1324CHAINID
1325PUSH10x40
1327DUP4
1328ADD
1329MSTORE
132aADDRESS
132bPUSH10x60
132dDUP4
132eADD
132fMSTORE
1330PUSH320x8ff45d05bf7eaecf1e3489de0ad3d898e5ab54735cd0ca116506a6c8a7438c95
1351PUSH10x80
1353DUP4
1354ADD
1355MSTORE
1356PUSH10x01
1358PUSH10x01
135aPUSH10x40
135cSHL
135dSUB
135eDUP8
135fAND
1360PUSH10xa0
1362DUP4
1363ADD
1364MSTORE
1365PUSH10xc0
1367DUP3
1368ADD
1369MSTORE
136aPUSH10xc0
136cDUP2
136dMSTORE
136ePUSH20x1378
1371PUSH10xe0
1373DUP3
1374PUSH20x3b63
1377JUMP
1378JUMPDEST
1379MLOAD
137aSWAP1
137bKECCAK256
137cSWAP1
137dDUP11
137eSLOAD
137fSWAP3
1380PUSH320x0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d540
13a1PUSH20x516a
13a4JUMP
13a5JUMPDEST
13a6POP
13a7PUSH10x01
13a9PUSH10x01
13abPUSH10x40
13adSHL
13aeSUB
13afPUSH20x13b9
13b2DUP2
13b3DUP4
13b4AND
13b5PUSH20x3bff
13b8JUMP
13b9JUMPDEST
13baAND
13bbSWAP1
13bcPUSH10x01
13bePUSH10x01
13c0PUSH10x40
13c2SHL
13c3SUB
13c4NOT
13c5AND
13c6OR
13c7PUSH10x0e
13c9SSTORE
13caPUSH20x13d7
13cdPUSH10x04
13cfCALLDATALOAD
13d0PUSH10x04
13d2ADD
13d3PUSH20x3bd7
13d6JUMP
13d7JUMPDEST
13d8PUSH10x01
13daPUSH10x01
13dcPUSH10xa0
13deSHL
13dfSUB
13e0DUP2
13e1AND
13e2SWAP3
13e3SWAP1
13e4SWAP2
13e5SWAP1
13e6DUP4
13e7ISZERO
13e8PUSH20x18db
13ebJUMPI
13ecDUP4
13edDUP7
13eeMSTORE
13efPUSH10x03
13f1PUSH10x20
13f3MSTORE
13f4PUSH10x40
13f6DUP7
13f7KECCAK256
13f8SWAP5
13f9PUSH10xff
13fbDUP7
13fcSLOAD
13fdAND
13fePUSH20x1928
1401JUMPI
1402DUP7
1403JUMPDEST
1404PUSH10x0f
1406DUP2
1407LT
1408PUSH20x1910
140bJUMPI
140cPOP
140dPOP
140ePUSH10xff
1410DUP6
1411SLOAD
1412AND
1413ISZERO
1414DUP1
1415ISZERO
1416PUSH20x1904
1419JUMPI
141aJUMPDEST
141bDUP1
141cISZERO
141dPUSH20x18ef
1420JUMPI
1421JUMPDEST
1422PUSH20x18db
1425JUMPI
1426PUSH20x1434
1429SWAP1
142aPUSH10x04
142cCALLDATALOAD
142dPUSH10x04
142fADD
1430PUSH20x4681
1433JUMP
1434JUMPDEST
1435SWAP1
1436DUP5
1437DUP8
1438MSTORE
1439PUSH10x04
143bPUSH10x20
143dMSTORE
143ePUSH10x40
1440DUP8
1441KECCAK256
1442SWAP1
1443PUSH10x01
1445PUSH10x01
1447PUSH10x40
1449SHL
144aSUB
144bDUP4
144cGT
144dPUSH20x18ac
1450JUMPI
1451PUSH20x145a
1454DUP4
1455DUP4
1456PUSH20x46e6
1459JUMP
145aJUMPDEST
145bSWAP1
145cDUP8
145dMSTORE
145ePUSH10x20
1460DUP8
1461KECCAK256
1462DUP8
1463JUMPDEST
1464DUP4
1465DUP2
1466LT
1467PUSH20x18c0
146aJUMPI
146bPOP
146cPOP
146dPOP
146ePOP
146fPUSH20x1483
1472PUSH20x0224
1475PUSH10x04
1477CALLDATALOAD
1478ADD
1479PUSH10x04
147bCALLDATALOAD
147cPUSH10x04
147eADD
147fPUSH20x4681
1482JUMP
1483JUMPDEST
1484SWAP1
1485DUP5
1486DUP8
1487MSTORE
1488PUSH10x05
148aPUSH10x20
148cMSTORE
148dPUSH10x40
148fDUP8
1490KECCAK256
1491SWAP1
1492PUSH10x01
1494PUSH10x01
1496PUSH10x40
1498SHL
1499SUB
149aDUP4
149bGT
149cPUSH20x18ac
149fJUMPI
14a0PUSH20x14a9
14a3DUP4
14a4DUP4
14a5PUSH20x46e6
14a8JUMP
14a9JUMPDEST
14aaSWAP1
14abDUP8
14acMSTORE
14adPUSH10x20
14afDUP8
14b0KECCAK256
14b1DUP8
14b2JUMPDEST
14b3DUP4
14b4DUP2
14b5LT
14b6PUSH20x1891
14b9JUMPI
14baPOP
14bbPOP
14bcPOP
14bdPOP
14beDUP5
14bfJUMPDEST
14c0PUSH10xff
14c2DUP2
14c3AND
14c4PUSH10x0b
14c6DUP2
14c7LT
14c8ISZERO
14c9PUSH20x1521
14ccJUMPI
14cdPUSH10xff
14cfSWAP2
14d0DUP2
14d1PUSH20x14ea
14d4PUSH20x14e5
14d7PUSH10x01
14d9SWAP5
14daPUSH20x0244
14ddPUSH10x04
14dfCALLDATALOAD
14e0ADD
14e1PUSH20x472a
14e4JUMP
14e5JUMPDEST
14e6PUSH20x3beb
14e9JUMP
14eaJUMPDEST
14ebSWAP1
14ecDUP8
14edDUP11
14eeMSTORE
14efPUSH10x06
14f1PUSH10x20
14f3MSTORE
14f4PUSH10x40
14f6DUP11
14f7KECCAK256
14f8SWAP1
14f9PUSH0
14faMSTORE
14fbPUSH10x20
14fdMSTORE
14fePUSH10x01
1500PUSH10x01
1502PUSH10x40
1504SHL
1505SUB
1506PUSH10x40
1508PUSH0
1509KECCAK256
150aSWAP2
150bAND
150cPUSH10x01
150ePUSH10x01
1510PUSH10x40
1512SHL
1513SUB
1514NOT
1515DUP3
1516SLOAD
1517AND
1518OR
1519SWAP1
151aSSTORE
151bADD
151cAND
151dPUSH20x14bf
1520JUMP
1521JUMPDEST
1522POP
1523POP
1524SWAP3
1525SWAP1
1526DUP5
1527JUMPDEST
1528PUSH20x153c
152bPUSH20x03a4
152ePUSH10x04
1530CALLDATALOAD
1531ADD
1532PUSH10x04
1534CALLDATALOAD
1535PUSH10x04
1537ADD
1538PUSH20x4681
153bJUMP
153cJUMPDEST
153dSWAP1
153ePOP
153fDUP2
1540LT
1541ISZERO
1542PUSH20x15c7
1545JUMPI
1546DUP1
1547PUSH20x1567
154aPUSH10x01
154cSWAP3
154dPUSH20x1561
1550PUSH20x03a4
1553PUSH10x04
1555CALLDATALOAD
1556ADD
1557PUSH10x04
1559CALLDATALOAD
155aPUSH10x04
155cADD
155dPUSH20x4681
1560JUMP
1561JUMPDEST
1562SWAP1
1563PUSH20x3f70
1566JUMP
1567JUMPDEST
1568CALLDATALOAD
1569DUP6
156aDUP9
156bMSTORE
156cPUSH10x09
156ePUSH10x20
1570MSTORE
1571PUSH10x40
1573DUP9
1574KECCAK256
1575DUP2
1576DUP10
1577MSTORE
1578PUSH10x20
157aMSTORE
157bPUSH10xff
157dPUSH10x40
157fDUP10
1580KECCAK256
1581SLOAD
1582AND
1583PUSH20x15c1
1586JUMPI
1587PUSH20x15bb
158aSWAP1
158bDUP7
158cDUP10
158dMSTORE
158ePUSH10x09
1590PUSH10x20
1592MSTORE
1593PUSH10x40
1595DUP10
1596KECCAK256
1597DUP2
1598DUP11
1599MSTORE
159aPUSH10x20
159cMSTORE
159dPUSH10x40
159fDUP10
15a0KECCAK256
15a1DUP5
15a2PUSH10xff
15a4NOT
15a5DUP3
15a6SLOAD
15a7AND
15a8OR
15a9SWAP1
15aaSSTORE
15abDUP7
15acDUP10
15adMSTORE
15aePUSH10x08
15b0PUSH10x20
15b2MSTORE
15b3PUSH10x40
15b5DUP10
15b6KECCAK256
15b7PUSH20x473b
15baJUMP
15bbJUMPDEST
15bcADD
15bdPUSH20x1527
15c0JUMP
15c1JUMPDEST
15c2POP
15c3PUSH20x15bb
15c6JUMP
15c7JUMPDEST
15c8POP
15c9DUP4
15caDUP6
15cbSWAP4
15ccDUP5
15cdJUMPDEST
15ceDUP6
15cfPUSH20x15e3
15d2PUSH20x03c4
15d5PUSH10x04
15d7CALLDATALOAD
15d8ADD
15d9PUSH10x04
15dbCALLDATALOAD
15dcPUSH10x04
15deADD
15dfPUSH20x476f
15e2JUMP
15e3JUMPDEST
15e4SWAP1
15e5POP
15e6DUP3
15e7LT
15e8ISZERO
15e9PUSH20x1637
15ecJUMPI
15edPOP
15eeDUP1
15efPUSH20x1631
15f2PUSH20x160c
15f5PUSH10x01
15f7SWAP4
15f8PUSH20x0a87
15fbPUSH20x03c4
15fePUSH10x04
1600CALLDATALOAD
1601ADD
1602PUSH10x04
1604CALLDATALOAD
1605PUSH10x04
1607ADD
1608PUSH20x476f
160bJUMP
160cJUMPDEST
160dCALLDATALOAD
160ePUSH10x20
1610PUSH20x1628
1613DUP5
1614PUSH20x0a87
1617PUSH20x03c4
161aPUSH10x04
161cCALLDATALOAD
161dADD
161ePUSH10x04
1620CALLDATALOAD
1621PUSH10x04
1623ADD
1624PUSH20x476f
1627JUMP
1628JUMPDEST
1629ADD
162aCALLDATALOAD
162bSWAP1
162cDUP8
162dPUSH20x53dc
1630JUMP
1631JUMPDEST
1632ADD
1633PUSH20x15cd
1636JUMP
1637JUMPDEST
1638DUP1
1639SWAP5
163aSWAP2
163bPOP
163cPUSH10x09
163eDUP7
163fPUSH20x1647
1642DUP5
1643PUSH20x47b4
1646JUMP
1647JUMPDEST
1648ADD
1649DUP1
164aSLOAD
164bSWAP1
164cSWAP3
164dSWAP1
164ePUSH20x1660
1651SWAP1
1652PUSH10x01
1654PUSH10x01
1656PUSH10xa0
1658SHL
1659SUB
165aAND
165bDUP3
165cPUSH20x5509
165fJUMP
1660JUMPDEST
1661DUP3
1662SLOAD
1663DUP5
1664PUSH320xcfe82510d1c464fb22d59e8531313b14d3894bb1dfdec9de06b77b65afa87a76
1685PUSH10x20
1687PUSH10x40
1689MLOAD
168aSWAP4
168bPUSH10x01
168dPUSH10x01
168fPUSH10x40
1691SHL
1692SUB
1693DUP2
1694PUSH10xb0
1696SHR
1697AND
1698DUP6
1699MSTORE
169aPUSH10x01
169cDUP1
169dPUSH10xa0
169fSHL
16a0SUB
16a1AND
16a2SWAP4
16a3LOG3
16a4PUSH20x16b4
16a7PUSH20x16ae
16aaPUSH20x3cb4
16adJUMP
16aeJUMPDEST
16afSWAP2
16b0PUSH20x4e29
16b3JUMP
16b4JUMPDEST
16b5PUSH20x16bd
16b8DUP3
16b9PUSH20x3cf4
16bcJUMP
16bdJUMPDEST
16beMSTORE
16bfPUSH20x16c7
16c2DUP2
16c3PUSH20x3cf4
16c6JUMP
16c7JUMPDEST
16c8POP
16c9PUSH320x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8
16eaPUSH10x01
16ecPUSH10x01
16eePUSH10xa0
16f0SHL
16f1SUB
16f2AND
16f3SWAP1
16f4DUP2
16f5EXTCODESIZE
16f6ISZERO
16f7PUSH20x0833
16faJUMPI
16fbDUP3
16fcPUSH20x1719
16ffSWAP2
1700PUSH10x40
1702MLOAD
1703DUP1
1704SWAP4
1705DUP2
1706SWAP3
1707PUSH40x2728f271
170cPUSH10xe2
170eSHL
170fDUP4
1710MSTORE
1711PUSH10x04
1713DUP4
1714ADD
1715PUSH20x3d15
1718JUMP
1719JUMPDEST
171aSUB
171bDUP2
171cDUP4
171dDUP7
171eGAS
171fCALL
1720SWAP1
1721DUP2
1722ISZERO
1723PUSH20x1886
1726JUMPI
1727DUP4
1728SWAP2
1729PUSH20x1871
172cJUMPI
172dJUMPDEST
172ePOP
172fPOP
1730PUSH10x40
1732SWAP4
1733DUP5
1734MLOAD
1735SWAP1
1736PUSH20x173f
1739DUP7
173aDUP4
173bPUSH20x3b63
173eJUMP
173fJUMPDEST
1740PUSH10x01
1742DUP3
1743MSTORE
1744PUSH10x1f
1746NOT
1747DUP7
1748ADD
1749SWAP7
174aDUP8
174bCALLDATASIZE
174cPUSH10x20
174eDUP6
174fADD
1750CALLDATACOPY
1751DUP7
1752MLOAD
1753SWAP2
1754PUSH20x175d
1757DUP9
1758DUP5
1759PUSH20x3b63
175cJUMP
175dJUMPDEST
175ePUSH10x01
1760DUP4
1761MSTORE
1762DUP9
1763CALLDATASIZE
1764PUSH10x20
1766DUP6
1767ADD
1768CALLDATACOPY
1769DUP8
176aMLOAD
176bSWAP1
176cPUSH40x82edfbd9
1771PUSH10xe0
1773SHL
1774DUP3
1775MSTORE
1776PUSH10x04
1778DUP3
1779ADD
177aMSTORE
177bPUSH10x20
177dDUP2
177ePUSH10x24
1780DUP2
1781DUP9
1782GAS
1783STATICCALL
1784SWAP1
1785DUP2
1786ISZERO
1787PUSH20x1867
178aJUMPI
178bDUP7
178cSWAP2
178dPUSH20x1832
1790JUMPI
1791JUMPDEST
1792POP
1793PUSH20x179b
1796DUP5
1797PUSH20x3cf4
179aJUMP
179bJUMPDEST
179cMSTORE
179dPUSH20x17a5
17a0DUP3
17a1PUSH20x3cf4
17a4JUMP
17a5JUMPDEST
17a6MSTORE
17a7DUP3
17a8EXTCODESIZE
17a9ISZERO
17aaPUSH20x0862
17adJUMPI
17aePUSH20x17cf
17b1SWAP3
17b2DUP5
17b3SWAP3
17b4DUP4
17b5DUP9
17b6MLOAD
17b7DUP1
17b8SWAP7
17b9DUP2
17baSWAP6
17bbDUP3
17bcSWAP5
17bdPUSH40xabf1570d
17c2PUSH10xe0
17c4SHL
17c5DUP5
17c6MSTORE
17c7PUSH10x04
17c9DUP5
17caADD
17cbPUSH20x47fd
17ceJUMP
17cfJUMPDEST
17d0SUB
17d1SWAP3
17d2GAS
17d3CALL
17d4DUP1
17d5ISZERO
17d6PUSH20x1828
17d9JUMPI
17daPUSH20x1813
17ddJUMPI
17deJUMPDEST
17dfPOP
17e0POP
17e1PUSH20x07e7
17e4SWAP3
17e5PUSH20x17f0
17e8DUP4
17e9MLOAD
17eaSWAP4
17ebDUP5
17ecPUSH20x3b63
17efJUMP
17f0JUMPDEST
17f1PUSH10x01
17f3DUP4
17f4MSTORE
17f5CALLDATASIZE
17f6PUSH10x20
17f8DUP5
17f9ADD
17faCALLDATACOPY
17fbSLOAD
17fcPUSH10x01
17fePUSH10x01
1800PUSH10xa0
1802SHL
1803SUB
1804AND
1805PUSH20x180d
1808DUP3
1809PUSH20x3cf4
180cJUMP
180dJUMPDEST
180eMSTORE
180fPUSH20x555f
1812JUMP
1813JUMPDEST
1814DUP2
1815PUSH20x181d
1818SWAP2
1819PUSH20x3b63
181cJUMP
181dJUMPDEST
181ePUSH20x0862
1821JUMPI
1822DUP4
1823DUP6
1824PUSH20x17de
1827JUMP
1828JUMPDEST
1829DUP5
182aMLOAD
182bRETURNDATASIZE
182cDUP5
182dDUP3
182eRETURNDATACOPY
182fRETURNDATASIZE
1830SWAP1
1831REVERT
1832JUMPDEST
1833SWAP6
1834POP
1835POP
1836PUSH10x20
1838DUP6
1839RETURNDATASIZE
183aPUSH10x20
183cGT
183dPUSH20x185f
1840JUMPI
1841JUMPDEST
1842DUP2
1843PUSH20x184e
1846PUSH10x20
1848SWAP4
1849DUP4
184aPUSH20x3b63
184dJUMP
184eJUMPDEST
184fDUP2
1850ADD
1851SUB
1852SLT
1853PUSH20x0da5
1856JUMPI
1857DUP9
1858SWAP5
1859MLOAD
185aDUP11
185bPUSH20x1791
185eJUMP
185fJUMPDEST
1860RETURNDATASIZE
1861SWAP2
1862POP
1863PUSH20x1841
1866JUMP
1867JUMPDEST
1868DUP9
1869MLOAD
186aRETURNDATASIZE
186bDUP9
186cDUP3
186dRETURNDATACOPY
186eRETURNDATASIZE
186fSWAP1
1870REVERT
1871JUMPDEST
1872DUP2
1873PUSH20x187b
1876SWAP2
1877PUSH20x3b63
187aJUMP
187bJUMPDEST
187cPUSH20x101c
187fJUMPI
1880DUP2
1881DUP8
1882PUSH20x172d
1885JUMP
1886JUMPDEST
1887PUSH10x40
1889MLOAD
188aRETURNDATASIZE
188bDUP6
188cDUP3
188dRETURNDATACOPY
188eRETURNDATASIZE
188fSWAP1
1890REVERT
1891JUMPDEST
1892PUSH10x01
1894SWAP1
1895PUSH10x20
1897PUSH20x189f
189aDUP6
189bPUSH20x3bd7
189eJUMP
189fJUMPDEST
18a0SWAP5
18a1ADD
18a2SWAP4
18a3DUP2
18a4DUP5
18a5ADD
18a6SSTORE
18a7ADD
18a8PUSH20x14b2
18abJUMP
18acJUMPDEST
18adPUSH40x4e487b71
18b2PUSH10xe0
18b4SHL
18b5DUP9
18b6MSTORE
18b7PUSH10x41
18b9PUSH10x04
18bbMSTORE
18bcPUSH10x24
18beDUP9
18bfREVERT
18c0JUMPDEST
18c1PUSH10x01
18c3SWAP1
18c4PUSH10x20
18c6PUSH20x18ce
18c9DUP6
18caPUSH20x3bd7
18cdJUMP
18ceJUMPDEST
18cfSWAP5
18d0ADD
18d1SWAP4
18d2DUP2
18d3DUP5
18d4ADD
18d5SSTORE
18d6ADD
18d7PUSH20x1463
18daJUMP
18dbJUMPDEST
18dcPUSH40x08e19609
18e1PUSH10xe4
18e3SHL
18e4DUP7
18e5MSTORE
18e6PUSH10x04
18e8DUP5
18e9SWAP1
18eaMSTORE
18ebPUSH10x24
18edDUP7
18eeREVERT
18efJUMPDEST
18f0POP
18f1PUSH10x09
18f3DUP6
18f4ADD
18f5SLOAD
18f6PUSH10x01
18f8PUSH10x01
18faPUSH10xa0
18fcSHL
18fdSUB
18feAND
18ffISZERO
1900PUSH20x1421
1903JUMP
1904JUMPDEST
1905POP
1906PUSH10x05
1908DUP6
1909ADD
190aSLOAD
190bISZERO
190cPUSH20x141a
190fJUMP
1910JUMPDEST
1911DUP1
1912PUSH20x191d
1915PUSH10x01
1917SWAP3
1918DUP5
1919PUSH20x41c9
191cJUMP
191dJUMPDEST
191eCALLDATALOAD
191fDUP2
1920DUP10
1921ADD
1922SSTORE
1923ADD
1924PUSH20x1403
1927JUMP
1928JUMPDEST
1929PUSH40x3b490093
192ePUSH10xe1
1930SHL
1931DUP8
1932MSTORE
1933PUSH10x04
1935DUP6
1936SWAP1
1937MSTORE
1938PUSH10x24
193aDUP8
193bREVERT
193cJUMPDEST
193dDUP12
193eDUP1
193fREVERT
1940JUMPDEST
1941PUSH10x20
1943DUP1
1944PUSH10x01
1946SWAP3
1947PUSH10x01
1949PUSH10x01
194bPUSH10x40
194dSHL
194eSUB
194fPUSH20x1957