Final Testnetexplorer K_J · Final Testnet · 48359
en

Contract

0xb3a91f3ea0c2c0455d45b282db0a74c74261b433

Address
0xb3a91f3ea0c2c0455d45b282db0a74c74261b433
Kind
verified contract FinalMorphMarker
Balance
0 vETH
Nonce
1
Code
8,714 bytes codehash 0x9337755345ccbf78f52f465761d1928eac2f9f8a559d75bbc5f23dbe1a6026b1

account tree

Tree
1 · accounts
Present
no leaf
Key
0x236318ebc72f4d7fe637e18feb31423c4f33826e68e0fef5d50a4340656b98b3
Live root
0x7f4d94d6a97e62130efbf1c3942376a5e3c24a0ea2b7abf27f9ebbe8810c1f41
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
FinalMorphMarker exact match · immutables masked
Compiler
v0.8.33+commit.64118f21
Optimizer
enabled · 200 runs
EVM version
prague
Verified
2026-09-14T11:30:31.318Z
Provenance
preverify-final-chain (forge artifact, bytecode compared against live code)

contracts/finalchain/FinalCertificate.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

contracts/finalchain/FinalChainInitializable.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
pragma solidity ^0.8.20;

import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";

/**
 * @title Final Chain Initializable
 * @notice The once-only initializer of a Final Chain state-plane contract that stands behind `FinalChainProxy`
 *         (ruled 2026-09-12: every plane contract does).
 *
 * @dev The proxy never re-runs an implementation's constructor, so a constructor that writes STORAGE — the
 *      trees' zero-hash ladder and live roots, a bootstrap admin, the supply's 100M — would leave the proxy's
 *      storage empty: the writes land in the implementation, which nothing reads through. Such a contract
 *      moves those writes into one internal `_setUp(...)` guarded by {initializer} and calls it from BOTH
 *      places: its constructor (a direct deploy — every Foundry fixture, every test — behaves exactly as
 *      before, and the bare implementation marks its OWN storage initialized, so nobody can initialize it
 *      later) and an external `initialize(...)`, which `FinalChainProxy`'s constructor runs by `delegatecall`
 *      in the proxy's storage. Constructor immutables (`registry`, `trees`, …) need none of this: they live in
 *      the implementation's code and read as constants through the proxy.
 *
 *      The flag lives in a namespaced slot, not in Solidity storage: inheriting this contract shifts no
 *      layout, and an implementation upgraded in place can never collide with it. An upgrade that appends
 *      storage seeds it through a new guarded function of its own — `initialize` runs once per proxy, ever.
 *
 *      A proxy deployed WITHOUT its init data is a live hole: `initialize` is external and the first caller
 *      would be the admin. The deploy tool refuses to place a proxy whose implementation declares
 *      `initialize` without running it, and reads {initialized} back before it continues.
 */
abstract contract FinalChainInitializable {
    /// @dev `bytes32(uint256(keccak256("final.chain.initialized")) - 1)`.
    bytes32 private constant INITIALIZED_SLOT = 0x1bf7ff51edde3507ea8edc0d02272dc3e66fd14d0a75a234f844ee7b236829d2;

    /// @notice The contract's storage was set up — by its constructor (a direct deploy) or by `initialize`
    ///         through its proxy.
    event Initialized();

    /// @notice `initialize` ran already in this storage — the constructor's, or a proxy's, once.
    error AlreadyInitialized();

    /// @dev Guards the one function that replays the constructor's storage writes. Sets the flag BEFORE the
    ///      body so a re-entrant call from inside the body cannot run it twice.
    modifier initializer() {
        StorageSlot.BooleanSlot storage flag = StorageSlot.getBooleanSlot(INITIALIZED_SLOT);
        if (flag.value) revert AlreadyInitialized();
        flag.value = true;
        _;
        emit Initialized();
    }

    /// @notice Whether this storage was set up. False on a proxy whose init data was not run — the state the
    ///         deploy tool refuses.
    function initialized() external view returns (bool) {
        return StorageSlot.getBooleanSlot(INITIALIZED_SLOT).value;
    }
}

contracts/finalchain/FinalChainPrecompiles.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

contracts/finalchain/FinalChainTime.sol

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

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

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

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

contracts/finalchain/FinalComplianceLeaves.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
pragma solidity ^0.8.24;

/**
 * @title Final Compliance Leaves
 * @notice The FOUR leaf families of tree 9 (compliance) — keys, structs and
 *         leaf hashes — as pure functions, byte for byte the specification
 *         (`arch/state-trees.md` § Tree 9, `arch/kyc-aml-data-placement.md` § 3).
 * @dev One definition, three readers: `FinalStateRecords` writes these leaves
 *      on the Final Chain (the registrar quorum through the typed door, and
 *      the presale ledger for the counters), `FinalPhiPresaleLedger` verifies
 *      an attestation field for field at admission (the hosting chains anchor
 *      only tree 1's and tree 8's roots, so the intake holds and the ledger
 *      decides), and the backend's encoder pins the same vectors
 *      (`tests/backend/tree9.test.js`). A field added here moves every key and
 *      hash at once — tree 1's rule.
 *
 *      Nothing in this tree names a person: the commitment and the nullifiers
 *      are hashes whose preimages hold the holder's secret; `walletBinding`
 *      folds the acting wallet into a hash the contract recomputes, so the
 *      address is never a field and a proof cannot be lent.
 */
library FinalComplianceLeaves {
    // ------------------------------------------------------------- branches

    /// @notice Branch 1 — the approved set, one leaf per approved Final Identity.
    uint8 internal constant BRANCH_APPROVALS = 1;
    /// @notice Branch 2 — revocations, keyed by the revoked subject.
    uint8 internal constant BRANCH_REVOCATIONS = 2;
    /// @notice Branch 3 — per-jurisdiction counters, mirrored from the presale ledger.
    uint8 internal constant BRANCH_COUNTERS = 3;
    /// @notice Branch 4 — minutes-lived action attestations.
    uint8 internal constant BRANCH_ATTESTATIONS = 4;

    // -------------------------------------------------------------- domains

    bytes32 internal constant DOMAIN_APPROVAL_KEY = keccak256("FinalStateTrees.key.approval.v01");
    bytes32 internal constant DOMAIN_REVOCATION_KEY = keccak256("FinalStateTrees.key.revocation.v01");
    bytes32 internal constant DOMAIN_COUNTER_KEY = keccak256("FinalStateTrees.key.jurisdictionCounter.v01");
    bytes32 internal constant DOMAIN_ATTESTATION_KEY = keccak256("FinalStateTrees.key.attestation.v01");

    bytes32 internal constant DOMAIN_APPROVAL_LEAF = keccak256("FINAL_KYC_APPROVAL_LEAF_v01");
    bytes32 internal constant DOMAIN_REVOCATION_LEAF = keccak256("FINAL_KYC_REVOKED_LEAF_v01");
    bytes32 internal constant DOMAIN_COUNTER_LEAF = keccak256("FINAL_JURISDICTION_COUNTER_LEAF_v01");
    bytes32 internal constant DOMAIN_ATTESTATION_LEAF = keccak256("FINAL_KYC_ATTESTATION_LEAF_v01");

    /// @dev The holder-side artefacts, for completeness of the one definition:
    ///      `commitment = keccak256(abi.encode(DOMAIN_KYC_COMMITMENT, secret, wallet, salt))`,
    ///      `nullifier = keccak256(abi.encode(DOMAIN_KYC_NULLIFIER, secret, actionType, salt))`.
    ///      Neither preimage ever reaches a contract.
    bytes32 internal constant DOMAIN_KYC_COMMITMENT = keccak256("FINAL_KYC_COMMITMENT_v01");
    bytes32 internal constant DOMAIN_KYC_NULLIFIER = keccak256("FINAL_KYC_NULLIFIER_v01");

    /// @notice The action types an attestation is scoped to — one nullifier per TYPE, never per instance.
    bytes32 internal constant ACTION_PRESALE_BUY = keccak256("PRESALE_BUY");
    bytes32 internal constant ACTION_MORPH = keccak256("MORPH");
    bytes32 internal constant ACTION_TRANSFER = keccak256("TRANSFER");

    /// @notice `RevocationLeaf.kind` values.
    uint8 internal constant SUBJECT_COMMITMENT = 0;
    uint8 internal constant SUBJECT_NULLIFIER = 1;

    /// @notice `RevocationLeaf.reasonCode` values — a code, never a narrative.
    uint16 internal constant REASON_SANCTIONS_DESIGNATION = 1;
    uint16 internal constant REASON_PEP_ESCALATION = 2;
    uint16 internal constant REASON_DOCUMENT_INVALID = 3;
    uint16 internal constant REASON_HOLDER_REQUEST = 4;
    uint16 internal constant REASON_POLICY_RETIRED = 5;
    uint16 internal constant REASON_OTHER = 255;

    // -------------------------------------------------------------- structs

    /// @notice Branch 1 — one approved Final Identity. The wallet is INSIDE `commitment`, never a field.
    struct ApprovalLeaf {
        /// @dev keccak256(abi.encode(DOMAIN_KYC_COMMITMENT, secret, wallet, salt)) — the join key.
        bytes32 commitment;
        /// @dev Unix seconds.
        uint64 approvedAt;
        /// @dev The document's expiry capped by the policy's credential life; a renewal rewrites the slot.
        uint64 credentialUntil;
        /// @dev The jurisdiction policy the approval was decided under (branch 0 pins its hash).
        uint32 policyVersion;
        /// @dev 0 = standard due diligence, 1 = enhanced.
        uint8 level;
        /// @dev Monotone per commitment.
        uint32 version;
    }

    /// @notice Branch 2 — one revoked subject: a commitment, or one of its live action nullifiers.
    struct RevocationLeaf {
        bytes32 subject;
        /// @dev `SUBJECT_COMMITMENT` or `SUBJECT_NULLIFIER`.
        uint8 kind;
        /// @dev Effective at the next block.
        uint64 revokedAt;
        /// @dev One of the `REASON_*` codes.
        uint16 reasonCode;
        uint32 policyVersion;
    }

    /// @notice Branch 3 — the presale ledger's authoritative counter for one (policy, bucket), mirrored so it is provable.
    struct CounterLeaf {
        uint32 policyVersion;
        uint16 bucket;
        /// @dev PHI admitted to buyers of this bucket under this policy.
        uint256 allocatedPhi;
        /// @dev The same in USD at the admitting marks — the exemption caps are USD-denominated.
        uint256 allocatedUsdMicros;
        /// @dev The bucket's cap under this policy, from the policy document.
        uint256 capPhi;
        /// @dev Count of admitted purchases.
        uint64 purchases;
        /// @dev The admitting block of the last increment.
        uint64 asOfBlock;
    }

    /// @notice Branch 4 — one minutes-lived attestation per (identity, action type).
    struct AttestationLeaf {
        /// @dev Action-scoped; unlinkable to the commitment and to other action types.
        bytes32 nullifier;
        bytes32 actionType;
        /// @dev keccak256(abi.encode(nullifier, wallet)) — recomputed by the reader with the acting wallet.
        bytes32 walletBinding;
        /// @dev Unix seconds.
        uint64 issuedAt;
        /// @dev issuedAt + attestationLife — MINUTES; freshness, read at execution.
        uint64 expiresAt;
        uint32 policyVersion;
        /// @dev The jurisdiction CLASS the counters key on — never a country.
        uint16 bucket;
        /// @dev Monotone per nullifier; each on-demand issue rewrites the slot.
        uint32 version;
    }

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

    function approvalKeyFor(bytes32 commitment) internal pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_APPROVAL_KEY, commitment));
    }

    function revocationKeyFor(bytes32 subject) internal pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_REVOCATION_KEY, subject));
    }

    function counterKeyFor(uint32 policyVersion, uint16 bucket) internal pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_COUNTER_KEY, policyVersion, bucket));
    }

    function attestationKeyFor(bytes32 nullifier) internal pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ATTESTATION_KEY, nullifier));
    }

    /// @notice The binding an attestation carries for the wallet that will act with it.
    function walletBindingFor(bytes32 nullifier, address wallet) internal pure returns (bytes32) {
        return keccak256(abi.encode(nullifier, wallet));
    }

    // --------------------------------------------------------------- hashes

    function approvalLeafHash(ApprovalLeaf memory leaf) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_APPROVAL_LEAF,
                leaf.commitment,
                leaf.approvedAt,
                leaf.credentialUntil,
                leaf.policyVersion,
                leaf.level,
                leaf.version
            )
        );
    }

    function revocationLeafHash(RevocationLeaf memory leaf) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(DOMAIN_REVOCATION_LEAF, leaf.subject, leaf.kind, leaf.revokedAt, leaf.reasonCode, leaf.policyVersion)
        );
    }

    function counterLeafHash(CounterLeaf memory leaf) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_COUNTER_LEAF,
                leaf.policyVersion,
                leaf.bucket,
                leaf.allocatedPhi,
                leaf.allocatedUsdMicros,
                leaf.capPhi,
                leaf.purchases,
                leaf.asOfBlock
            )
        );
    }

    function attestationLeafHash(AttestationLeaf memory leaf) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ATTESTATION_LEAF,
                leaf.nullifier,
                leaf.actionType,
                leaf.walletBinding,
                leaf.issuedAt,
                leaf.expiresAt,
                leaf.policyVersion,
                leaf.bucket,
                leaf.version
            )
        );
    }
}

contracts/finalchain/FinalIdentityRegistry.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /**
     * @notice The constructor's storage write, for a registry behind `FinalChainProxy` — whose upgrade
     *         authority is this registry itself: the proxy is built with its own address as `registry`.
     *         Runs once, in the proxy's constructor; `AlreadyInitialized` afterwards and on a direct deploy.
     * @param admin The bootstrap registrar.
     */
    function initialize(address admin) external {
        _setUp(admin);
    }

    /// @dev The bootstrap admin is storage (cleared by {sealBootstrap}), so a proxy needs it replayed.
    function _setUp(address admin) internal initializer {
        bootstrapAdmin = admin;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        accountOfCertificate[live.certHash] = account;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

contracts/finalchain/FinalMorphMarker.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy and operate this marker as part of a
//    Final DeFi Protocol chain, and may record a morph decision in it under
//    the authority the chain recognises.
// 2. Integrators, auditors, and node operators may read the decisions it
//    records and the carve-out each one consumed, as part of their
//    integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this marker or a competing risk or settlement
//    plane derived from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.24;

import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalStateRecords, MORPH_DIRECTION_SHORT} from "./FinalStateRecords.sol";
import {FinalPlaneSweep} from "./FinalPlaneSweep.sol";

/**
 * @title FinalMorphMarker
 * @notice The morph knockout, decided ON this chain (the design review, adjusted and
 *         a morph's carve-out is its entire risk budget,
 *         and the moment adverse price movement consumes it the position is
 *         knocked out — marked here, swept at the daily reconciliation, and
 *         restorable by quorum in between.
 *
 * @dev **Why a contract, and why permissionless.** The ruling wants knockouts
 * evaluated at every price write — the 1 s oracle cadence IS the tick — with
 * no keeper and nothing polling. So the mark is not an assertion anyone makes:
 * `mark` recomputes the knockout from the trees this chain already holds
 * (tree 2's stored `PhiAccountLeaf`, tree 4's stored prices) and refuses
 * anything that does not follow from them. Whoever calls carries no authority
 * and pays the (free) gas — the oracle round calls it right after its own
 * price write because it is already there, and anyone else may too. That is
 * the same shape as `FinalAccountLedger.submitRequest`: the state decides,
 * the sender merely delivers.
 *
 * **What a mark is, and is not.** A mark records that the barrier was touched
 * at an observed pair of prices. It moves no value: the CAPTURE — despawn of
 * 100 % of the carve-out, protocol revenue — settles at the daily
 * reconciliation pass, batched, by the PHI publisher quorum folding the
 * marked position out of the tree-2 row. The gap between mark and sweep is
 * deliberate: it is the REPAIR LANE. A poisoned feed marks positions wrongly;
 * the feed is repaired and the marks restored by REGISTRAR QUORUM — never
 * unilaterally — before the sweep finalizes them. A mark whose position was
 * already swept is history, not state: restoring it moves nothing, because
 * the row no longer carries the position.
 *
 * **The arithmetic is division-free.** A morph is long its asset against USD,
 * cash-settled in PHI. With `B = phiAmount × phiUsdNow` (the budget) and
 * `A = assetUnits × (assetUsdAtOpen − assetUsdNow)` (the adverse PnL, where
 * `assetUnits = phiAmount × phiUsdAtOpen × leveragePct / (100 × assetUsdAtOpen)`),
 * the knockout `A ≥ B` rearranges to
 *
 *   phiUsdAtOpen × leveragePct × (assetUsdAtOpen − assetUsdNow)
 *     ≥ 100 × phiUsdNow × assetUsdAtOpen
 *
 * — `phiAmount` cancels, every term is a multiplication, and the comparison
 * is exact. Leverage scales the SPEED and never the size, visibly: it
 * multiplies the left side alone.
 *
 * **Running fees deliberately do not mark.** The phi-tree record derives an
 * open exposure's running fee off-chain (it is a function of `openedAt` and
 * tree-6 policy); folding that schedule in here would let a mis-set policy
 * row knock out healthy positions. So this contract marks on price alone —
 * strictly LATE, never early — and the reconciliation, which computes the
 * exact figure, captures fee-consumed positions the tick could not see. A
 * late mark is the free look the fee schedule already prices; an early mark
 * would be taking a user's position over a barrier it never touched.
 *
 * **PHI-01's polarity, kept.** A missing or zero price refuses the mark
 * (`UnusableMark`) — an unmarkable position is not a dead one. Zero prices
 * are exactly what a broken feed publishes, and the one thing a broken feed
 * must never do here is liquidate anyone.
 */
contract FinalMorphMarker is FinalPlaneSweep {
    // ------------------------------------------------------------- constants

    /// @dev `FinalStateRecords.Morph.state` of an open exposure — the only
    ///      state a knockout applies to. The enum is `1 open | 2 closing |
    ///      3 liquidating` (see `FinalStateRecords.Morph`); 0 is NO state, so
    ///      an absent or zeroed morph can never read as open here.
    uint8 private constant MORPH_STATE_OPEN = 1;

    /// @dev Action id for `configure`, distinct per contract exactly as the
    ///      ledger's and the trees' are.
    bytes32 private constant ACTION_CONFIGURE = keccak256("FinalMorphMarker.configure.v01");
    /// @dev Action id for `restore` — the repair lane's quorum action.
    bytes32 private constant ACTION_RESTORE = keccak256("FinalMorphMarker.restore.v01");

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

    /// @notice Where quorums resolve. Immutable like the ledger's: a registry
    ///         supplied per call could be anyone's.
    FinalIdentityRegistry public immutable registry;
    /// @notice The records behind tree 2 (the morph rows) and tree 4 (the
    ///         marks) live here — `FinalStateRecords`, the typed companion of
    ///         the trees.
    FinalStateRecords public immutable records;

    /// @notice Tree-4 identity of PHI — the budget side of every knockout.
    bytes32 public phiAssetId;
    /// @notice The quote asset the USD marks are denominated in (tree-4
    ///         `quoteAsset`); the chainRef used is 0, the composite row.
    bytes32 public usdQuoteAsset;
    /// @notice Restore burns one nonce per quorum round, so a repair cannot be
    ///         replayed onto marks it never named.
    uint64 public restoreNonce;

    struct KnockoutMark {
        /// @dev Block the barrier touch was recorded at.
        uint64 markedAtBlock;
        /// @dev Block a registrar-quorum repair cleared it; 0 = standing.
        uint64 restoredAtBlock;
        /// @dev The observed pair, kept so the mark is auditable against the
        ///      tree-4 history it was computed from.
        uint256 phiUsdAtMark;
        /// @dev The asset's USD price at the moment the knockout was marked, carried so the decision can be
        ///       re-derived later from the record rather than from whatever the price is when someone looks.
        uint256 assetUsdAtMark;
    }

    /// @dev Keyed by `markKey` — wallet, chain, index AND `openedAt`, so a
    ///      slot reused by a later position never inherits a stale mark.
    mapping(bytes32 => KnockoutMark) private _marks;

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

    /// @notice The marker was bound to the asset it prices and the asset it quotes against.
    /// @param phiAssetId The collateral asset.
    /// @param usdQuoteAsset The asset prices are quoted in.
    event MarkerConfigured(bytes32 phiAssetId, bytes32 usdQuoteAsset);
    /// @notice A morph was knocked out — its carve-out is spent and the position is closed.
    /// @dev The knockout is decided on this chain, against published prices, so the decision is re-derivable from
    ///       public state rather than being a claim by whichever process observed the price.
    event MorphKnockedOut(
        address indexed wallet,
        uint64 indexed chainId,
        uint256 indexed morphIndex,
        uint64 openedAt,
        uint256 phiUsd,
        uint256 assetUsd
    );
    /// @notice A standing knockout mark was withdrawn and the morph returned to open.
    /// @param wallet The account holding the morph.
    /// @param chainId The chain the morph is on.
    /// @param morphIndex Which morph on that account.
    /// @param openedAt When the morph was originally opened.
    event MorphMarkRestored(address indexed wallet, uint64 indexed chainId, uint256 indexed morphIndex, uint64 openedAt);

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

    /// @notice Thrown when the marker is used before it has been bound to its assets.
    error MarkerNotConfigured();
    /// @notice Thrown when the named account holds no collateral position on that chain.
    /// @param wallet The account.
    /// @param chainId The chain.
    error UnknownPhiAccount(address wallet, uint64 chainId);
    /// @notice Thrown when the named morph index does not exist on that account.
    /// @param wallet The account.
    /// @param chainId The chain.
    /// @param morphIndex The index.
    error NoSuchMorph(address wallet, uint64 chainId, uint256 morphIndex);
    /// @notice Thrown when a morph that is not open is marked.
    /// @param state The state it was actually in.
    error MorphNotOpen(uint8 state);
    /// @notice A missing, zero or malformed input that must refuse the mark
    ///         rather than decide it — a broken feed must never liquidate.
    error UnusableMark(string what);
    /// @notice Thrown when a mark is submitted for a morph whose carve-out is not actually exhausted.
    /// @dev The condition is checked against published prices rather than asserted by the caller, so a mark is
    ///       a claim anyone can refute rather than one anyone can make.
    error NotKnockedOut();
    /// @notice Thrown when a mark that already stands is submitted again.
    /// @param markKey The standing mark.
    error AlreadyMarked(bytes32 markKey);
    /// @notice Thrown when a mark that does not stand is withdrawn.
    /// @param markKey The absent mark.
    error MarkNotStanding(bytes32 markKey);

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

    /// @dev The precompile probe pins deployment to Final Chain, exactly as
    ///      the ledger's does: the registrar quorum `restore` consumes would
    ///      verify nothing anywhere else.
    constructor(FinalIdentityRegistry registry_, FinalStateRecords records_) {
        FinalChainPrecompiles.assertAvailable();
        registry = registry_;
        records = records_;
    }

    // ------------------------------------------------------------ configure

    /**
     * @notice Pin the two tree-4 identities every knockout reads.
     * @dev The registry's bootstrap admin alone while its window is open, the
     * sealed `ROLE_REGISTRAR` quorum afterwards — the ledger's exact pattern.
     * Re-callable: the oracle key conventions are the publishers', and a
     * marker pinned to a retired key would refuse every mark forever.
     */
    function configure(
        bytes32 phiAssetId_,
        bytes32 usdQuoteAsset_,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (registry.bootstrapSealed() || msg.sender != registry.bootstrapAdmin()) {
            registry.requireRegistrarQuorum(
                ACTION_CONFIGURE, keccak256(abi.encode(phiAssetId_, usdQuoteAsset_)), anchorBlock, approvals
            );
        }
        if (phiAssetId_ == bytes32(0)) revert UnusableMark("phiAssetId");
        phiAssetId = phiAssetId_;
        usdQuoteAsset = usdQuoteAsset_;
        emit MarkerConfigured(phiAssetId_, usdQuoteAsset_);
    }

    // ----------------------------------------------------------------- mark

    /// @notice One position to (attempt to) mark.
    struct MarkRef {
        /// @dev The account holding the morph.
        address wallet;
        /// @dev The chain the morph is on.
        uint64 chainId;
        /// @dev Which morph on that account.
        uint256 morphIndex;
    }

    /**
     * @notice Record the knockout of one open morph. **Permissionless**: the
     * verdict is recomputed here from tree state, so the sender carries no
     * authority — the oracle round calls this right after its price write
     * because it is already in flight, and anyone else may as well.
     */
    function mark(address wallet, uint64 chainId, uint256 morphIndex) public {
        if (phiAssetId == bytes32(0)) revert MarkerNotConfigured();

        (FinalStateRecords.PhiAccountLeaf memory leaf, bool present) =
            records.phiAccountAt(records.phiAccountKey(wallet, chainId));
        if (!present) revert UnknownPhiAccount(wallet, chainId);
        if (morphIndex >= leaf.morphs.length) revert NoSuchMorph(wallet, chainId, morphIndex);
        FinalStateRecords.Morph memory m = leaf.morphs[morphIndex];
        if (m.state != MORPH_STATE_OPEN) revert MorphNotOpen(m.state);

        bytes32 key = markKey(wallet, chainId, morphIndex, m.openedAt);
        // Only a STANDING mark blocks: a restored one was the quorum saying
        // "that touch was not real", and a position it cleared can genuinely
        // die later — the fresh mark overwrites, with a fresh observed pair.
        if (_marks[key].markedAtBlock != 0 && _marks[key].restoredAtBlock == 0) revert AlreadyMarked(key);

        (uint256 phiUsd, uint256 assetUsd) = _usableMarks(m);

        // The division-free barrier (header): adverse price movement alone —
        // strictly late, never early; fees settle at the sweep. Adverse is a
        // fall for a long and a rise for a short (M1): the barrier is evaluated
        // on the signed exposure, the same inequality either way.
        (bool moved, uint256 adverse) = _adverseMove(m, assetUsd);
        if (!moved) revert NotKnockedOut();
        if (m.phiUsdAtOpen * m.leveragePct * adverse < 100 * phiUsd * m.assetUsdAtOpen) {
            revert NotKnockedOut();
        }

        _marks[key] = KnockoutMark({
            markedAtBlock: uint64(block.number),
            restoredAtBlock: 0,
            phiUsdAtMark: phiUsd,
            assetUsdAtMark: assetUsd
        });
        emit MorphKnockedOut(wallet, chainId, morphIndex, m.openedAt, phiUsd, assetUsd);
    }

    /// @notice The batch the oracle round submits after a price write. Refusals
    /// are PER POSITION and silent here (a batch mixing one dead position with
    /// nine healthy ones must land the one), so callers filter with
    /// {isKnockedOut} first and treat a fully-refused batch as their own bug.
    function markMany(MarkRef[] calldata refs) external returns (uint256 marked) {
        for (uint256 i = 0; i < refs.length; i++) {
            // Identical checks to `mark`, tolerated per entry: try/catch over
            // an external self-call would re-enter; a private probe is cheaper.
            if (isKnockedOut(refs[i].wallet, refs[i].chainId, refs[i].morphIndex)) {
                mark(refs[i].wallet, refs[i].chainId, refs[i].morphIndex);
                marked += 1;
            }
        }
    }

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

    /**
     * @notice The repair lane: clear wrongly-recorded marks after a feed
     * incident, under the registrar quorum. `openedAt` rides each ref so the
     * quorum restores the exact position instances it examined.
     * @dev A restored mark is CLEARED, not annotated away: the same position
     * can be marked again by the next genuine barrier touch. Finality is the
     * sweep's, not this contract's — a mark whose position the reconciliation
     * already folded out is history, and restoring it moves nothing.
     */
    struct RestoreRef {
        /// @dev The account holding the morph.
        address wallet;
        /// @dev The chain the morph is on.
        uint64 chainId;
        /// @dev Which morph on that account.
        uint256 morphIndex;
        uint64 openedAt;
    }

    /// @notice Withdraws a standing knockout mark and returns the morph to open.
    /// @dev The counterpart to marking, and it exists because a mark is a claim about a price at a moment: if
    ///       the condition did not actually hold, the position must be recoverable rather than closed by an
    ///       assertion nobody could contest.
    function restore(
        RestoreRef[] calldata refs,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        uint64 n = restoreNonce;
        registry.requireRegistrarQuorum(
            ACTION_RESTORE, keccak256(abi.encode(n, refs)), anchorBlock, approvals
        );
        restoreNonce = n + 1;
        for (uint256 i = 0; i < refs.length; i++) {
            RestoreRef calldata r = refs[i];
            bytes32 key = markKey(r.wallet, r.chainId, r.morphIndex, r.openedAt);
            KnockoutMark storage k = _marks[key];
            if (k.markedAtBlock == 0 || k.restoredAtBlock != 0) revert MarkNotStanding(key);
            k.restoredAtBlock = uint64(block.number);
            emit MorphMarkRestored(r.wallet, r.chainId, r.morphIndex, r.openedAt);
        }
    }

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

    /// @notice The mark's identity: slot AND instance, so a reused morph slot
    /// never inherits its predecessor's death.
    function markKey(address wallet, uint64 chainId, uint256 morphIndex, uint64 openedAt)
        public
        pure
        returns (bytes32)
    {
        return keccak256(abi.encode(wallet, chainId, morphIndex, openedAt));
    }

    /// @notice The stored mark, verbatim (zeroed struct = never marked).
    function markOf(address wallet, uint64 chainId, uint256 morphIndex, uint64 openedAt)
        external
        view
        returns (KnockoutMark memory)
    {
        return _marks[markKey(wallet, chainId, morphIndex, openedAt)];
    }

    /// @notice Whether a STANDING mark exists — marked and not restored. This
    /// is what the reconciliation sweep reads per open position.
    function isMarked(address wallet, uint64 chainId, uint256 morphIndex, uint64 openedAt)
        external
        view
        returns (bool)
    {
        KnockoutMark storage k = _marks[markKey(wallet, chainId, morphIndex, openedAt)];
        return k.markedAtBlock != 0 && k.restoredAtBlock == 0;
    }

    /// @notice Would `mark` succeed right now? The oracle round's filter, and
    /// anyone's dry run. False for every refusal shape — unknown account,
    /// closed morph, unusable price, healthy position, already marked.
    function isKnockedOut(address wallet, uint64 chainId, uint256 morphIndex) public view returns (bool) {
        if (phiAssetId == bytes32(0)) return false;
        (FinalStateRecords.PhiAccountLeaf memory leaf, bool present) =
            records.phiAccountAt(records.phiAccountKey(wallet, chainId));
        if (!present || morphIndex >= leaf.morphs.length) return false;
        FinalStateRecords.Morph memory m = leaf.morphs[morphIndex];
        if (m.state != MORPH_STATE_OPEN) return false;
        KnockoutMark storage existing = _marks[markKey(wallet, chainId, morphIndex, m.openedAt)];
        if (existing.markedAtBlock != 0 && existing.restoredAtBlock == 0) return false;

        (bool usable, uint256 phiUsd, uint256 assetUsd) = _tryMarks(m);
        if (!usable) return false;
        (bool moved, uint256 adverse) = _adverseMove(m, assetUsd);
        if (!moved) return false;
        return m.phiUsdAtOpen * m.leveragePct * adverse >= 100 * phiUsd * m.assetUsdAtOpen;
    }

    /// @dev The adverse price movement of an exposure since its open, in the asset's USD quote: a FALL for a
    ///      long, a RISE for a short (M1). `moved` is false when the price sits at or on the favourable side of
    ///      the open mark — a healthy position, whatever the leverage.
    function _adverseMove(FinalStateRecords.Morph memory m, uint256 assetUsd)
        private
        pure
        returns (bool moved, uint256 adverse)
    {
        if (m.direction == MORPH_DIRECTION_SHORT) {
            if (assetUsd <= m.assetUsdAtOpen) return (false, 0);
            unchecked {
                adverse = assetUsd - m.assetUsdAtOpen;
            }
        } else {
            if (m.assetUsdAtOpen <= assetUsd) return (false, 0);
            unchecked {
                adverse = m.assetUsdAtOpen - assetUsd;
            }
        }
        return (true, adverse);
    }

    // -------------------------------------------------------------- private

    /// @dev The two current marks, REFUSING anything unusable.
    function _usableMarks(FinalStateRecords.Morph memory m)
        private
        view
        returns (uint256 phiUsd, uint256 assetUsd)
    {
        bool usable;
        (usable, phiUsd, assetUsd) = _tryMarks(m);
        if (!usable) revert UnusableMark("price");
    }

    /// @dev Both prices from the composite tree-4 rows (`chainRef = 0`),
    /// denominated in the configured quote. Unusable when either row is
    /// absent or zero, or the morph's own open marks are malformed.
    function _tryMarks(FinalStateRecords.Morph memory m)
        private
        view
        returns (bool usable, uint256 phiUsd, uint256 assetUsd)
    {
        if (m.phiUsdAtOpen == 0 || m.assetUsdAtOpen == 0 || m.leveragePct == 0) {
            return (false, 0, 0);
        }
        (FinalStateRecords.OraclePriceLeaf memory phiLeaf, bool phiPresent) =
            records.oraclePriceFor(phiAssetId, bytes32(0), usdQuoteAsset);
        (FinalStateRecords.OraclePriceLeaf memory assetLeaf, bool assetPresent) =
            records.oraclePriceFor(m.asset, bytes32(0), usdQuoteAsset);
        if (!phiPresent || !assetPresent || phiLeaf.price == 0 || assetLeaf.price == 0) {
            return (false, 0, 0);
        }
        return (true, phiLeaf.price, assetLeaf.price);
    }

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

    /// @dev This contract's configuration gate reads the membership registry it
    /// was constructed against, so the sweep authority reads the same one.
    function _sweepRegistry() internal view override returns (FinalIdentityRegistry) {
        return registry;
    }

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

contracts/finalchain/FinalPlaneSweep.sol

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

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

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

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

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

contracts/finalchain/FinalPqQuorum.sol

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

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

/**
 * @title FinalPqQuorum
 * @notice K-of-N approval where the signatures are post-quantum and the chain
 *         is what checks them.
 *
 * @dev This library is the reason Final Chain exists in this design.
 *
 * `FinalBackend/src/pq/credential.js` carries a rule it had to enforce in code
 * because nothing else could: **a surface whose signature is verified on chain
 * cannot be PQ.** A co-signer approval reaching `FinalRootAuthority` is checked
 * by ECDSA/ERC-1271 in Solidity, so a PQ co-signer would produce approvals the
 * contract cannot read, and the quorum would stop reaching threshold with
 * nothing in any log naming the cause. `PQ_SURFACE` and `assertBackendVerified`
 * exist to keep anyone from crossing that line by accident.
 *
 * Here the line is gone. The precompiles verify ML-DSA-87 and
 * SLH-DSA-SHAKE-256s natively, so a quorum can be PQ *and* on chain, and
 * "the backend says these four signatures verified" becomes "these four
 * signatures verify, and any node re-derives that independently".
 *
 * ## Three rules, each closing a specific hole
 *
 * 1. **Keys come from the registry, never from calldata.** A key passed as an
 *    argument proves nothing — anyone with a keypair can sign under it. This is
 *    the difference between a 4-of-5 quorum and a 1-of-1 held by whoever built
 *    the transaction.
 *
 * 2. **Signers strictly ascending.** One comparison per entry rejects duplicates
 *    outright, so a single member cannot supply four approvals and satisfy a
 *    threshold of four. The alternative — an O(n²) seen-check — is the same
 *    guarantee with more ways to get it wrong.
 *
 * 3. **The digest binds chain id and verifying contract.** Without both, an
 *    approval collected for one contract is replayable against another with the
 *    same payload shape, and an approval from the test chain is replayable on
 *    the production one. These co-signers hold one key across environments.
 *
 * ## Which algorithm
 *
 * The stack splits its keys by hardness assumption, not by convenience:
 * ML-DSA-87 (lattice) signs transactions, SLH-DSA-SHAKE-256s (hash-based) signs
 * identity. Two families, so one cryptanalytic result cannot take both.
 *
 * So an action inherits the class of what it authorizes. Advancing a state root
 * is operational and high-cadence: transaction class. Registering or revoking
 * an identity is the thing the access class exists for. `ALG_ANY` is available
 * and should be used sparingly — accepting either means a break in one family
 * takes the quorum.
 *
 * A MEMBERSHIP action — the registrar quorum that admits, re-roles or revokes
 * an identity and upgrades a plane contract — 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. 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 OPERATIONAL action — a bundle root or payload appended to
 * the log, a settlement leaf, an account-state write, a tree write, a PHI
 * movement — takes the ML-DSA-87 approval alone (the user's ruling of 12 Sep
 * 2026, arch/quorum-signing-ml-dsa.md Q1/Q4): the SLH-DSA family is exercised
 * at the boundary where a member JOINS — the joiner's own proof of possession
 * over the admission digest, verified here through `0x0205` — and by a holder
 * on its ledger actions, not on every bundle. An SLH-DSA seal costs a Cloud Run
 * co-signer about forty seconds per digest, and the fleet paid it once per
 * member per bundle; ML-DSA-87 signs in milliseconds under the key the member
 * already votes with.
 *
 * 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 membership-class quorum is ~119 KB of calldata. That is affordable
 * here only because this is our own chain and membership changes are rare. 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 by the membership class (the registrar quorum); an
        /// operational action never reads it, so it is empty there.
        bytes seal;
    }

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

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

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

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

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

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

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

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

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

            valid++;
        }

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

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

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

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

contracts/finalchain/FinalStateRecords.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy this records contract alongside the state
//    trees of a Final DeFi Protocol chain, and may operate that chain.
// 2. Integrators, indexers, operators and end users may read every record it
//    holds, derive the keys and leaf hashes it defines, and publish records
//    through it under the quorum of the tree concerned, as part of their
//    integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this records contract or a competing price, PHI
//    or vAsset record derived from it without permission prior to the Change
//    Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalStateTrees} from "./FinalStateTrees.sol";
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
import {ILeverageCapSource} from "./ILeverageCapSource.sol";
import {FinalPlaneSweep} from "./FinalPlaneSweep.sol";
import {FinalComplianceLeaves} from "./FinalComplianceLeaves.sol";

/// @dev `Morph.direction` of a long morph: exposure to the asset rising against USD (M1).
uint8 constant MORPH_DIRECTION_LONG = 0;
/// @dev `Morph.direction` of a short morph: exposure to the asset falling — mirrors the intent body's
/// settings bit 4 (SHORT). File scope, like the wallet's domain constants: the marker and the tests import
/// the one declaration (a contract's own constants are not reachable through its type from outside), and
/// the leaf hash refuses any other value (`InvalidMorphDirection`).
uint8 constant MORPH_DIRECTION_SHORT = 1;

/**
 * @title Final State Records
 * @notice The RECORDS behind the typed trees — 2 (PHI), 3 (vAsset) and
 *         4 (oracle): their leaf shapes, keys, hashes, the typed setters and
 *         the value reads.
 *
 * @dev A Merkle tree stores commitments, and a commitment is not an answer:
 * `FinalStateTrees.leafOf` proves that SOME price was published for a key
 * without saying what it was. This contract holds the preimage beside the
 * commitment so a consumer on this chain reads the value in one call and a
 * consumer anywhere proves it against the round root with `proofFor`.
 *
 * It is a companion, not a second authority. Every typed write computes the
 * key and the leaf hash from the struct and hands them to
 * `FinalStateTrees.writeTyped`, which admits this contract alone
 * (`typedWriter`) and runs the SAME quorum check, over the SAME digest, with
 * the SAME per-tree nonce as every other write to those trees — the typed
 * entrypoints choose the PREIMAGE, never the authorization, and a publisher
 * signs one digest whichever door a batch came through. `setLeaves` refuses
 * the three typed trees outright, so the value here can never drift from the
 * commitment there: both halves are written by one loop, and there is no
 * ordering in which one lands and the other does not.
 *
 * It is a separate contract from the trees for one reason: the two together
 * exceed EIP-170's deployed-code ceiling. The split costs one external call
 * per typed write and buys both halves room to grow without competing for the
 * same byte budget.
 *
 * **Where this runs.** The project's own reth-based chains and nowhere else.
 * It reaches the trees, which resolve every signer through a registry that
 * verifies post-quantum signatures in precompiles those chains alone provide,
 * so a deployment anywhere else could not authorize a write. Nothing under
 * `contracts/` outside the Final Chain directory imports it, and it takes part
 * in no CREATE2 derivation — its address is whatever its deploy transaction
 * produced. Gas is deliberately NOT a design constraint here and must not be
 * optimised for: every leaf is stored in full beside its hash, and the whole
 * point of that redundancy is that a reader never has to reconstruct anything.
 *
 * **Immutable, and behind no proxy.** Any change to this surface is a redeploy
 * at a new address, after which the trees must be repointed at it through
 * `setTypedWriter` and every service reading records has to follow. The
 * records themselves do not travel: a fresh deployment starts with empty
 * preimages behind roots that still commit to the old values, so the
 * publishers must republish before the value reads are usable again.
 */
contract FinalStateRecords is FinalPlaneSweep {
    // ---------------------------------------------------------------- trees

    /// @notice The trees this contract holds the records for. Immutable: a
    /// record that could be re-pointed is a record that proves nothing.
    FinalStateTrees public immutable trees;

    /// @notice Per-(asset, chain) leverage ceilings — tree 6 through
    ///         `FinalAssetRegistry.leverageCapPct`. Unset (zero) = the global
    ///         bound alone. Set by the bootstrap admin before the identity
    ///         registry seals; afterwards only a redeploy changes it.
    ILeverageCapSource public leverageCaps;

    /// @notice The per-chain leverage-cap source was installed.
    /// @param source The asset registry now consulted; zero leaves only the
    ///        global `[MIN_LEVERAGE_PCT, MAX_LEVERAGE_PCT]` bound in force.
    event LeverageCapSourceSet(address indexed source);
    /// @notice The presale ledger was pinned as the branch-3 writer.
    event PresaleLedgerSet(address indexed ledger);
    /// @notice `recordCounters` was called by anyone but `presaleLedger`.
    error NotPresaleLedger(address caller);

    /// @notice A morph's leverage is above the asset's cap on that chain.
    /// @param asset The tree-6 asset id the exposure tracks.
    /// @param chainId The chain the PHI record belongs to.
    /// @param leveragePct The leverage the publisher tried to record.
    /// @param capPct The ceiling that asset carries on that chain.
    error LeverageAboveCap(bytes32 asset, uint64 chainId, uint16 leveragePct, uint16 capPct);
    /// @notice Only the bootstrap admin, before the seal.
    /// @dev The one-shot configuration window. Afterwards the leverage-cap
    ///      pointer is fixed for the life of this deployment, so a compromised
    ///      operator cannot widen every asset's ceiling by repointing it.
    error NotConfigurationAuthority();

    /// @notice Tree id of the PHI record tree.
    /// @dev `FinalStateTrees.TREE_PHI` / `TREE_VASSET` / `TREE_ORACLE`, pinned
    ///      by test — constants rather than three external reads per write.
    ///      They must agree with the trees contract; a mismatch would publish
    ///      a record into the wrong tree under the wrong roster's quorum.
    uint8 internal constant TREE_PHI = 2;
    /// @notice Tree id of the vAsset issuance tree.
    uint8 internal constant TREE_VASSET = 3;
    /// @notice Tree id of the oracle price tree.
    uint8 internal constant TREE_ORACLE = 4;
    /// @dev `FinalStateTrees.TREE_COMPLIANCE` — tree 9; its four families live in `FinalComplianceLeaves`.
    uint8 internal constant TREE_COMPLIANCE = 9;

    // -------------------------------------------------------------- domains

    /// @dev Key domain for tree 2, one row per `(wallet, chain)`. Full-width
    /// hashes rather than the packed identifiers they came from — see
    /// `FinalStateTrees`' account key for why the width matters.
    bytes32 private constant DOMAIN_PHI_KEY = keccak256("FinalStateTrees.key.phi.v01");
    /// @dev Key domain for tree 3, one row per `(assetId, chainRef)`. There is
    ///      deliberately no holder in the key: holding a unit of the vAsset IS
    ///      the claim on the custody behind it, so a per-holder row would prove
    ///      nothing the vAsset's own chain does not already prove.
    bytes32 private constant DOMAIN_VASSET_KEY = keccak256("FinalStateTrees.key.vasset.v02");
    /// @dev Key domain for tree 4, one row per `(assetId, chainRef, quoteAsset)`.
    bytes32 private constant DOMAIN_ORACLE_KEY = keccak256("FinalStateTrees.key.oracle.v01");
    /// @notice Leaf domain for tree 2's PHI records.
    /// @dev One domain per tree, so a leaf lifted out of one and replayed into
    /// another hashes to something no consumer of the second will match. The
    /// trees share a slot space and a proof format; only the domain keeps a
    /// price from being provable as a PHI balance.
    bytes32 public constant DOMAIN_PHI_ACCOUNT_LEAF = keccak256("FINAL_PHI_ACCOUNT_LEAF_v02");
    /// @notice Leaf domain for tree 3's vAsset rows.
    bytes32 public constant DOMAIN_VASSET_LEAF = keccak256("FINAL_VASSET_LEAF_v02");
    /// @notice Leaf domain for tree 4's prices.
    bytes32 public constant DOMAIN_ORACLE_PRICE_LEAF = keccak256("FINAL_ORACLE_PRICE_LEAF_v01");
    /// @dev Separate from the leaf domain so a quote set can never be mistaken
    ///      for the leaf that commits to it.
    bytes32 public constant DOMAIN_ORACLE_INPUTS = keccak256("FINAL_ORACLE_PRICE_INPUTS_v01");

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

    /// @notice The PREIMAGE behind every tree-4 price leaf, by key.
    /// @dev A Merkle tree stores commitments, and a commitment is not an
    ///      answer: `leafOf` proves that SOME price was published for a key
    ///      without saying what it was. A tree holding only commitments leaves
    ///      every consumer to read a venue directly and disagree with every
    ///      other consumer, which is exactly the fragmentation the tree exists
    ///      to end. The preimage beside the commitment is what makes the tree
    ///      an answer rather than a receipt.
    ///
    ///      Bounded by KEY count, not by round count: a new round overwrites
    ///      its key rather than appending, so the store grows with the number
    ///      of assets and not with time.
    ///
    ///      The value cannot drift from the commitment because `setLeaves`
    ///      refuses these three trees outright — see `TypedTreeOnly`. Both
    ///      halves are written by one loop in the typed setter, so there is no
    ///      ordering in which one lands and the other does not.
    mapping(bytes32 key => OraclePriceLeaf) private _priceLeaf;
    /// @dev Tree 9, branch 1 — the approved set. Same discipline as `_priceLeaf`.
    mapping(bytes32 key => FinalComplianceLeaves.ApprovalLeaf) private _approvalLeaf;
    /// @dev Tree 9, branch 2 — revocations.
    mapping(bytes32 key => FinalComplianceLeaves.RevocationLeaf) private _revocationLeaf;
    /// @dev Tree 9, branch 3 — per-jurisdiction counters, the presale ledger's mirror.
    mapping(bytes32 key => FinalComplianceLeaves.CounterLeaf) private _counterLeaf;
    /// @dev Tree 9, branch 4 — action attestations.
    mapping(bytes32 key => FinalComplianceLeaves.AttestationLeaf) private _attestationLeaf;
    /// @notice The presale ledger — the ONE contract allowed to write branch-3 counters (`recordCounters`):
    ///         the counter it admits a buyer against is authoritative in the ledger and mirrored here in the
    ///         same transaction, so a cap is evidence and not a backend's word. Bootstrap admin before the
    ///         seal (`setPresaleLedger`), a redeploy after — the ledger's rules are its bytecode, which is the
    ///         whole argument for a quorum-free writer.
    address public presaleLedger;
    /// @notice The PREIMAGE behind every tree-2 PHI record, by key.
    /// @dev Same discipline as `_priceLeaf`. Written field by field through
    ///      `_storePhiLeaf`, because the record carries two dynamic members
    ///      that a shorter update must not leave stale rows behind in.
    mapping(bytes32 key => PhiAccountLeaf) private _phiLeaf;
    /// @notice The PREIMAGE behind every tree-3 vAsset row, by key.
    /// @dev Same discipline as `_priceLeaf`.
    mapping(bytes32 key => VAssetLeaf) private _vAssetLeaf;

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

    /// @notice A morph's direction is neither long (0) nor short (1).
    /// @param direction The value the publisher tried to record.
    error InvalidMorphDirection(uint8 direction);
    /// @notice A morph's leverage is outside `[MIN_LEVERAGE_PCT, MAX_LEVERAGE_PCT]`.
    /// @dev 5× is the hard cap of the design, not a schedule constant, so the
    ///      record refuses it rather than trusting every publisher to.
    error LeverageOutOfRange(uint16 leveragePct);

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

    /// @notice Pin the trees this contract holds the records for.
    /// @dev `immutable`, so the pairing is fixed for the life of the
    ///      deployment. A repointable trees address would let a record be
    ///      published into one plane and read as if it belonged to another.
    /// @param trees_ The state trees whose typed writer this contract becomes.
    constructor(FinalStateTrees trees_) {
        trees = trees_;
    }

    /// @notice Point the per-chain leverage caps at their source (the asset
    ///         registry). Bootstrap admin only, before the seal — installed as
    ///         the plane comes up, and fixed for good afterwards.
    /// @dev Deliberately a one-shot window rather than a standing setter: this
    ///      pointer decides the ceiling on every exposure the record accepts,
    ///      so a later mutation is a way to widen every cap at once. Zero
    ///      leaves the global bound alone, which is the state a plane starts in
    ///      and a legitimate configuration for one with no per-asset ceilings.
    /// @param source The asset registry answering `leverageCapPct`.
    function setLeverageCapSource(ILeverageCapSource source) external {
        FinalIdentityRegistry reg = trees.registry();
        if (reg.bootstrapSealed() || msg.sender != reg.bootstrapAdmin()) revert NotConfigurationAuthority();
        leverageCaps = source;
        emit LeverageCapSourceSet(address(source));
    }

    /**
     * @notice Pin the presale ledger as the direct writer of tree 9's counters (`recordCounters`).
     * @dev Bootstrap admin only, before the registry seals — the same window `setLeverageCapSource`
     *      has; afterwards only a redeploy changes it. The trees must ALSO name this contract as tree 9's
     *      `treeWriter` (`FinalStateTrees.setTreeWriter(9, records)`), which the plane deploy does.
     * @param ledger The `FinalPhiPresaleLedger`; zero removes the path.
     */
    function setPresaleLedger(address ledger) external {
        FinalIdentityRegistry reg = trees.registry();
        if (reg.bootstrapSealed() || msg.sender != reg.bootstrapAdmin()) revert NotConfigurationAuthority();
        presaleLedger = ledger;
        emit PresaleLedgerSet(ledger);
    }

    /// @notice Refuse a PHI record whose exposures exceed their per-chain ceilings.
    /// @dev Every morph on the leaf within its asset's cap on the leaf's chain.
    ///      The global `[MIN, MAX]` bound is checked by `phiAccountLeafHash`;
    ///      this is the tighter, per-(asset, chain) ceiling from tree 6. A zero
    ///      cap means the asset carries no per-chain ceiling, not a ceiling of
    ///      zero — an unset row must not silently forbid every exposure.
    /// @param leaf The record about to be published.
    function _requireWithinLeverageCaps(PhiAccountLeaf calldata leaf) internal view {
        if (address(leverageCaps) == address(0)) return;
        for (uint256 i = 0; i < leaf.morphs.length; i++) {
            uint16 cap = leverageCaps.leverageCapPct(leaf.morphs[i].asset, leaf.chainId);
            if (cap != 0 && leaf.morphs[i].leveragePct > cap) {
                revert LeverageAboveCap(leaf.morphs[i].asset, leaf.chainId, leaf.morphs[i].leveragePct, cap);
            }
        }
    }

    // --------------------------------------------- shapes, keys and hashes

    /// @notice Lower leverage bound on a morph, in percent. 100 = 1×.
    /// @dev No exposure below its own collateral: a sub-1× morph is a position
    ///      the lock over-collateralises, which the lock already expresses.
    uint16 public constant MIN_LEVERAGE_PCT = 100;
    /// @notice Upper leverage bound on a morph, in percent. 500 = 5×.
    /// @dev The 5× is the design's hard cap, enforced where the record is
    ///      written so it cannot depend on every publisher remembering it. A
    ///      class may cap lower as a schedule constant; nothing may cap higher.
    uint16 public constant MAX_LEVERAGE_PCT = 500;

    /**
     * @notice One price exposure carved out of a wallet's PHI lock.
     *
     * @dev A morph is NOT the lock. The lock is collateral — one per wallet per
     * chain, PHI added to and removed from it on the execution chain. A morph
     * is exposure to one tree-6 `USE_MORPH` asset (USD included, which is how a
     * holder halts PHI exposure), at a leverage, from marks fixed when its terms
     * were set. A wallet holds any number against one lock and the sum of their
     * `phiAmount` never exceeds it.
     *
     * The record keeps the INPUTS — the two USD marks and the leverage — never a
     * derived amount, because the inputs are what every later question (running
     * fees, PnL, liquidation, capture at reconciliation, disputes) is asked
     * against. Notional at open is `phiAmount × phiUsdAtOpen × leveragePct /
     * 100`; running fees and PnL are computed by readers from `openedAt`, tree 4
     * and the clock, and are never published — publishing them would move every
     * open row every round.
     */
    struct Morph {
        /// @dev Tree-6 asset id the exposure tracks. `USE_MORPH` on its kind-1 row.
        bytes32 asset;
        /// @dev How many PHI were morphed into this exposure — the collateral.
        uint256 phiAmount;
        /// @dev Exposure multiplier in percent, within the bounds above.
        uint16 leveragePct;
        /// @dev PHI price, USD-denominated, at the round that set the terms.
        uint256 phiUsdAtOpen;
        /// @dev The asset's price, USD-denominated, at the same round.
        uint256 assetUsdAtOpen;
        /// @dev Final Chain block that set the current terms and marks — the
        ///      open, the last change, or the last reconciliation.
        uint64 openedAt;
        /// @dev Liquidity class recorded at open, re-classed at renewal.
        uint8 class;
        /// @dev `1` open | `2` closing | `3` liquidating; `0` = no state (an
        ///      absent morph). The one definition — the backend's `MORPH_STATE`
        ///      and `FinalMorphMarker.MORPH_STATE_OPEN` mirror these values.
        uint8 state;
        /// @dev The exposure's direction (M1): `MORPH_DIRECTION_LONG` gains when the asset rises against USD,
        ///      `MORPH_DIRECTION_SHORT` when it falls. The knockout barrier is evaluated on the SIGNED exposure —
        ///      adverse movement is a fall for a long and a rise for a short — and nothing else about the record
        ///      changes with it: the same collateral, the same notional, the same fee accrual.
        uint8 direction;
    }

    /**
     * @notice One wallet's PHI on one chain — the record every fee, discount,
     *         mark and liquidation is computed from.
     *
     * @dev Keyed by `(wallet, chainId)` because PHI is allocated per chain — a
     * wallet's balance is not one number. Balances and the lock are chain facts
     * read from `PHIToken` at `sourceBlock` by the publishers; everything the
     * user chooses — lock-global terms, each exposure — arrives by an admitted
     * intent and is folded in by the same publishers. Nothing about PHI lives
     * in tree 1.
     *
     * `accrualUsd` is the bookkeeping between reconciliations: every morph
     * change and close realizes its running fees and PnL at that moment's marks
     * into it — positive owed by the user, negative earned. Reconciliation
     * (daily or the user's instant pass) settles it by spawning or despawning
     * PHI in the wallet and resets it to zero, advancing `reconciledEpoch`.
     *
     * Deliberately NOT here: the tier or discount (a tier is not state — it is
     * where the total falls against tree 6's ranges when an intent is submitted,
     * and it lives in that intent's terms), the freeze (tree 1's), any per-chain
     * fact such as the supply allocation epoch, and the running fee of an open
     * exposure.
     */
    struct PhiAccountLeaf {
        /// @dev The wallet this record is about. Half of the tree-2 key.
        address wallet;
        /// @dev The chain the balances and the lock belong to. The other half
        ///      of the key, because PHI is allocated per chain.
        uint64 chainId;
        /// @dev `PHIToken.availableBalanceOf(wallet)` at `sourceBlock`.
        uint256 available;
        /// @dev `PHIToken.lockedBalanceOf(wallet)` at `sourceBlock` — the one lock.
        uint256 locked;
        /// @dev The lock's timer, from the token, at `sourceBlock`.
        uint64 lockExpiry;
        /// @dev Lock-global: renew the lock at expiry rather than release it.
        ///      From the latest admitted lock-change or morph-change intent
        ///      that carried it.
        bool autoExtend;
        /// @dev Lock-global: tree-6 asset ids fees are taken from, in the user's
        ///      order. Same source as `autoExtend`.
        bytes32[] feeTokens;
        /// @dev PHI leaving this chain: the move is recorded on Final Chain and
        ///      the target's spawn has not landed. Counts toward the tier on
        ///      this chain until it reappears in the target's `available`.
        uint256 inTransit;
        /// @dev Signed USD micros since the last reconciliation. Positive is
        ///      owed by the user; negative is more the user may morph, in USD.
        int256 accrualUsd;
        /// @dev Final Chain block of the reconciliation pass — daily or instant
        ///      — through which this wallet's exposures are captured and settled.
        uint64 reconciledEpoch;
        /// @dev The pinned execution-chain block the token reads were taken at.
        uint64 sourceBlock;
        /// @dev Block of the wallet's last PHI balance change on this chain.
        uint64 since;
        /// @dev The exposures carved out of the lock. Any number of them; the
        ///      sum of their `phiAmount` never exceeds `locked`. Committed in
        ///      order, so two publishers must agree on the ordering as well as
        ///      the contents to reach the same leaf.
        Morph[] morphs;
    }

    /**
     * @notice One vAsset's issuance against its backing, on one chain.
     *
     * @dev **The vAsset is the key.** A unit of a vAsset is the claim on the
     * custody behind it, at all times, so a per-holder row here would prove
     * nothing the vAsset's own chain does not already prove. Rows are per
     * `(assetId, chainRef)`, plus the cross-chain aggregate at `chainRef = 0`
     * where the backing invariant lives — custody sits on one chain and
     * issuance is spread over the rest, so any single non-origin row reads as
     * unbacked on its own.
     *
     * Final Chain leads, so `recorded` (what Final Chain has admitted) and
     * `issuedSupply` (what the target has minted) differ for the minutes a
     * crossing takes; without the pair every crossing reads as drift.
     * `recorded ≤ lockedOnOrigin` is the invariant, `issuedSupply ≤ recorded`
     * is the lag. Whether a given holder may EXERCISE the claim is tree 1's
     * fact: a frozen wallet cannot burn to claim.
     */
    struct VAssetLeaf {
        /// @dev The asset this row is about. Half of the tree-3 key.
        bytes32 assetId;
        /// @dev Where the real asset is custodied.
        bytes32 originChainRef;
        /// @dev WHICH chain this row is about; zero for the aggregate.
        bytes32 chainRef;
        /// @dev `FinalSettlement.totalLocked` on the origin.
        uint256 lockedOnOrigin;
        /// @dev Source-chain block of the lock this row was last updated by — a
        ///      fact of one lock, kept for the proof's public inputs beside the
        ///      lock address (`srcSettlement`) and the amount, never a release
        ///      key. Release is by amount, against `totalLocked`.
        uint64 lockBlock;
        /// @dev Issuance Final Chain has admitted for `chainRef`.
        uint256 recorded;
        /// @dev `FinalVAsset.totalSupply` on `chainRef`.
        uint256 issuedSupply;
        /// @dev Commitment to the LaBRADOR proof that this issuance is backed —
        ///      the proof IS the key that unlocks the base asset on its origin
        ///      chain, so the leaf commits to it rather than to a receipt for
        ///      it. Its public inputs name every lock's block, address and
        ///      amount. Verified off-chain by the co-signer quorum, exactly as
        ///      PQ signatures are; the commitment is what makes their verdict
        ///      re-derivable afterwards.
        bytes32 proofCommitment;
        /// @dev Final Chain block this row's observations were taken at.
        uint64 observedAt;
    }

    /// @notice Which publisher composed a price. A FIELD, never part of the key.
    ///
    /// @dev Three services publish prices — `oracle` (DEX TWAP and the
    ///      attestation anchor), `fee-oracle` (gas and native-asset pricing) and
    ///      `phi-oracle`. Keying by kind would let all three answer "what is X
    ///      worth" differently, in three separate rows, and all three be right,
    ///      which is exactly the fragmentation this tree exists to end. Keying
    ///      WITHOUT it and recording it gives one answer per question and makes
    ///      the answerer auditable: two publishers alternating on one key show
    ///      up as a flapping `kind`, where a shared anonymous slot would look
    ///      calm.
    /// @dev No price published for the key; the zero value of the field.
    uint8 public constant PRICE_KIND_UNSET = 0;
    /// @dev A time-weighted average composed from DEX pool observations.
    uint8 public constant PRICE_KIND_DEX_TWAP = 1;
    /// @dev A spot price carried by an attestation rather than a pool read.
    uint8 public constant PRICE_KIND_ATTESTED_SPOT = 2;
    /// @dev Gas and native-asset pricing, published for fee quotation.
    uint8 public constant PRICE_KIND_FEE = 3;
    /// @dev RETIRED (A-01 (a), 2026-09-09): the dedicated PHI publisher plane is gone. The value is kept so the
    ///      numbering never reuses it; nothing publishes kind 4. Higher kinds (5–7, and 8 = the sale price the
    ///      listing is derived from) are defined by the backend's price-kind enum, which is their source — the
    ///      store validates no kind.
    uint8 public constant PRICE_KIND_PHI = 4;

    /// @notice The granularity every price is truncated to before it is hashed.
    ///
    /// @dev Two publishers folding the same weighted quotes in a different
    ///      order disagree in the last few wei — integer division does not
    ///      commute — and a few wei is a different leaf hash, a different root
    ///      and a round nobody reaches quorum on. The fix is not "sum in a
    ///      canonical order", which is a convention each publisher can drift
    ///      from silently; it is to discard more precision than any ordering can
    ///      disagree about. 1e6 wei against prices in the 1e15..1e21 range
    ///      leaves nine to fifteen significant digits, far more than any venue
    ///      quotes to.
    uint256 public constant PRICE_QUANTUM = 1e6;

    /// @dev A price below one quantum is not a small price, it is a price this
    ///      shape cannot carry — and truncating it would publish zero, which
    ///      every consumer reads as "no price" rather than "nearly free".
    error PriceBelowQuantum(uint256 raw);


    /**
     * @notice One venue's contribution to a composed price.
     * @dev Not stored. It exists so `priceInputsCommitment` has a shape to fold,
     *      and so the off-chain composer and the on-chain commitment agree on
     *      what "the inputs" were down to the byte.
     */
    struct PriceQuote {
        /// @dev The pool address or the API source id. Matches the `venue` on
        ///      the asset's tree-6 per-chain row, so a consumer can check that
        ///      the price came from the venue policy says to use.
        bytes32 venue;
        /// @dev Matches `venueKind` in `FinalAssetRegistry`.
        uint8 venueKind;
        /// @dev Quantised, exactly as the composite is.
        uint256 price;
        /// @dev This quote's weight in the fold, in basis points.
        uint32 weightBps;
        /// @dev When the quote was observed, so a stale venue is visible inside
        ///      the commitment rather than only in the composite's timestamp.
        uint64 observedAt;
    }

    /**
     * @notice One published price, from any of the three publishers.
     *
     * @dev `assetId` is the token's address left-padded to 32 bytes, so a
     * consumer holding an address can derive the key without a table. 32 bytes
     * rather than `address` because an origin chain that is not EVM has no
     * 20-byte account, and widening later would move every key.
     *
     * `price` is quantised units of `quoteAsset` for ONE WHOLE token —
     * `10 ** decimals` of it — so the number proven here is the number a fee
     * calculation used rather than a rounding of it.
     *
     * `transferTaxBps` is the EFFECTIVE tax pricing applies, not the one the
     * token declares. A consumer grossing up by the declared figure reproduces
     * the shortfall `weiToFeeAmount` exists to avoid, so the leaf carries the
     * one that is correct to use.
     *
     * `inputsCommitment` is what turns this from an assertion into a claim that
     * can be checked. A leaf carrying only its output says "the price is X", and
     * the only way to disagree is to distrust the publisher wholesale. A leaf
     * carrying the venues, weights and round that produced X can be recomputed
     * by anyone holding the same quotes — so a wrong price is attributable to a
     * wrong input or a wrong fold, and those are different incidents.
     */
    struct OraclePriceLeaf {
        /// @dev WHAT is priced: the token's address left-padded to 32 bytes.
        bytes32 assetId;
        /// @dev WHERE this price was observed. Zero for a cross-chain
        ///      composite. A DEX price is a property of a pool on a chain, so
        ///      the chain belongs in the key: one global slot per asset would
        ///      let a pool address from one chain be applied on another and
        ///      read reserves that cannot exist there.
        bytes32 chainRef;
        /// @dev DENOMINATED IN WHAT. Zero means the chain's native asset, which
        ///      is what every existing consumer means by "wei".
        bytes32 quoteAsset;
        /// @dev Quantised units of `quoteAsset` for ONE WHOLE token.
        uint256 price;
        /// @dev Which publisher composed it — one of the `PRICE_KIND_*` values.
        ///      Recorded, never part of the key.
        uint8 kind;
        /// @dev How many quotes went into `inputsCommitment`. Cheap to read
        ///      without opening the commitment, and a composite that quietly
        ///      drops from three venues to one is a failure that otherwise looks
        ///      identical to a healthy round.
        uint8 sourceCount;
        /// @dev The EFFECTIVE transfer tax pricing applies, in basis points,
        ///      not the figure the token declares.
        uint32 transferTaxBps;
        /// @dev Commitment to the quotes, weights and epoch this price was
        ///      folded from, as `priceInputsCommitment` computes it.
        bytes32 inputsCommitment;
        /// @dev The tree-4 nonce this price was composed at. Monotone, so a
        ///      consumer compares it to tell a stale price from a current one
        ///      without trusting a wall clock — the same role `epoch` plays on
        ///      a registry entry, and named the same for that reason.
        ///
        ///      NOT `round`: `FinalStateTrees.round` is the publish round of
        ///      every tree together, and one word for two counters that
        ///      advance at different rates is a comparison waiting to be made
        ///      between them.
        uint64 epoch;
        /// @dev When the composite was observed. A wall clock, useful for
        ///      display and staleness alarms; `epoch` is what a consumer
        ///      compares to decide which of two prices is newer.
        uint64 observedAt;
    }

    /// @notice The tree-2 key a `(wallet, chain)` balance occupies.
    /// @param wallet The wallet the record is about.
    /// @param chainId The chain the balances belong to.
    /// @return The tree-2 key.
    function phiAccountKey(address wallet, uint64 chainId) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_PHI_KEY, wallet, chainId));
    }

    /// @notice One vAsset row's key: asset and chain.
    ///
    /// @dev `chainRef == 0` is the cross-chain aggregate. There is deliberately
    ///      no holder in the key: a unit held IS the claim on the custody
    ///      behind it, so a per-holder row would prove nothing the vAsset's own
    ///      chain does not already prove.
    /// @param assetId The asset the row is about.
    /// @param chainRef The chain the row is about; zero for the aggregate.
    /// @return The tree-3 key.
    function vAssetKey(bytes32 assetId, bytes32 chainRef) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_VASSET_KEY, assetId, chainRef));
    }

    /// @notice The tree-4 key one price occupies: what, where, and in what.
    ///
    /// @dev `chainRef == 0` is the cross-chain composite and `quoteAsset == 0`
    ///      is the native asset, so a consumer that only cares about "the price
    ///      of X in wei" derives its key from two zeros and never has to know
    ///      the other two dimensions exist. A per-chain price occupies its own
    ///      key beside the composite rather than overwriting it.
    /// @param assetId The token, left-padded to 32 bytes.
    /// @param chainRef The chain observed; zero for the cross-chain composite.
    /// @param quoteAsset The denominating asset; zero for the native asset.
    /// @return The tree-4 key.
    function oraclePriceKey(bytes32 assetId, bytes32 chainRef, bytes32 quoteAsset)
        public
        pure
        returns (bytes32)
    {
        return keccak256(abi.encode(DOMAIN_ORACLE_KEY, assetId, chainRef, quoteAsset));
    }

    /// @notice Truncate a price to `PRICE_QUANTUM`.
    /// @dev Publishers call this before composing and before publishing. It is
    ///      on-chain so there is one definition rather than one per publisher.
    ///      A nonzero price that truncates to zero is refused rather than
    ///      published: zero reads as "no price" to every consumer, which is a
    ///      different and much worse claim than "nearly free".
    /// @param raw The untruncated price.
    /// @return q The price truncated to a whole number of quanta.
    function quantise(uint256 raw) public pure returns (uint256 q) {
        q = (raw / PRICE_QUANTUM) * PRICE_QUANTUM;
        if (q == 0 && raw != 0) revert PriceBelowQuantum(raw);
    }

    /// @notice The commitment a price leaf carries to the quotes behind it.
    /// @dev `epoch` is inside the fold, so the same quotes at two epochs commit
    ///      differently and a stale composite cannot be re-presented as fresh.
    ///      The quotes are committed in order, so the composer and any auditor
    ///      must agree on the ordering as well as the contents.
    /// @param quotes The venue quotes that were folded, in composition order.
    /// @param epoch The tree-4 nonce the composite was built at.
    /// @return The commitment to place in the leaf's `inputsCommitment`.
    function priceInputsCommitment(PriceQuote[] memory quotes, uint64 epoch)
        public
        pure
        returns (bytes32)
    {
        return keccak256(abi.encode(DOMAIN_ORACLE_INPUTS, epoch, quotes));
    }

    /// @notice The tree-2 leaf hash.
    /// @dev `feeTokens` and `morphs` go through `abi.encode` like every other
    ///      field, so the whole record — terms, exposures, marks — is committed
    ///      in order, and agreement between publishers is exact over all of it.
    ///      Every off-chain producer of this leaf must encode the same fields
    ///      in the same order; a divergence is not a compile error anywhere, it
    ///      is a round that never reaches quorum.
    ///
    ///      The global leverage bound and the direction's two values are
    ///      enforced here rather than at the setter, so no path can commit to
    ///      an exposure outside them.
    /// @param leaf The PHI record to commit to.
    /// @return The untagged leaf value for tree 2.
    function phiAccountLeafHash(PhiAccountLeaf memory leaf) public pure returns (bytes32) {
        for (uint256 i = 0; i < leaf.morphs.length; i++) {
            uint16 lev = leaf.morphs[i].leveragePct;
            if (lev < MIN_LEVERAGE_PCT || lev > MAX_LEVERAGE_PCT) revert LeverageOutOfRange(lev);
            if (leaf.morphs[i].direction > MORPH_DIRECTION_SHORT) revert InvalidMorphDirection(leaf.morphs[i].direction);
        }
        return keccak256(
            abi.encode(
                DOMAIN_PHI_ACCOUNT_LEAF,
                leaf.wallet,
                leaf.chainId,
                leaf.available,
                leaf.locked,
                leaf.lockExpiry,
                leaf.autoExtend,
                leaf.feeTokens,
                leaf.inTransit,
                leaf.accrualUsd,
                leaf.reconciledEpoch,
                leaf.sourceBlock,
                leaf.since,
                leaf.morphs
            )
        );
    }

    /// @notice The tree-3 leaf hash.
    /// @dev Every off-chain producer must encode the same fields in the same
    ///      order. The row's own key fields ride inside the leaf as well, so a
    ///      leaf cannot be moved to another key and still verify.
    /// @param leaf The vAsset row to commit to.
    /// @return The untagged leaf value for tree 3.
    function vAssetLeafHash(VAssetLeaf memory leaf) public pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_VASSET_LEAF,
                leaf.assetId,
                leaf.originChainRef,
                leaf.chainRef,
                leaf.lockedOnOrigin,
                leaf.lockBlock,
                leaf.recorded,
                leaf.issuedSupply,
                leaf.proofCommitment,
                leaf.observedAt
            )
        );
    }

    /// @notice The tree-4 leaf hash.
    /// @dev Quantises rather than trusting the caller to have done it: a
    ///      publisher that forgets produces a leaf no other publisher can
    ///      reproduce, and the round simply never reaches quorum. Every
    ///      off-chain producer must encode the same fields in the same order.
    /// @param leaf The price to commit to. Its `price` is quantised here.
    /// @return The untagged leaf value for tree 4.
    function oraclePriceLeafHash(OraclePriceLeaf memory leaf) public pure returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_ORACLE_PRICE_LEAF,
                leaf.assetId,
                leaf.chainRef,
                leaf.quoteAsset,
                quantise(leaf.price),
                leaf.kind,
                leaf.sourceCount,
                leaf.transferTaxBps,
                leaf.inputsCommitment,
                leaf.epoch,
                leaf.observedAt
            )
        );
    }

    /**
     * @notice Write PHI balances into tree 2 from typed leaves.
     * @dev Typed for the same reason `setAccountStates` is: the preimage is
     * built HERE rather than by whoever assembles the calldata. A consumer
     * verifies a proof against 32 bytes and cannot tell a balance from a price
     * — so if the publisher chose the preimage, the quorum would be approving
     * an opaque hash and the leaf's MEANING would be the publisher's alone.
     *
     * The per-chain leverage ceiling is checked per leaf before the batch is
     * handed on, so one exposure over its cap fails the whole batch rather
     * than landing beside the ones that were in range.
     * @param leaves The PHI records to publish, one per `(wallet, chain)`.
     * @param anchorBlock The block the approving roster is read as of.
     * @param approvals The tree-2 publishers' quorum, ascending by signer.
     */
    function setPhiAccounts(
        PhiAccountLeaf[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        bytes32[] memory keys = new bytes32[](leaves.length);
        bytes32[] memory hashes = new bytes32[](leaves.length);
        for (uint256 i = 0; i < leaves.length; i++) {
            keys[i] = phiAccountKey(leaves[i].wallet, leaves[i].chainId);
            hashes[i] = phiAccountLeafHash(leaves[i]);
            _requireWithinLeverageCaps(leaves[i]);
            _storePhiLeaf(_phiLeaf[keys[i]], leaves[i]);
        }
        trees.writeTyped(TREE_PHI, keys, hashes, anchorBlock, approvals);
    }

    /// @notice Copy one PHI record from calldata into its storage slot.
    /// @dev Field-by-field copy into storage. The two dynamic members are
    ///      cleared and re-pushed so a shorter list never leaves stale rows
    ///      behind it; a whole-struct assignment from calldata is what the
    ///      compiler declines for this shape, and would be less explicit anyway.
    ///      A stale trailing morph is the failure this guards against: it would
    ///      read as a live exposure while the leaf hash commits to a record
    ///      without it.
    /// @param dst The storage record to overwrite.
    /// @param src The record as published.
    function _storePhiLeaf(PhiAccountLeaf storage dst, PhiAccountLeaf calldata src) private {
        dst.wallet = src.wallet;
        dst.chainId = src.chainId;
        dst.available = src.available;
        dst.locked = src.locked;
        dst.lockExpiry = src.lockExpiry;
        dst.autoExtend = src.autoExtend;
        dst.inTransit = src.inTransit;
        dst.accrualUsd = src.accrualUsd;
        dst.reconciledEpoch = src.reconciledEpoch;
        dst.sourceBlock = src.sourceBlock;
        dst.since = src.since;
        delete dst.feeTokens;
        for (uint256 i = 0; i < src.feeTokens.length; i++) {
            dst.feeTokens.push(src.feeTokens[i]);
        }
        delete dst.morphs;
        for (uint256 i = 0; i < src.morphs.length; i++) {
            dst.morphs.push(src.morphs[i]);
        }
    }

    /// @notice Write vAsset issuance and backing into tree 3 from typed leaves.
    /// @dev Typed for `setPhiAccounts`' reason: the preimage is built here, so
    ///      the quorum approves a record whose meaning this contract fixed
    ///      rather than an opaque hash the publisher chose.
    /// @param leaves The rows to publish, one per `(asset, chain)` and one
    ///        aggregate at `chainRef == 0`.
    /// @param anchorBlock The block the approving roster is read as of.
    /// @param approvals The tree-3 publishers' quorum, ascending by signer.
    function setVAssetStates(
        VAssetLeaf[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        bytes32[] memory keys = new bytes32[](leaves.length);
        bytes32[] memory hashes = new bytes32[](leaves.length);
        for (uint256 i = 0; i < leaves.length; i++) {
            keys[i] = vAssetKey(leaves[i].assetId, leaves[i].chainRef);
            hashes[i] = vAssetLeafHash(leaves[i]);
            _vAssetLeaf[keys[i]] = leaves[i];
        }
        trees.writeTyped(TREE_VASSET, keys, hashes, anchorBlock, approvals);
    }

    /// @notice Write prices into tree 4 from typed leaves.
    /// @dev Typed for `setPhiAccounts`' reason, and quantising on the way in so
    ///      the stored value is the one the commitment covers.
    /// @param leaves The prices to publish, one per tree-4 key.
    /// @param anchorBlock The block the approving roster is read as of.
    /// @param approvals The tree-4 publishers' quorum, ascending by signer.
    function setOraclePrices(
        OraclePriceLeaf[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        bytes32[] memory keys = new bytes32[](leaves.length);
        bytes32[] memory hashes = new bytes32[](leaves.length);
        for (uint256 i = 0; i < leaves.length; i++) {
            keys[i] = oraclePriceKey(leaves[i].assetId, leaves[i].chainRef, leaves[i].quoteAsset);
            hashes[i] = oraclePriceLeafHash(leaves[i]);
            OraclePriceLeaf memory stored = leaves[i];
            // Quantised on the way in, because the HASH is over the quantised
            // price. Storing the caller's raw figure would give a reader a
            // number that does not reproduce the commitment it was proven
            // against — self-consistent on both sides and wrong between them.
            stored.price = quantise(stored.price);
            _priceLeaf[keys[i]] = stored;
        }
        trees.writeTyped(TREE_ORACLE, keys, hashes, anchorBlock, approvals);
    }

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

    /**
     * @notice The price behind a tree-4 key, as a value rather than a hash.
     *
     * @dev What makes this safe to read is that it cannot disagree with the
     * commitment: the pair is written by one loop in `setOraclePrices`, and
     * `setLeaves` refuses tree 4 so there is no second door. A consumer that
     * wants to prove the answer elsewhere takes `proofFor` as well and
     * re-derives `oraclePriceLeafHash` from these fields.
     *
     * `present` is the slot, not the price. A key that has never been written
     * returns a zeroed struct, and zero is a price — so a caller that ignores
     * the flag reads "not published yet" as "free".
     * @param key The tree-4 key, as `oraclePriceKey` computes it.
     * @return leaf The stored price record, zeroed when `present` is false.
     * @return present Whether the key holds a slot in tree 4.
     */
    // ------------------------------------------------------------ tree 9 — compliance

    /// @notice Write approval leaves (tree 9, branch 1) under the REGISTRAR quorum — an attestation is an admission, so the writer role is the registrars' and nothing new; a renewal rewrites its slot. Keys and hashes are `FinalComplianceLeaves`'s; the fields are stored beside the hash for `approvalAt`.
    function setApprovals(
        FinalComplianceLeaves.ApprovalLeaf[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        bytes32[] memory keys = new bytes32[](leaves.length);
        bytes32[] memory hashes = new bytes32[](leaves.length);
        for (uint256 i = 0; i < leaves.length; i++) {
            keys[i] = FinalComplianceLeaves.approvalKeyFor(leaves[i].commitment);
            hashes[i] = FinalComplianceLeaves.approvalLeafHash(leaves[i]);
            _approvalLeaf[keys[i]] = leaves[i];
        }
        trees.writeTypedInBranch(TREE_COMPLIANCE, FinalComplianceLeaves.BRANCH_APPROVALS, keys, hashes, anchorBlock, approvals);
    }

    /// @notice Write revocation leaves (tree 9, branch 2) — a commitment or a live action nullifier each; effective at the next block.
    function setRevocations(
        FinalComplianceLeaves.RevocationLeaf[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        bytes32[] memory keys = new bytes32[](leaves.length);
        bytes32[] memory hashes = new bytes32[](leaves.length);
        for (uint256 i = 0; i < leaves.length; i++) {
            keys[i] = FinalComplianceLeaves.revocationKeyFor(leaves[i].subject);
            hashes[i] = FinalComplianceLeaves.revocationLeafHash(leaves[i]);
            _revocationLeaf[keys[i]] = leaves[i];
        }
        trees.writeTypedInBranch(TREE_COMPLIANCE, FinalComplianceLeaves.BRANCH_REVOCATIONS, keys, hashes, anchorBlock, approvals);
    }

    /// @notice Write attestation leaves (tree 9, branch 4) — minutes-lived; each on-demand issue rewrites its slot.
    function setAttestations(
        FinalComplianceLeaves.AttestationLeaf[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        bytes32[] memory keys = new bytes32[](leaves.length);
        bytes32[] memory hashes = new bytes32[](leaves.length);
        for (uint256 i = 0; i < leaves.length; i++) {
            keys[i] = FinalComplianceLeaves.attestationKeyFor(leaves[i].nullifier);
            hashes[i] = FinalComplianceLeaves.attestationLeafHash(leaves[i]);
            _attestationLeaf[keys[i]] = leaves[i];
        }
        trees.writeTypedInBranch(TREE_COMPLIANCE, FinalComplianceLeaves.BRANCH_ATTESTATIONS, keys, hashes, anchorBlock, approvals);
    }

    /**
     * @notice The presale ledger's mirror — and branch 3's ONLY writer: the counter a buyer was admitted
     *         against, written into branch 3 directly (this contract is tree 9's `treeWriter`) in the
     *         admitting transaction. The caps live in the ledger (`setBucketCap`); the first admission of a
     *         bucket creates its leaf. No registrar door writes counters: one branch, one writer, and a
     *         chain-derived count beats a database mirror.
     * @dev `presaleLedger` only. No quorum — the ledger's admission already carried one, and the counter
     *      is the ledger's own state; this is its provable copy.
     */
    function recordCounters(FinalComplianceLeaves.CounterLeaf[] calldata leaves) external {
        if (presaleLedger == address(0) || msg.sender != presaleLedger) revert NotPresaleLedger(msg.sender);
        bytes32[] memory keys = new bytes32[](leaves.length);
        bytes32[] memory hashes = new bytes32[](leaves.length);
        for (uint256 i = 0; i < leaves.length; i++) {
            keys[i] = FinalComplianceLeaves.counterKeyFor(leaves[i].policyVersion, leaves[i].bucket);
            hashes[i] = FinalComplianceLeaves.counterLeafHash(leaves[i]);
            _counterLeaf[keys[i]] = leaves[i];
        }
        trees.setLeavesAsWriter(TREE_COMPLIANCE, FinalComplianceLeaves.BRANCH_COUNTERS, keys, hashes);
    }

    /// @notice The approval behind a tree-9 branch-1 key, as a value; `present` from the tree.
    function approvalAt(bytes32 key) external view returns (FinalComplianceLeaves.ApprovalLeaf memory leaf, bool present) {
        (, present) = trees.leafOf(TREE_COMPLIANCE, key);
        if (present) leaf = _approvalLeaf[key];
    }

    /// @notice The revocation behind a tree-9 branch-2 key; absent = not revoked.
    function revocationAt(bytes32 key) external view returns (FinalComplianceLeaves.RevocationLeaf memory leaf, bool present) {
        (, present) = trees.leafOf(TREE_COMPLIANCE, key);
        if (present) leaf = _revocationLeaf[key];
    }

    /// @notice The counter behind a tree-9 branch-3 key; absent = no cap seeded for that (policy, bucket).
    function counterAt(bytes32 key) external view returns (FinalComplianceLeaves.CounterLeaf memory leaf, bool present) {
        (, present) = trees.leafOf(TREE_COMPLIANCE, key);
        if (present) leaf = _counterLeaf[key];
    }

    /// @notice The attestation behind a tree-9 branch-4 key; the reader checks the window and the binding.
    function attestationAt(bytes32 key) external view returns (FinalComplianceLeaves.AttestationLeaf memory leaf, bool present) {
        (, present) = trees.leafOf(TREE_COMPLIANCE, key);
        if (present) leaf = _attestationLeaf[key];
    }

    function oraclePriceAt(bytes32 key)
        external
        view
        returns (OraclePriceLeaf memory leaf, bool present)
    {
        (, present) = trees.leafOf(TREE_ORACLE, key);
        if (present) leaf = _priceLeaf[key];
    }

    /// @notice The same, for a tree-4 key derived from its three parts.
    /// @dev Saves a caller the key derivation; identical semantics, including
    ///      the `present` caveat above.
    /// @param assetId The token, left-padded to 32 bytes.
    /// @param chainRef The chain observed; zero for the cross-chain composite.
    /// @param quoteAsset The denominating asset; zero for the native asset.
    /// @return leaf The stored price record, zeroed when `present` is false.
    /// @return present Whether the key holds a slot in tree 4.
    function oraclePriceFor(bytes32 assetId, bytes32 chainRef, bytes32 quoteAsset)
        external
        view
        returns (OraclePriceLeaf memory leaf, bool present)
    {
        bytes32 key = oraclePriceKey(assetId, chainRef, quoteAsset);
        (, present) = trees.leafOf(TREE_ORACLE, key);
        if (present) leaf = _priceLeaf[key];
    }

    /// @notice The PHI balance behind a tree-2 key, as a value.
    /// @dev Same `present` caveat as the price reads: an unwritten key answers
    ///      a zeroed record, and a zero balance is a meaningful balance.
    /// @param key The tree-2 key, as `phiAccountKey` computes it.
    /// @return leaf The stored PHI record, zeroed when `present` is false.
    /// @return present Whether the key holds a slot in tree 2.
    function phiAccountAt(bytes32 key)
        external
        view
        returns (PhiAccountLeaf memory leaf, bool present)
    {
        (, present) = trees.leafOf(TREE_PHI, key);
        if (present) leaf = _phiLeaf[key];
    }

    /// @notice The vAsset row behind a tree-3 key, as a value.
    /// @dev Same `present` caveat as the price reads. Remember that a single
    ///      non-origin row reads as unbacked on its own: the backing invariant
    ///      lives on the aggregate row at `chainRef == 0`.
    /// @param key The tree-3 key, as `vAssetKey` computes it.
    /// @return leaf The stored vAsset row, zeroed when `present` is false.
    /// @return present Whether the key holds a slot in tree 3.
    function vAssetAt(bytes32 key)
        external
        view
        returns (VAssetLeaf memory leaf, bool present)
    {
        (, present) = trees.leafOf(TREE_VASSET, key);
        if (present) leaf = _vAssetLeaf[key];
    }

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

    /// @notice The registry the inherited sweep authority resolves members through.
    /// @dev Reached through the trees, which is where this contract's own
    /// configuration gate reads it — `setLeverageCapSource` asks
    /// `trees.registry()` for the same bootstrap admin. One pointer, one
    /// answer, and no second registry to drift from the first.
    /// @return The identity registry the pinned trees were constructed against.
    function _sweepRegistry() internal view override returns (FinalIdentityRegistry) {
        return trees.registry();
    }

    /// @dev Nothing is reserved because nothing is owed: this contract holds the
    /// PREIMAGES behind three trees' leaves and has no payable entrypoint and no
    /// custody line. Anything it carries arrived by accident.
}

contracts/finalchain/FinalStateTrees.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /**
     * @notice The constructor's storage writes, for a deployment behind `FinalChainProxy`: the proxy's
     *         constructor runs this once in the proxy's storage. Reverts `AlreadyInitialized` on a direct
     *         deploy (its constructor ran it) and on a second call.
     */
    function initialize() external {
        _setUp();
    }

    /// @dev The empty-subtree ladder and every tree's empty root — storage, so a proxy needs it replayed.
    function _setUp() internal initializer {
        // Level 0: the tagged hash of an empty (zero) leaf.
        _zero[0] = keccak256(abi.encodePacked(bytes1(0x00), bytes32(0)));
        for (uint256 l = 0; l < ROUND_DEPTH; l++) {
            // Both children equal, so the sort is a no-op and the order is
            // irrelevant — which is the only reason this table is one value per
            // level rather than one per position.
            _zero[l + 1] = keccak256(abi.encodePacked(bytes1(0x01), _zero[l], _zero[l]));
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

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

    /**
     * @notice Write account state into tree 1 from the typed leaf.
     * @dev The typed form exists so the leaf preimage is built HERE rather than
     * by whoever assembles the calldata. Tree 1 is the source of truth for every
     * other chain, and `syncAccountState` will accept any 32 bytes that carry a
     * valid proof — so if the publisher chose the preimage, the publisher could
     * write an account state that no wallet record on this chain agrees with,
     * and the proof would still verify everywhere.
     *
     * The round takes the ML-DSA-87 vote alone, as every tree write does (the
     * user's ruling of 12 Sep 2026, arch/quorum-signing-ml-dsa.md). Who an
     * account IS is decided by the holder's own SLH-DSA credential in
     * `FinalAccountLedger` — the ledger is `treeWriter[1]` and writes tree 1
     * with no service quorum at all — so a quorum round here re-publishes state
     * the holder already authorized; it is the roster's membership, not the
     * account's, that keeps the SLH-DSA seal (the registrar quorum).
     * @param leaves The account states to write, one per wallet.
     * @param anchorBlock The block the approving roster is read as of.
     * @param approvals At least `threshold[TREE_ACCOUNTS]` of them, ascending by signer.
     */
    function setAccountStates(
        AccountStateLeaf[] calldata leaves,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        uint256 k = threshold[TREE_ACCOUNTS];
        if (k == 0) revert TreeNotConfigured(TREE_ACCOUNTS);

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

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

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

        _bump(TREE_ACCOUNTS, leaves.length);
    }

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

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

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

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        _leaf[treeId][idx] = leaf;

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

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

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

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

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

contracts/finalchain/ILeverageCapSource.sol

// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may implement this interface to serve per-chain
//    leverage caps to the Final DeFi Protocol's state-record surface, and may
//    compile against it to read those caps.
// 2. Integrators, indexers, and operators may call the view this interface
//    declares as part of their integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
//    deployment of a Fork of this interface or a competing asset-registry or
//    risk-parameter plane derived from it without permission prior to the
//    Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

/**
 * @title Leverage Cap Source
 * @notice The one question the state-record surface asks about risk limits: how much leverage a morph may carry
 *         against a given asset on a given chain.
 * @dev Deliberately a single view rather than a shared base contract. The record surface needs one number and
 *      must not gain a dependency on how the asset registry stores it, or on anything else that registry does;
 *      the registry is free to change its own layout as long as it keeps answering this question.
 *
 *      The implementation of record is the chain-global asset registry, which serves the cap from its per-asset,
 *      per-chain entry. A zero answer means NO per-chain cap is configured, not a cap of zero — a caller that
 *      read zero as "no leverage permitted" would silently freeze every asset the registry has not been given an
 *      explicit limit for. Callers must treat zero as "unconstrained here" and apply whatever chain-global limit
 *      governs instead.
 *
 *      Like everything under this directory, it belongs to the Final Chain state plane and is not deployed to,
 *      or imported by, any execution-chain contract.
 */
interface ILeverageCapSource {
    /// @notice Returns the leverage cap configured for `assetId` on `chainId`.
    /// @dev A view, so it may be called inside another contract's validation without gas-metering concerns on
    ///      this chain. Returns a whole percentage rather than a basis-point or ray value: the caps are coarse
    ///      risk parameters set by a quorum, and a finer unit would imply a precision the setting does not have.
    /// @param assetId Chain-global identifier of the asset the cap applies to.
    /// @param chainId Identifier of the chain the cap applies on, so one asset may be capped differently per
    ///        venue.
    /// @return capPct The cap as a whole percentage. Zero means no per-chain cap is configured — never a cap
    ///         of zero.
    function leverageCapPct(bytes32 assetId, uint64 chainId) external view returns (uint16);
}

contracts/utils/FinalSweep.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

node_modules/@openzeppelin/contracts/utils/StorageSlot.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC-1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {SlotDerivation}.
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct Int256Slot {
        int256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Int256Slot` with member `value` located at `slot`.
     */
    function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns a `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }
}

abi

[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "registry_",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      },
      {
        "name": "records_",
        "type": "address",
        "internalType": "contract FinalStateRecords"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "configure",
    "inputs": [
      {
        "name": "phiAssetId_",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "usdQuoteAsset_",
        "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": "isKnockedOut",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "morphIndex",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "isMarked",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "morphIndex",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "openedAt",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "mark",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "morphIndex",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "markKey",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "morphIndex",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "openedAt",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "markMany",
    "inputs": [
      {
        "name": "refs",
        "type": "tuple[]",
        "internalType": "struct FinalMorphMarker.MarkRef[]",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "chainId",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "morphIndex",
            "type": "uint256",
            "internalType": "uint256"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "marked",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "markOf",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "morphIndex",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "openedAt",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalMorphMarker.KnockoutMark",
        "components": [
          {
            "name": "markedAtBlock",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "restoredAtBlock",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "phiUsdAtMark",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "assetUsdAtMark",
            "type": "uint256",
            "internalType": "uint256"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "onERC1155BatchReceived",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC1155Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC721Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "phiAssetId",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "records",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalStateRecords"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registry",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "restore",
    "inputs": [
      {
        "name": "refs",
        "type": "tuple[]",
        "internalType": "struct FinalMorphMarker.RestoreRef[]",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "chainId",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "morphIndex",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "openedAt",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "restoreNonce",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "sweepAsset",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "moved",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "sweepableSurplus",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "surplus",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "usdQuoteAsset",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "event",
    "name": "AssetSwept",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "indexed": true,
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "MarkerConfigured",
    "inputs": [
      {
        "name": "phiAssetId",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      },
      {
        "name": "usdQuoteAsset",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "MorphKnockedOut",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "indexed": true,
        "internalType": "uint64"
      },
      {
        "name": "morphIndex",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "openedAt",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "phiUsd",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "assetUsd",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "MorphMarkRestored",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "indexed": true,
        "internalType": "uint64"
      },
      {
        "name": "morphIndex",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "openedAt",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "error",
    "name": "AlreadyMarked",
    "inputs": [
      {
        "name": "markKey",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "MarkNotStanding",
    "inputs": [
      {
        "name": "markKey",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "MarkerNotConfigured",
    "inputs": []
  },
  {
    "type": "error",
    "name": "MorphNotOpen",
    "inputs": [
      {
        "name": "state",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "NoSuchMorph",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "morphIndex",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotKnockedOut",
    "inputs": []
  },
  {
    "type": "error",
    "name": "PrecompileUnavailable",
    "inputs": [
      {
        "name": "precompile",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepAboveSurplus",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "requested",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "surplus",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepDestinationNotAllowed",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepTransferFailed",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepUnauthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepZeroAmount",
    "inputs": []
  },
  {
    "type": "error",
    "name": "UnknownPhiAccount",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "UnusableMark",
    "inputs": [
      {
        "name": "what",
        "type": "string",
        "internalType": "string"
      }
    ]
  }
]

read contract

bytecode · 8,714 bytes

0x6080806040526004361015610012575f80fd5b5f905f3560e01c9081630f43d24a1461101b57508063150b7a0214610fc557806319665cb014610fac57806321e3916914610f5a5780633297170414610c31578063423ad10914610c0d5780634798941314610bf05780635bccff3614610b4d57806360a1800814610b17578063629d7b8a14610af25780637b10399914610aad5780637d4bc12a1461099c57806395a3be991461095757806396f51f3a1461065a57806398356204146101ff578063a7ce2703146101d8578063bc197c811461013f5763f23a6e61146100e4575f80fd5b3461013c5760a036600319011261013c576100fd611035565b5061010661104b565b506084356001600160401b03811161013a57610126903690600401611075565b505060405163f23a6e6160e01b8152602090f35b505b80fd5b503461013c5760a036600319011261013c57610159611035565b5061016261104b565b506044356001600160401b03811161013a5761018290369060040161113e565b50506064356001600160401b03811161013a576101a390369060040161113e565b50506084356001600160401b03811161013a576101c4903690600401611075565b505060405163bc197c8160e01b8152602090f35b503461013c578060031936011261013c5760206001600160401b0360025416604051908152f35b503461013c57606036600319011261013c576004356001600160401b03811161013a573660238201121561013a578060040135916001600160401b03831161013c573660248460071b8401011161013c576024356001600160401b03811680910361013a576044356001600160401b0381116106565761028390369060040161113e565b91906001600160401b03600254169260018060a01b037f000000000000000000000000c19d888a2f7ba65a8dcf8d03ad8f1af5cdc6643016916040516020810190896060820188845260408084015252608081018160248b01918a5b8d81106105ec57506102fa925003601f19810183528261119d565b519020833b156105e8579391869391816040519687956322f3f44760e11b875260848701917f6e6bcb1868fa292b670e04e1a01bef2031a85041bccd38bca20cee8ccfadeec4600489015260248801526044870152608060648701525260a4840160a060048460051b8701010192828790607e19813603015b8383106105385750505050505083838281935003925af1801561052d57908391610518575b50506001016001600160401b038111610504576002805467ffffffffffffffff19166001600160401b0392831617905543168382845b82851015610500578460071b810194608460248701966103ed88611b0c565b9061041960448201926103ff84611b20565b926064810135958691019361041385611b20565b92611ab7565b8087526003602052604087209081546001600160401b038116159081156104ea575b506104d8575060206001600160401b036104ba6104b46104ae8d9e6104a960019c9d9e9f7fd1882d17658f987f7ff6186a3833d01f3fd3e5451dbe4fbb8730da2111a31666989067ffffffffffffffff60401b82549160401b169067ffffffffffffffff60401b1916179055565b611b0c565b96611b20565b94611b20565b9381604051951685521693878060a01b031692a401939291906103ce565b63697e74d560e11b8852600452602487fd5b6001600160401b03915060401c1615158c61043b565b5080f35b634e487b7160e01b82526011600452602482fd5b816105229161119d565b61013a57815f610398565b6040513d85823e3d90fd5b60a3198b8803018552969850939650919490939192918635828112156105e4578301906001600160a01b0361056c83611061565b168152602082013560ff81168091036105e0576105cc600193836020949385809501526105be6105b36105a2604085018561186c565b60806040860152608085019161189d565b92606081019061186c565b91606081850391015261189d565b9801960193018a9795939289979592610373565b8c80fd5b8b80fd5b8680fd5b915060019060809081906001600160a01b0361060787611061565b1681526001600160401b0361061e602088016110a2565b166020820152604086013560408201526001600160401b03610642606088016110a2565b1660608201520193019101918391926102df565b8280fd5b503461013c5760a036600319011261013c57600435600481101561013a5761068061104b565b91606435916084356001600160a01b03811692604435929184810361013a576106a7611f73565b60405163f5778b0360e01b81526020816004817f000000000000000000000000c19d888a2f7ba65a8dcf8d03ad8f1af5cdc664306001600160a01b03165afa90811561052d57839161091d575b5085159081156108f9575b506108e55761070f848885611aa3565b955f1981036108e05750855b809681156108d1578082116108ac57508291846107be5750508180808089895af1610744611b34565b50156107aa575b6107965750604080519283526020838101869052956001600160a01b0316927f7643c83e539cea2f6bf506545392e52cfd5f917e327efbcd0ba28f29c28d042e9190a4604051908152f35b634e487b7160e01b81526021600452602490fd5b6365f4a9ef60e11b82526004829052602482fd5b8392509060018503610818575060405163a9059cbb60e01b60208201526001600160a01b039091166024820152604481018790526108139061080d81606481015b03601f19810183528261119d565b88612162565b61074b565b969150508195600284145f146108615750506001946108136040516323b872dd60e01b60208201523060248201528660448201528560648201526064815261080d60848261119d565b6108139060409792975190637921219560e11b6020830152306024830152876044830152866064830152608482015260a060a48201528360c482015260c4815261080d60e48261119d565b632190968160e01b84526001600160a01b038916600452602491909152604452606482fd5b637c2e506f60e11b8452600484fd5b61071b565b6315150d4d60e31b82526004859052602482fd5b6001600160a01b0316861415905080610913575b5f6106ff565b503385141561090d565b90506020813d60201161094f575b816109386020938361119d565b8101031261065657610949906111be565b5f6106f4565b3d915061092b565b503461013c578060031936011261013c576040517f00000000000000000000000064d56ce8ace5840970ad2fe0b4ed78bf0951333a6001600160a01b03168152602090f35b503461013c57602036600319011261013c576004356001600160401b03811161013a573660238201121561013a578060040135906001600160401b0382116106565760248101906024369160608502010111610656578291835b818110610a0857602084604051908152f35b610a44610a196104a9838587611afc565b610a2f6020610a29858789611afc565b01611b20565b6040610a3c858789611afc565b0135916118bd565b610a51575b6001016109f6565b92610a88610a636104a9868587611afc565b610a736020610a29888789611afc565b6040610a80888789611afc565b0135916114f7565b60018101809111610a995792610a49565b634e487b7160e01b85526011600452602485fd5b503461013c578060031936011261013c576040517f000000000000000000000000c19d888a2f7ba65a8dcf8d03ad8f1af5cdc664306001600160a01b03168152602090f35b503461013c576020610b0f610b06366110f0565b92919091611ab7565b604051908152f35b503461013c57606036600319011261013c5760043590600482101561013c576020610b0f83610b4461104b565b60443591611aa3565b503461013c576040608091610b8b610b64366110f0565b928560608894939451610b768161116e565b828152826020820152828a8201520152611ab7565b8152600360205220604051610b9f8161116e565b8154916001600160401b038084169384845281602085019160401c16815260606002600185015494604087019586520154940193845260405194855251166020840152516040830152516060820152f35b503461013c578060031936011261013c5760209054604051908152f35b503461013c576020610c27610c21366110b6565b916118bd565b6040519015158152f35b5034610eab576080366003190112610eab576004356024356044356001600160401b038116809103610eab576064356001600160401b038111610eab57610c7c90369060040161113e565b6040516328305db160e21b81527f000000000000000000000000c19d888a2f7ba65a8dcf8d03ad8f1af5cdc664306001600160a01b03169290602081600481875afa908115610e28575f91610f20575b508015610eaf575b610d50575b505050508115610d1d57816040917f4391c666e2eec9f63c8148b09b0f6ef96b1b32168156ae728348c0e64c39e0fc9385558060015582519182526020820152a180f35b604051631a89d6e560e21b815260206004820152600a6024820152691c1a1a505cdcd95d125960b21b6044820152606490fd5b604051602081019087825286604082015260408152610d7060608261119d565b519020833b15610eab57939190816040519586946322f3f44760e11b865260848601917f8e862443f1a13f07889f0179e58d71eba838b3af3539500b8c4d084c6bed7be8600488015260248701526044860152608060648601525260a4830160a060048460051b8601010192825f90607e19813603015b838310610e335750505050505091815f818582965003925af18015610e2857610e13575b808080610cd9565b610e209193505f9061119d565b5f915f610e0b565b6040513d5f823e3d90fd5b60a3198a880301855294965092949193909291863582811215610eab5783016001600160a01b03610e6382611061565b16825260208101359160ff8316809303610eab57610e996020928260019585809501526105be6105b36105a2604085018561186c565b98019601930190918896959492610de7565b5f80fd5b5060405163f5778b0360e01b8152602081600481875afa908115610e28575f91610ee6575b506001600160a01b0316331415610cd4565b90506020813d602011610f18575b81610f016020938361119d565b81010312610eab57610f12906111be565b5f610ed4565b3d9150610ef4565b90506020813d602011610f52575b81610f3b6020938361119d565b81010312610eab57610f4c906111e6565b5f610ccc565b3d9150610f2e565b34610eab57610f6b610b06366110f0565b5f526003602052602060405f20546001600160401b03811615159081610f97575b506040519015158152f35b6001600160401b03915060401c161582610f8c565b34610eab57610fc3610fbd366110b6565b916114f7565b005b34610eab576080366003190112610eab57610fde611035565b50610fe761104b565b506064356001600160401b038111610eab57611007903690600401611075565b5050604051630a85bd0160e11b8152602090f35b34610eab575f366003190112610eab576020906001548152f35b600435906001600160a01b0382168203610eab57565b602435906001600160a01b0382168203610eab57565b35906001600160a01b0382168203610eab57565b9181601f84011215610eab578235916001600160401b038311610eab5760208381860195010111610eab57565b35906001600160401b0382168203610eab57565b6060906003190112610eab576004356001600160a01b0381168103610eab57906024356001600160401b0381168103610eab579060443590565b6080906003190112610eab576004356001600160a01b0381168103610eab57906024356001600160401b0381168103610eab5790604435906064356001600160401b0381168103610eab5790565b9181601f84011215610eab578235916001600160401b038311610eab576020808501948460051b010111610eab57565b608081019081106001600160401b0382111761118957604052565b634e487b7160e01b5f52604160045260245ffd5b90601f801991011681019081106001600160401b0382111761118957604052565b51906001600160a01b0382168203610eab57565b51906001600160401b0382168203610eab57565b51908115158203610eab57565b6001600160401b0381116111895760051b60200190565b519060ff82168203610eab57565b9190604083820312610eab5782516001600160401b038111610eab578301906101a082820312610eab57604051916101a083018381106001600160401b0382111761118957604052611269816111be565b8352611277602082016111d2565b6020840152604081015160408401526060810151606084015261129c608082016111d2565b60808401526112ad60a082016111e6565b60a084015260c08101516001600160401b038111610eab57810182601f82011215610eab578051906112de826111f3565b916112ec604051938461119d565b80835260208084019160051b83010191858311610eab57602001905b8282106114985750505060c084015260e081015160e084015261010081015161010084015261133a61012082016111d2565b61012084015261134d61014082016111d2565b61014084015261136061016082016111d2565b610160840152610180810151906001600160401b038211610eab570181601f82011215610eab57805190611393826111f3565b926113a1604051948561119d565b8284526020610120818601940283010191818311610eab57602001925b8284106113df5750505050610180820152916113dc906020016111e6565b90565b61012084830312610eab576040519061012082018281106001600160401b0382111761118957604052845182526020850151602083015260408501519061ffff82168203610eab57826020926040610120950152606087015160608201526080870151608082015261145360a088016111d2565b60a082015261146460c0880161120a565b60c082015261147560e0880161120a565b60e0820152611487610100880161120a565b6101008201528152019301926113be565b8151815260209182019101611308565b80518210156114bc5760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b818102929181159184041417156114e357565b634e487b7160e01b5f52601160045260245ffd5b905f541561185d576040516376a9021360e01b81526001600160a01b0383811660048301526001600160401b03831660248301527f00000000000000000000000064d56ce8ace5840970ad2fe0b4ed78bf0951333a1690602081604481855afa8015610e28575f9061182b575b5f915060246040518094819363bbba03b960e01b835260048301525afa8015610e28575f915f91611807575b50156117db57610180018051518410156117b057836115af91516114a8565b519060ff60e0830151166001810361179e575060a082016115dc6001600160401b03825116868487611ab7565b90815f5260036020526001600160401b0360405f20541615158061177e575b61176b5761160884611c80565b94919590951561173d5761161c8582611b72565b901561172e576116429061163d606084015161ffff604086015116906114d0565b6114d0565b90866064026064810488036114e357608061165f920151906114d0565b1161172e577feaacb149d0730aaa7f48dd4ebaa6c481fee2c2cb90fff9b2cdd12972396a6d2a936001600160401b0380936060956002604051916116a28361116e565b84431683528a61170386602086015f8152604087019384528c8701948a86525f5260036020528160405f2097511682198854161787555116859067ffffffffffffffff60401b82549160401b169067ffffffffffffffff60401b1916179055565b5160018401555191015551169560405196875260208701526040860152169360018060a01b031692a4565b6303e4914160e41b5f5260045ffd5b604051631a89d6e560e21b8152602060048201526005602482015264707269636560d81b6044820152606490fd5b506305cb5cd560e11b5f5260045260245ffd5b50815f5260036020526001600160401b0360405f205460401c16156115fb565b630b1bf39760e01b5f5260045260245ffd5b506001600160401b039163e0671a1960e01b5f5260018060a01b03166004521660245260445260645ffd5b50633bcd856f60e21b5f9081526001600160a01b039092166004526001600160401b0316602452604490fd5b905061182591503d805f833e61181d818361119d565b810190611218565b5f611590565b506020813d602011611855575b816118456020938361119d565b81010312610eab575f9051611564565b3d9150611838565b63f9b7983160e01b5f5260045ffd5b9035601e1982360301811215610eab5701602081359101916001600160401b038211610eab578136038313610eab57565b908060209392818452848401375f828201840152601f01601f1916010190565b5f5415611a1c576040516376a9021360e01b81526001600160a01b0382811660048301526001600160401b03841660248301527f00000000000000000000000064d56ce8ace5840970ad2fe0b4ed78bf0951333a1690602081604481855afa8015610e28575f90611a71575b5f915060246040518094819363bbba03b960e01b835260048301525afa8015610e28575f915f91611a55575b50158015611a45575b611a3d57836101806119719201516114a8565b5192600160ff60e08601511603611a3d5761199a926001600160401b0360a08601511692611ab7565b5f52600360205260405f20546001600160401b03811615159081611a28575b50611a23576119c781611c80565b90929115611a1c576119d99082611b72565b9015611a1c576119fa9061163d606084015161ffff604086015116906114d0565b91806064029060648204036114e3576080611a17920151906114d0565b111590565b5050505f90565b505f90565b6001600160401b03915060401c16155f6119b9565b505050505f90565b506101808101515184101561195e565b9050611a6b91503d805f833e61181d818361119d565b5f611955565b506020813d602011611a9b575b81611a8b6020938361119d565b81010312610eab575f9051611929565b3d9150611a7e565b90611aae9291611e1c565b8015611a235790565b926001600160401b0391928260405194602086019660018060a01b03168752166040850152606084015216608082015260808152611af660a08261119d565b51902090565b91908110156114bc576060020190565b356001600160a01b0381168103610eab5790565b356001600160401b0381168103610eab5790565b3d15611b6d573d906001600160401b0382116111895760405191611b62601f8201601f19166020018461119d565b82523d5f602084013e565b606090565b608090600160ff61010083015116145f14611ba35701519081811115611b9b5703905b60019190565b50505f905f90565b015181811115611b9b570390611b95565b809291036101608112610eab5761014013610eab5760405161014081018181106001600160401b038211176111895760405282518152602083015160208201526040830151604082015260608301516060820152611c146080840161120a565b6080820152611c2560a0840161120a565b60a082015260c083015163ffffffff81168103610eab57816101409160c06113dc94015260e085015160e0820152611c6061010086016111d2565b610100820152611c7361012086016111d2565b61012082015293016111e6565b906060820151158015611e10575b8015611e00575b611df75760018060a01b037f00000000000000000000000064d56ce8ace5840970ad2fe0b4ed78bf0951333a16915f54906001546040519263ddcc4b0d60e01b845260048401525f602484015280604484015261016083606481885afa918215610e28575f935f93611dca575b509060646101609251604051978893849263ddcc4b0d60e01b845260048401525f602484015260448301525afa908115610e28575f945f92611d96575b5015908115611d8d575b508015611d81575b8015611d75575b611d6b5760609081015192015160019291565b505f915081908190565b50606083015115611d58565b50606081015115611d51565b9050155f611d49565b909450611dbb91506101603d8111611dc3575b611db3818361119d565b810190611bb4565b90935f611d3f565b503d611da9565b6064945061016092919350611deb90833d8111611dc357611db3818361119d565b94909493919250611d02565b5f915081908190565b5061ffff60408301511615611c95565b50608082015115611c8e565b906004821015611f5f578115611f58575f92839260018114611f2f57600214611ea757604051627eeac760e11b602082019081523060248301526044820192909252611e6b81606481016107ff565b51915afa611e77611b34565b9080611e9b575b15611a235760208151918180820193849201010312610eab575190565b50602081511015611e7e565b60405160208101916331a9108f60e11b8352602482015260248152611ecd60448261119d565b51915afa611ed9611b34565b81611f21575b81611ef4575b5015611ef057600190565b5f90565b9050602081805181010312610eab57602001516001600160a01b03811690819003610eab5730145f611ee5565b905060208151101590611edf565b505060405160208101906370a0823160e01b825230602482015260248152611e6b60448261119d565b5050504790565b634e487b7160e01b5f52602160045260245ffd5b6040516328305db160e21b81527f000000000000000000000000c19d888a2f7ba65a8dcf8d03ad8f1af5cdc664306001600160a01b031690602081600481855afa908115610e28575f91612128575b5015806120b8575b6120b55760405163e14c465b60e01b8152602081600481855afa908115610e28575f91612081575b50604051632e4bfa5160e11b815233600482015260248101919091529060209082908180604481015b03915afa908115610e28575f91612047575b506120455763321cbc0960e21b5f523360045260245ffd5b565b90506020813d602011612079575b816120626020938361119d565b81010312610eab57612073906111e6565b5f61202d565b3d9150612055565b90506020813d6020116120ad575b8161209c6020938361119d565b81010312610eab575161201b611ff2565b3d915061208f565b50565b5060405163f5778b0360e01b8152602081600481855afa908115610e28575f916120ee575b506001600160a01b03163314611fca565b90506020813d602011612120575b816121096020938361119d565b81010312610eab5761211a906111be565b5f6120dd565b3d91506120fc565b90506020813d60201161215a575b816121436020938361119d565b81010312610eab57612154906111e6565b5f611fc2565b3d9150612136565b90813b156121e9575f816020829351910182855af161217f611b34565b90159081156121b1575b506121915750565b6365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b80518015159250826121c6575b50505f612189565b8192509060209181010312610eab5760206121e191016111e6565b155f806121be565b506365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd
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
0002DUP1
0003PUSH10x40
0005MSTORE
0006PUSH10x04
0008CALLDATASIZE
0009LT
000aISZERO
000bPUSH20x0012
000eJUMPI
000fPUSH0
0010DUP1
0011REVERT
0012JUMPDEST
0013PUSH0
0014SWAP1
0015PUSH0
0016CALLDATALOAD
0017PUSH10xe0
0019SHR
001aSWAP1
001bDUP2
001cPUSH40x0f43d24a
0021EQ
0022PUSH20x101b
0025JUMPI
0026POP
0027DUP1
0028PUSH40x150b7a02
002dEQ
002ePUSH20x0fc5
0031JUMPI
0032DUP1
0033PUSH40x19665cb0
0038EQ
0039PUSH20x0fac
003cJUMPI
003dDUP1
003ePUSH40x21e39169
0043EQ
0044PUSH20x0f5a
0047JUMPI
0048DUP1
0049PUSH40x32971704
004eEQ
004fPUSH20x0c31
0052JUMPI
0053DUP1
0054PUSH40x423ad109
0059EQ
005aPUSH20x0c0d
005dJUMPI
005eDUP1
005fPUSH40x47989413
0064EQ
0065PUSH20x0bf0
0068JUMPI
0069DUP1
006aPUSH40x5bccff36
006fEQ
0070PUSH20x0b4d
0073JUMPI
0074DUP1
0075PUSH40x60a18008
007aEQ
007bPUSH20x0b17
007eJUMPI
007fDUP1
0080PUSH40x629d7b8a
0085EQ
0086PUSH20x0af2
0089JUMPI
008aDUP1
008bPUSH40x7b103999
0090EQ
0091PUSH20x0aad
0094JUMPI
0095DUP1
0096PUSH40x7d4bc12a
009bEQ
009cPUSH20x099c
009fJUMPI
00a0DUP1
00a1PUSH40x95a3be99
00a6EQ
00a7PUSH20x0957
00aaJUMPI
00abDUP1
00acPUSH40x96f51f3a
00b1EQ
00b2PUSH20x065a
00b5JUMPI
00b6DUP1
00b7PUSH40x98356204
00bcEQ
00bdPUSH20x01ff
00c0JUMPI
00c1DUP1
00c2PUSH40xa7ce2703
00c7EQ
00c8PUSH20x01d8
00cbJUMPI
00ccDUP1
00cdPUSH40xbc197c81
00d2EQ
00d3PUSH20x013f
00d6JUMPI
00d7PUSH40xf23a6e61
00dcEQ
00ddPUSH20x00e4
00e0JUMPI
00e1PUSH0
00e2DUP1
00e3REVERT
00e4JUMPDEST
00e5CALLVALUE
00e6PUSH20x013c
00e9JUMPI
00eaPUSH10xa0
00ecCALLDATASIZE
00edPUSH10x03
00efNOT
00f0ADD
00f1SLT
00f2PUSH20x013c
00f5JUMPI
00f6PUSH20x00fd
00f9PUSH20x1035
00fcJUMP
00fdJUMPDEST
00fePOP
00ffPUSH20x0106
0102PUSH20x104b
0105JUMP
0106JUMPDEST
0107POP
0108PUSH10x84
010aCALLDATALOAD
010bPUSH10x01
010dPUSH10x01
010fPUSH10x40
0111SHL
0112SUB
0113DUP2
0114GT
0115PUSH20x013a
0118JUMPI
0119PUSH20x0126
011cSWAP1
011dCALLDATASIZE
011eSWAP1
011fPUSH10x04
0121ADD
0122PUSH20x1075
0125JUMP
0126JUMPDEST
0127POP
0128POP
0129PUSH10x40
012bMLOAD
012cPUSH40xf23a6e61
0131PUSH10xe0
0133SHL
0134DUP2
0135MSTORE
0136PUSH10x20
0138SWAP1
0139RETURN
013aJUMPDEST
013bPOP
013cJUMPDEST
013dDUP1
013eREVERT
013fJUMPDEST
0140POP
0141CALLVALUE
0142PUSH20x013c
0145JUMPI
0146PUSH10xa0
0148CALLDATASIZE
0149PUSH10x03
014bNOT
014cADD
014dSLT
014ePUSH20x013c
0151JUMPI
0152PUSH20x0159
0155PUSH20x1035
0158JUMP
0159JUMPDEST
015aPOP
015bPUSH20x0162
015ePUSH20x104b
0161JUMP
0162JUMPDEST
0163POP
0164PUSH10x44
0166CALLDATALOAD
0167PUSH10x01
0169PUSH10x01
016bPUSH10x40
016dSHL
016eSUB
016fDUP2
0170GT
0171PUSH20x013a
0174JUMPI
0175PUSH20x0182
0178SWAP1
0179CALLDATASIZE
017aSWAP1
017bPUSH10x04
017dADD
017ePUSH20x113e
0181JUMP
0182JUMPDEST
0183POP
0184POP
0185PUSH10x64
0187CALLDATALOAD
0188PUSH10x01
018aPUSH10x01
018cPUSH10x40
018eSHL
018fSUB
0190DUP2
0191GT
0192PUSH20x013a
0195JUMPI
0196PUSH20x01a3
0199SWAP1
019aCALLDATASIZE
019bSWAP1
019cPUSH10x04
019eADD
019fPUSH20x113e
01a2JUMP
01a3JUMPDEST
01a4POP
01a5POP
01a6PUSH10x84
01a8CALLDATALOAD
01a9PUSH10x01
01abPUSH10x01
01adPUSH10x40
01afSHL
01b0SUB
01b1DUP2
01b2GT
01b3PUSH20x013a
01b6JUMPI
01b7PUSH20x01c4
01baSWAP1
01bbCALLDATASIZE
01bcSWAP1
01bdPUSH10x04
01bfADD
01c0PUSH20x1075
01c3JUMP
01c4JUMPDEST
01c5POP
01c6POP
01c7PUSH10x40
01c9MLOAD
01caPUSH40xbc197c81
01cfPUSH10xe0
01d1SHL
01d2DUP2
01d3MSTORE
01d4PUSH10x20
01d6SWAP1
01d7RETURN
01d8JUMPDEST
01d9POP
01daCALLVALUE
01dbPUSH20x013c
01deJUMPI
01dfDUP1
01e0PUSH10x03
01e2NOT
01e3CALLDATASIZE
01e4ADD
01e5SLT
01e6PUSH20x013c
01e9JUMPI
01eaPUSH10x20
01ecPUSH10x01
01eePUSH10x01
01f0PUSH10x40
01f2SHL
01f3SUB
01f4PUSH10x02
01f6SLOAD
01f7AND
01f8PUSH10x40
01faMLOAD
01fbSWAP1
01fcDUP2
01fdMSTORE
01feRETURN
01ffJUMPDEST
0200POP
0201CALLVALUE
0202PUSH20x013c
0205JUMPI
0206PUSH10x60
0208CALLDATASIZE
0209PUSH10x03
020bNOT
020cADD
020dSLT
020ePUSH20x013c
0211JUMPI
0212PUSH10x04
0214CALLDATALOAD
0215PUSH10x01
0217PUSH10x01
0219PUSH10x40
021bSHL
021cSUB
021dDUP2
021eGT
021fPUSH20x013a
0222JUMPI
0223CALLDATASIZE
0224PUSH10x23
0226DUP3
0227ADD
0228SLT
0229ISZERO
022aPUSH20x013a
022dJUMPI
022eDUP1
022fPUSH10x04
0231ADD
0232CALLDATALOAD
0233SWAP2
0234PUSH10x01
0236PUSH10x01
0238PUSH10x40
023aSHL
023bSUB
023cDUP4
023dGT
023ePUSH20x013c
0241JUMPI
0242CALLDATASIZE
0243PUSH10x24
0245DUP5
0246PUSH10x07
0248SHL
0249DUP5
024aADD
024bADD
024cGT
024dPUSH20x013c
0250JUMPI
0251PUSH10x24
0253CALLDATALOAD
0254PUSH10x01
0256PUSH10x01
0258PUSH10x40
025aSHL
025bSUB
025cDUP2
025dAND
025eDUP1
025fSWAP2
0260SUB
0261PUSH20x013a
0264JUMPI
0265PUSH10x44
0267CALLDATALOAD
0268PUSH10x01
026aPUSH10x01
026cPUSH10x40
026eSHL
026fSUB
0270DUP2
0271GT
0272PUSH20x0656
0275JUMPI
0276PUSH20x0283
0279SWAP1
027aCALLDATASIZE
027bSWAP1
027cPUSH10x04
027eADD
027fPUSH20x113e
0282JUMP
0283JUMPDEST
0284SWAP2
0285SWAP1
0286PUSH10x01
0288PUSH10x01
028aPUSH10x40
028cSHL
028dSUB
028ePUSH10x02
0290SLOAD
0291AND
0292SWAP3
0293PUSH10x01
0295DUP1
0296PUSH10xa0
0298SHL
0299SUB
029aPUSH320x000000000000000000000000c19d888a2f7ba65a8dcf8d03ad8f1af5cdc66430
02bbAND
02bcSWAP2
02bdPUSH10x40
02bfMLOAD
02c0PUSH10x20
02c2DUP2
02c3ADD
02c4SWAP1
02c5DUP10
02c6PUSH10x60
02c8DUP3
02c9ADD
02caDUP9
02cbDUP5
02ccMSTORE
02cdPUSH10x40
02cfDUP1
02d0DUP5
02d1ADD
02d2MSTORE
02d3MSTORE
02d4PUSH10x80
02d6DUP2
02d7ADD
02d8DUP2
02d9PUSH10x24
02dbDUP12
02dcADD
02ddSWAP2
02deDUP11
02dfJUMPDEST
02e0DUP14
02e1DUP2
02e2LT
02e3PUSH20x05ec
02e6JUMPI
02e7POP
02e8PUSH20x02fa
02ebSWAP3
02ecPOP
02edSUB
02eePUSH10x1f
02f0NOT
02f1DUP2
02f2ADD
02f3DUP4
02f4MSTORE
02f5DUP3
02f6PUSH20x119d
02f9JUMP
02faJUMPDEST
02fbMLOAD
02fcSWAP1
02fdKECCAK256
02feDUP4
02ffEXTCODESIZE
0300ISZERO
0301PUSH20x05e8
0304JUMPI
0305SWAP4
0306SWAP2
0307DUP7
0308SWAP4
0309SWAP2
030aDUP2
030bPUSH10x40
030dMLOAD
030eSWAP7
030fDUP8
0310SWAP6
0311PUSH40x22f3f447
0316PUSH10xe1
0318SHL
0319DUP8
031aMSTORE
031bPUSH10x84
031dDUP8
031eADD
031fSWAP2
0320PUSH320x6e6bcb1868fa292b670e04e1a01bef2031a85041bccd38bca20cee8ccfadeec4
0341PUSH10x04
0343DUP10
0344ADD
0345MSTORE
0346PUSH10x24
0348DUP9
0349ADD
034aMSTORE
034bPUSH10x44
034dDUP8
034eADD
034fMSTORE
0350PUSH10x80
0352PUSH10x64
0354DUP8
0355ADD
0356MSTORE
0357MSTORE
0358PUSH10xa4
035aDUP5
035bADD
035cPUSH10xa0
035ePUSH10x04
0360DUP5
0361PUSH10x05
0363SHL
0364DUP8
0365ADD
0366ADD
0367ADD
0368SWAP3
0369DUP3
036aDUP8
036bSWAP1
036cPUSH10x7e
036eNOT
036fDUP2
0370CALLDATASIZE
0371SUB
0372ADD
0373JUMPDEST
0374DUP4
0375DUP4
0376LT
0377PUSH20x0538
037aJUMPI
037bPOP
037cPOP
037dPOP
037ePOP
037fPOP
0380POP
0381DUP4
0382DUP4
0383DUP3
0384DUP2
0385SWAP4
0386POP
0387SUB
0388SWAP3
0389GAS
038aCALL
038bDUP1
038cISZERO
038dPUSH20x052d
0390JUMPI
0391SWAP1
0392DUP4
0393SWAP2
0394PUSH20x0518
0397JUMPI
0398JUMPDEST
0399POP
039aPOP
039bPUSH10x01
039dADD
039ePUSH10x01
03a0PUSH10x01
03a2PUSH10x40
03a4SHL
03a5SUB
03a6DUP2
03a7GT
03a8PUSH20x0504
03abJUMPI
03acPUSH10x02
03aeDUP1
03afSLOAD
03b0PUSH80xffffffffffffffff
03b9NOT
03baAND
03bbPUSH10x01
03bdPUSH10x01
03bfPUSH10x40
03c1SHL
03c2SUB
03c3SWAP3
03c4DUP4
03c5AND
03c6OR
03c7SWAP1
03c8SSTORE
03c9NUMBER
03caAND
03cbDUP4
03ccDUP3
03cdDUP5
03ceJUMPDEST
03cfDUP3
03d0DUP6
03d1LT
03d2ISZERO
03d3PUSH20x0500
03d6JUMPI
03d7DUP5
03d8PUSH10x07
03daSHL
03dbDUP2
03dcADD
03ddSWAP5
03dePUSH10x84
03e0PUSH10x24
03e2DUP8
03e3ADD
03e4SWAP7
03e5PUSH20x03ed
03e8DUP9
03e9PUSH20x1b0c
03ecJUMP
03edJUMPDEST
03eeSWAP1
03efPUSH20x0419
03f2PUSH10x44
03f4DUP3
03f5ADD
03f6SWAP3
03f7PUSH20x03ff
03faDUP5
03fbPUSH20x1b20
03feJUMP
03ffJUMPDEST
0400SWAP3
0401PUSH10x64
0403DUP2
0404ADD
0405CALLDATALOAD
0406SWAP6
0407DUP7
0408SWAP2
0409ADD
040aSWAP4
040bPUSH20x0413
040eDUP6
040fPUSH20x1b20
0412JUMP
0413JUMPDEST
0414SWAP3
0415PUSH20x1ab7
0418JUMP
0419JUMPDEST
041aDUP1
041bDUP8
041cMSTORE
041dPUSH10x03
041fPUSH10x20
0421MSTORE
0422PUSH10x40
0424DUP8
0425KECCAK256
0426SWAP1
0427DUP2
0428SLOAD
0429PUSH10x01
042bPUSH10x01
042dPUSH10x40
042fSHL
0430SUB
0431DUP2
0432AND
0433ISZERO
0434SWAP1
0435DUP2
0436ISZERO
0437PUSH20x04ea
043aJUMPI
043bJUMPDEST
043cPOP
043dPUSH20x04d8
0440JUMPI
0441POP
0442PUSH10x20
0444PUSH10x01
0446PUSH10x01
0448PUSH10x40
044aSHL
044bSUB
044cPUSH20x04ba
044fPUSH20x04b4
0452PUSH20x04ae
0455DUP14
0456SWAP15
0457PUSH20x04a9
045aPUSH10x01
045cSWAP13
045dSWAP14
045eSWAP15
045fSWAP16
0460PUSH320xd1882d17658f987f7ff6186a3833d01f3fd3e5451dbe4fbb8730da2111a31666
0481SWAP9
0482SWAP1
0483PUSH80xffffffffffffffff
048cPUSH10x40
048eSHL
048fDUP3
0490SLOAD
0491SWAP2
0492PUSH10x40
0494SHL
0495AND
0496SWAP1
0497PUSH80xffffffffffffffff
04a0PUSH10x40
04a2SHL
04a3NOT
04a4AND
04a5OR
04a6SWAP1
04a7SSTORE
04a8JUMP
04a9JUMPDEST
04aaPUSH20x1b0c
04adJUMP
04aeJUMPDEST
04afSWAP7
04b0PUSH20x1b20
04b3JUMP
04b4JUMPDEST
04b5SWAP5
04b6PUSH20x1b20
04b9JUMP
04baJUMPDEST
04bbSWAP4
04bcDUP2
04bdPUSH10x40
04bfMLOAD
04c0SWAP6
04c1AND
04c2DUP6
04c3MSTORE
04c4AND
04c5SWAP4
04c6DUP8
04c7DUP1
04c8PUSH10xa0
04caSHL
04cbSUB
04ccAND
04cdSWAP3
04ceLOG4
04cfADD
04d0SWAP4
04d1SWAP3
04d2SWAP2
04d3SWAP1
04d4PUSH20x03ce
04d7JUMP
04d8JUMPDEST
04d9PUSH40x697e74d5
04dePUSH10xe1
04e0SHL
04e1DUP9
04e2MSTORE
04e3PUSH10x04
04e5MSTORE
04e6PUSH10x24
04e8DUP8
04e9REVERT
04eaJUMPDEST
04ebPUSH10x01
04edPUSH10x01
04efPUSH10x40
04f1SHL
04f2SUB
04f3SWAP2
04f4POP
04f5PUSH10x40
04f7SHR
04f8AND
04f9ISZERO
04faISZERO
04fbDUP13
04fcPUSH20x043b
04ffJUMP
0500JUMPDEST
0501POP
0502DUP1
0503RETURN
0504JUMPDEST
0505PUSH40x4e487b71
050aPUSH10xe0
050cSHL
050dDUP3
050eMSTORE
050fPUSH10x11
0511PUSH10x04
0513MSTORE
0514PUSH10x24
0516DUP3
0517REVERT
0518JUMPDEST
0519DUP2
051aPUSH20x0522
051dSWAP2
051ePUSH20x119d
0521JUMP
0522JUMPDEST
0523PUSH20x013a
0526JUMPI
0527DUP2
0528PUSH0
0529PUSH20x0398
052cJUMP
052dJUMPDEST
052ePUSH10x40
0530MLOAD
0531RETURNDATASIZE
0532DUP6
0533DUP3
0534RETURNDATACOPY
0535RETURNDATASIZE
0536SWAP1
0537REVERT
0538JUMPDEST
0539PUSH10xa3
053bNOT
053cDUP12
053dDUP9
053eSUB
053fADD
0540DUP6
0541MSTORE
0542SWAP7
0543SWAP9
0544POP
0545SWAP4
0546SWAP7
0547POP
0548SWAP2
0549SWAP5
054aSWAP1
054bSWAP4
054cSWAP2
054dSWAP3
054eSWAP2
054fDUP7
0550CALLDATALOAD
0551DUP3
0552DUP2
0553SLT
0554ISZERO
0555PUSH20x05e4
0558JUMPI
0559DUP4
055aADD
055bSWAP1
055cPUSH10x01
055ePUSH10x01
0560PUSH10xa0
0562SHL
0563SUB
0564PUSH20x056c
0567DUP4
0568PUSH20x1061
056bJUMP
056cJUMPDEST
056dAND
056eDUP2
056fMSTORE
0570PUSH10x20
0572DUP3
0573ADD
0574CALLDATALOAD
0575PUSH10xff
0577DUP2
0578AND
0579DUP1
057aSWAP2
057bSUB
057cPUSH20x05e0
057fJUMPI
0580PUSH20x05cc
0583PUSH10x01
0585SWAP4
0586DUP4
0587PUSH10x20
0589SWAP5
058aSWAP4
058bDUP6
058cDUP1
058dSWAP6
058eADD
058fMSTORE
0590PUSH20x05be
0593PUSH20x05b3
0596PUSH20x05a2
0599PUSH10x40
059bDUP6
059cADD
059dDUP6
059ePUSH20x186c
05a1JUMP
05a2JUMPDEST
05a3PUSH10x80
05a5PUSH10x40
05a7DUP7
05a8ADD
05a9MSTORE
05aaPUSH10x80
05acDUP6
05adADD
05aeSWAP2
05afPUSH20x189d
05b2JUMP
05b3JUMPDEST
05b4SWAP3
05b5PUSH10x60
05b7DUP2
05b8ADD
05b9SWAP1
05baPUSH20x186c
05bdJUMP
05beJUMPDEST
05bfSWAP2
05c0PUSH10x60
05c2DUP2
05c3DUP6
05c4SUB
05c5SWAP2
05c6ADD
05c7MSTORE
05c8PUSH20x189d
05cbJUMP
05ccJUMPDEST
05cdSWAP9
05ceADD
05cfSWAP7
05d0ADD
05d1SWAP4
05d2ADD
05d3DUP11
05d4SWAP8
05d5SWAP6
05d6SWAP4
05d7SWAP3
05d8DUP10
05d9SWAP8
05daSWAP6
05dbSWAP3
05dcPUSH20x0373
05dfJUMP
05e0JUMPDEST
05e1DUP13
05e2DUP1
05e3REVERT
05e4JUMPDEST
05e5DUP12
05e6DUP1
05e7REVERT
05e8JUMPDEST
05e9DUP7
05eaDUP1
05ebREVERT
05ecJUMPDEST
05edSWAP2
05eePOP
05efPUSH10x01
05f1SWAP1
05f2PUSH10x80
05f4SWAP1
05f5DUP2
05f6SWAP1
05f7PUSH10x01
05f9PUSH10x01
05fbPUSH10xa0
05fdSHL
05feSUB
05ffPUSH20x0607
0602DUP8
0603PUSH20x1061
0606JUMP
0607JUMPDEST
0608AND
0609DUP2
060aMSTORE
060bPUSH10x01
060dPUSH10x01
060fPUSH10x40
0611SHL
0612SUB
0613PUSH20x061e
0616PUSH10x20
0618DUP9
0619ADD
061aPUSH20x10a2
061dJUMP
061eJUMPDEST
061fAND
0620PUSH10x20
0622DUP3
0623ADD
0624MSTORE
0625PUSH10x40
0627DUP7
0628ADD
0629CALLDATALOAD
062aPUSH10x40
062cDUP3
062dADD
062eMSTORE
062fPUSH10x01
0631PUSH10x01
0633PUSH10x40
0635SHL
0636SUB
0637PUSH20x0642
063aPUSH10x60
063cDUP9
063dADD
063ePUSH20x10a2
0641JUMP
0642JUMPDEST
0643AND
0644PUSH10x60
0646DUP3
0647ADD
0648MSTORE
0649ADD
064aSWAP4
064bADD
064cSWAP2
064dADD
064eSWAP2
064fDUP4
0650SWAP2
0651SWAP3
0652PUSH20x02df
0655JUMP
0656JUMPDEST
0657DUP3
0658DUP1
0659REVERT
065aJUMPDEST
065bPOP
065cCALLVALUE
065dPUSH20x013c
0660JUMPI
0661PUSH10xa0
0663CALLDATASIZE
0664PUSH10x03
0666NOT
0667ADD
0668SLT
0669PUSH20x013c
066cJUMPI
066dPUSH10x04
066fCALLDATALOAD
0670PUSH10x04
0672DUP2
0673LT
0674ISZERO
0675PUSH20x013a
0678JUMPI
0679PUSH20x0680
067cPUSH20x104b
067fJUMP
0680JUMPDEST
0681SWAP2
0682PUSH10x64
0684CALLDATALOAD
0685SWAP2
0686PUSH10x84
0688CALLDATALOAD
0689PUSH10x01
068bPUSH10x01
068dPUSH10xa0
068fSHL
0690SUB
0691DUP2
0692AND
0693SWAP3
0694PUSH10x44
0696CALLDATALOAD
0697SWAP3
0698SWAP2
0699DUP5
069aDUP2
069bSUB
069cPUSH20x013a
069fJUMPI
06a0PUSH20x06a7
06a3PUSH20x1f73
06a6JUMP
06a7JUMPDEST
06a8PUSH10x40
06aaMLOAD
06abPUSH40xf5778b03
06b0PUSH10xe0
06b2SHL
06b3DUP2
06b4MSTORE
06b5PUSH10x20
06b7DUP2
06b8PUSH10x04
06baDUP2
06bbPUSH320x000000000000000000000000c19d888a2f7ba65a8dcf8d03ad8f1af5cdc66430
06dcPUSH10x01
06dePUSH10x01
06e0PUSH10xa0
06e2SHL
06e3SUB
06e4AND
06e5GAS
06e6STATICCALL
06e7SWAP1
06e8DUP2
06e9ISZERO
06eaPUSH20x052d
06edJUMPI
06eeDUP4
06efSWAP2
06f0PUSH20x091d
06f3JUMPI
06f4JUMPDEST
06f5POP
06f6DUP6
06f7ISZERO
06f8SWAP1
06f9DUP2
06faISZERO
06fbPUSH20x08f9
06feJUMPI
06ffJUMPDEST
0700POP
0701PUSH20x08e5
0704JUMPI
0705PUSH20x070f
0708DUP5
0709DUP9
070aDUP6
070bPUSH20x1aa3
070eJUMP
070fJUMPDEST
0710SWAP6
0711PUSH0
0712NOT
0713DUP2
0714SUB
0715PUSH20x08e0
0718JUMPI
0719POP
071aDUP6
071bJUMPDEST
071cDUP1
071dSWAP7
071eDUP2
071fISZERO
0720PUSH20x08d1
0723JUMPI
0724DUP1
0725DUP3
0726GT
0727PUSH20x08ac
072aJUMPI
072bPOP
072cDUP3
072dSWAP2
072eDUP5
072fPUSH20x07be
0732JUMPI
0733POP
0734POP
0735DUP2
0736DUP1
0737DUP1
0738DUP1
0739DUP10
073aDUP10
073bGAS
073cCALL
073dPUSH20x0744
0740PUSH20x1b34
0743JUMP
0744JUMPDEST
0745POP
0746ISZERO
0747PUSH20x07aa
074aJUMPI
074bJUMPDEST
074cPUSH20x0796
074fJUMPI
0750POP
0751PUSH10x40
0753DUP1
0754MLOAD
0755SWAP3
0756DUP4
0757MSTORE
0758PUSH10x20
075aDUP4
075bDUP2
075cADD
075dDUP7
075eSWAP1
075fMSTORE
0760SWAP6
0761PUSH10x01
0763PUSH10x01
0765PUSH10xa0
0767SHL
0768SUB
0769AND
076aSWAP3
076bPUSH320x7643c83e539cea2f6bf506545392e52cfd5f917e327efbcd0ba28f29c28d042e
078cSWAP2
078dSWAP1
078eLOG4
078fPUSH10x40
0791MLOAD
0792SWAP1
0793DUP2
0794MSTORE
0795RETURN
0796JUMPDEST
0797PUSH40x4e487b71
079cPUSH10xe0
079eSHL
079fDUP2
07a0MSTORE
07a1PUSH10x21
07a3PUSH10x04
07a5MSTORE
07a6PUSH10x24
07a8SWAP1
07a9REVERT
07aaJUMPDEST
07abPUSH40x65f4a9ef
07b0PUSH10xe1
07b2SHL
07b3DUP3
07b4MSTORE
07b5PUSH10x04
07b7DUP3
07b8SWAP1
07b9MSTORE
07baPUSH10x24
07bcDUP3
07bdREVERT
07beJUMPDEST
07bfDUP4
07c0SWAP3
07c1POP
07c2SWAP1
07c3PUSH10x01
07c5DUP6
07c6SUB
07c7PUSH20x0818
07caJUMPI
07cbPOP
07ccPUSH10x40
07ceMLOAD
07cfPUSH40xa9059cbb
07d4PUSH10xe0
07d6SHL
07d7PUSH10x20
07d9DUP3
07daADD
07dbMSTORE
07dcPUSH10x01
07dePUSH10x01
07e0PUSH10xa0
07e2SHL
07e3SUB
07e4SWAP1
07e5SWAP2
07e6AND
07e7PUSH10x24
07e9DUP3
07eaADD
07ebMSTORE
07ecPUSH10x44
07eeDUP2
07efADD
07f0DUP8
07f1SWAP1
07f2MSTORE
07f3PUSH20x0813
07f6SWAP1
07f7PUSH20x080d
07faDUP2
07fbPUSH10x64
07fdDUP2
07feADD
07ffJUMPDEST
0800SUB
0801PUSH10x1f
0803NOT
0804DUP2
0805ADD
0806DUP4
0807MSTORE
0808DUP3
0809PUSH20x119d
080cJUMP
080dJUMPDEST
080eDUP9
080fPUSH20x2162
0812JUMP
0813JUMPDEST
0814PUSH20x074b
0817JUMP
0818JUMPDEST
0819SWAP7
081aSWAP2
081bPOP
081cPOP
081dDUP2
081eSWAP6
081fPUSH10x02
0821DUP5
0822EQ
0823PUSH0
0824EQ
0825PUSH20x0861
0828JUMPI
0829POP
082aPOP
082bPUSH10x01
082dSWAP5
082ePUSH20x0813
0831PUSH10x40
0833MLOAD
0834PUSH40x23b872dd
0839PUSH10xe0
083bSHL
083cPUSH10x20
083eDUP3
083fADD
0840MSTORE
0841ADDRESS
0842PUSH10x24
0844DUP3
0845ADD
0846MSTORE
0847DUP7
0848PUSH10x44
084aDUP3
084bADD
084cMSTORE
084dDUP6
084ePUSH10x64
0850DUP3
0851ADD
0852MSTORE
0853PUSH10x64
0855DUP2
0856MSTORE
0857PUSH20x080d
085aPUSH10x84
085cDUP3
085dPUSH20x119d
0860JUMP
0861JUMPDEST
0862PUSH20x0813
0865SWAP1
0866PUSH10x40
0868SWAP8
0869SWAP3
086aSWAP8
086bMLOAD
086cSWAP1
086dPUSH40x79212195
0872PUSH10xe1
0874SHL
0875PUSH10x20
0877DUP4
0878ADD
0879MSTORE
087aADDRESS
087bPUSH10x24
087dDUP4
087eADD
087fMSTORE
0880DUP8
0881PUSH10x44
0883DUP4
0884ADD
0885MSTORE
0886DUP7
0887PUSH10x64
0889DUP4
088aADD
088bMSTORE
088cPUSH10x84
088eDUP3
088fADD
0890MSTORE
0891PUSH10xa0
0893PUSH10xa4
0895DUP3
0896ADD
0897MSTORE
0898DUP4
0899PUSH10xc4
089bDUP3
089cADD
089dMSTORE
089ePUSH10xc4
08a0DUP2
08a1MSTORE
08a2PUSH20x080d
08a5PUSH10xe4
08a7DUP3
08a8PUSH20x119d
08abJUMP
08acJUMPDEST
08adPUSH40x21909681
08b2PUSH10xe0
08b4SHL
08b5DUP5
08b6MSTORE
08b7PUSH10x01
08b9PUSH10x01
08bbPUSH10xa0
08bdSHL
08beSUB
08bfDUP10
08c0AND
08c1PUSH10x04
08c3MSTORE
08c4PUSH10x24
08c6SWAP2
08c7SWAP1
08c8SWAP2
08c9MSTORE
08caPUSH10x44
08ccMSTORE
08cdPUSH10x64
08cfDUP3
08d0REVERT
08d1JUMPDEST
08d2PUSH40x7c2e506f
08d7PUSH10xe1
08d9SHL
08daDUP5
08dbMSTORE
08dcPUSH10x04
08deDUP5
08dfREVERT
08e0JUMPDEST
08e1PUSH20x071b
08e4JUMP
08e5JUMPDEST
08e6PUSH40x15150d4d
08ebPUSH10xe3
08edSHL
08eeDUP3
08efMSTORE
08f0PUSH10x04
08f2DUP6
08f3SWAP1
08f4MSTORE
08f5PUSH10x24
08f7DUP3
08f8REVERT
08f9JUMPDEST
08faPUSH10x01
08fcPUSH10x01
08fePUSH10xa0
0900SHL
0901SUB
0902AND
0903DUP7
0904EQ
0905ISZERO
0906SWAP1
0907POP
0908DUP1
0909PUSH20x0913
090cJUMPI
090dJUMPDEST
090ePUSH0
090fPUSH20x06ff
0912JUMP
0913JUMPDEST
0914POP
0915CALLER
0916DUP6
0917EQ
0918ISZERO
0919PUSH20x090d
091cJUMP
091dJUMPDEST
091eSWAP1
091fPOP
0920PUSH10x20
0922DUP2
0923RETURNDATASIZE
0924PUSH10x20
0926GT
0927PUSH20x094f
092aJUMPI
092bJUMPDEST
092cDUP2
092dPUSH20x0938
0930PUSH10x20
0932SWAP4
0933DUP4
0934PUSH20x119d
0937JUMP
0938JUMPDEST
0939DUP2
093aADD
093bSUB
093cSLT
093dPUSH20x0656
0940JUMPI
0941PUSH20x0949
0944SWAP1
0945PUSH20x11be
0948JUMP
0949JUMPDEST
094aPUSH0
094bPUSH20x06f4
094eJUMP
094fJUMPDEST
0950RETURNDATASIZE
0951SWAP2
0952POP
0953PUSH20x092b
0956JUMP
0957JUMPDEST
0958POP
0959CALLVALUE
095aPUSH20x013c
095dJUMPI
095eDUP1
095fPUSH10x03
0961NOT
0962CALLDATASIZE
0963ADD
0964SLT
0965PUSH20x013c
0968JUMPI
0969PUSH10x40
096bMLOAD
096cPUSH320x00000000000000000000000064d56ce8ace5840970ad2fe0b4ed78bf0951333a
098dPUSH10x01
098fPUSH10x01
0991PUSH10xa0
0993SHL
0994SUB
0995AND
0996DUP2
0997MSTORE
0998PUSH10x20
099aSWAP1
099bRETURN
099cJUMPDEST
099dPOP
099eCALLVALUE
099fPUSH20x013c
09a2JUMPI
09a3PUSH10x20
09a5CALLDATASIZE
09a6PUSH10x03
09a8NOT
09a9ADD
09aaSLT
09abPUSH20x013c
09aeJUMPI
09afPUSH10x04
09b1CALLDATALOAD
09b2PUSH10x01
09b4PUSH10x01
09b6PUSH10x40
09b8SHL
09b9SUB
09baDUP2
09bbGT
09bcPUSH20x013a
09bfJUMPI
09c0CALLDATASIZE
09c1PUSH10x23
09c3DUP3
09c4ADD
09c5SLT
09c6ISZERO
09c7PUSH20x013a
09caJUMPI
09cbDUP1
09ccPUSH10x04
09ceADD
09cfCALLDATALOAD
09d0SWAP1
09d1PUSH10x01
09d3PUSH10x01
09d5PUSH10x40
09d7SHL
09d8SUB
09d9DUP3
09daGT
09dbPUSH20x0656
09deJUMPI
09dfPUSH10x24
09e1DUP2
09e2ADD
09e3SWAP1
09e4PUSH10x24
09e6CALLDATASIZE
09e7SWAP2
09e8PUSH10x60
09eaDUP6
09ebMUL
09ecADD
09edADD
09eeGT
09efPUSH20x0656
09f2JUMPI
09f3DUP3
09f4SWAP2
09f5DUP4
09f6JUMPDEST
09f7DUP2
09f8DUP2
09f9LT
09faPUSH20x0a08
09fdJUMPI
09fePUSH10x20
0a00DUP5
0a01PUSH10x40
0a03MLOAD
0a04SWAP1
0a05DUP2
0a06MSTORE
0a07RETURN
0a08JUMPDEST
0a09PUSH20x0a44
0a0cPUSH20x0a19
0a0fPUSH20x04a9
0a12DUP4
0a13DUP6
0a14DUP8
0a15PUSH20x1afc
0a18JUMP
0a19JUMPDEST
0a1aPUSH20x0a2f
0a1dPUSH10x20
0a1fPUSH20x0a29
0a22DUP6
0a23DUP8
0a24DUP10
0a25PUSH20x1afc
0a28JUMP
0a29JUMPDEST
0a2aADD
0a2bPUSH20x1b20
0a2eJUMP
0a2fJUMPDEST
0a30PUSH10x40
0a32PUSH20x0a3c
0a35DUP6
0a36DUP8
0a37DUP10
0a38PUSH20x1afc
0a3bJUMP
0a3cJUMPDEST
0a3dADD
0a3eCALLDATALOAD
0a3fSWAP2
0a40PUSH20x18bd
0a43JUMP
0a44JUMPDEST
0a45PUSH20x0a51
0a48JUMPI
0a49JUMPDEST
0a4aPUSH10x01
0a4cADD
0a4dPUSH20x09f6
0a50JUMP
0a51JUMPDEST
0a52SWAP3
0a53PUSH20x0a88
0a56PUSH20x0a63
0a59PUSH20x04a9
0a5cDUP7
0a5dDUP6
0a5eDUP8
0a5fPUSH20x1afc
0a62JUMP
0a63JUMPDEST
0a64PUSH20x0a73
0a67PUSH10x20
0a69PUSH20x0a29
0a6cDUP9
0a6dDUP8
0a6eDUP10
0a6fPUSH20x1afc
0a72JUMP
0a73JUMPDEST
0a74PUSH10x40
0a76PUSH20x0a80
0a79DUP9
0a7aDUP8
0a7bDUP10
0a7cPUSH20x1afc
0a7fJUMP
0a80JUMPDEST
0a81ADD
0a82CALLDATALOAD
0a83SWAP2
0a84PUSH20x14f7
0a87JUMP
0a88JUMPDEST
0a89PUSH10x01
0a8bDUP2
0a8cADD
0a8dDUP1
0a8eSWAP2
0a8fGT
0a90PUSH20x0a99
0a93JUMPI
0a94SWAP3
0a95PUSH20x0a49
0a98JUMP
0a99JUMPDEST
0a9aPUSH40x4e487b71
0a9fPUSH10xe0
0aa1SHL
0aa2DUP6
0aa3MSTORE
0aa4PUSH10x11
0aa6PUSH10x04
0aa8MSTORE
0aa9PUSH10x24
0aabDUP6
0aacREVERT
0aadJUMPDEST
0aaePOP
0aafCALLVALUE
0ab0PUSH20x013c
0ab3JUMPI
0ab4DUP1
0ab5PUSH10x03
0ab7NOT
0ab8CALLDATASIZE
0ab9ADD
0abaSLT
0abbPUSH20x013c
0abeJUMPI
0abfPUSH10x40
0ac1MLOAD
0ac2PUSH320x000000000000000000000000c19d888a2f7ba65a8dcf8d03ad8f1af5cdc66430
0ae3PUSH10x01
0ae5PUSH10x01
0ae7PUSH10xa0
0ae9SHL
0aeaSUB
0aebAND
0aecDUP2
0aedMSTORE
0aeePUSH10x20
0af0SWAP1
0af1RETURN
0af2JUMPDEST
0af3POP
0af4CALLVALUE
0af5PUSH20x013c
0af8JUMPI
0af9PUSH10x20
0afbPUSH20x0b0f
0afePUSH20x0b06
0b01CALLDATASIZE
0b02PUSH20x10f0
0b05JUMP
0b06JUMPDEST
0b07SWAP3
0b08SWAP2
0b09SWAP1
0b0aSWAP2
0b0bPUSH20x1ab7
0b0eJUMP
0b0fJUMPDEST
0b10PUSH10x40
0b12MLOAD
0b13SWAP1
0b14DUP2
0b15MSTORE
0b16RETURN
0b17JUMPDEST
0b18POP
0b19CALLVALUE
0b1aPUSH20x013c
0b1dJUMPI
0b1ePUSH10x60
0b20CALLDATASIZE
0b21PUSH10x03
0b23NOT
0b24ADD
0b25SLT
0b26PUSH20x013c
0b29JUMPI
0b2aPUSH10x04
0b2cCALLDATALOAD
0b2dSWAP1
0b2ePUSH10x04
0b30DUP3
0b31LT
0b32ISZERO
0b33PUSH20x013c
0b36JUMPI
0b37PUSH10x20
0b39PUSH20x0b0f
0b3cDUP4
0b3dPUSH20x0b44
0b40PUSH20x104b
0b43JUMP
0b44JUMPDEST
0b45PUSH10x44
0b47CALLDATALOAD
0b48SWAP2
0b49PUSH20x1aa3
0b4cJUMP
0b4dJUMPDEST
0b4ePOP
0b4fCALLVALUE
0b50PUSH20x013c
0b53JUMPI
0b54PUSH10x40
0b56PUSH10x80
0b58SWAP2
0b59PUSH20x0b8b
0b5cPUSH20x0b64
0b5fCALLDATASIZE
0b60PUSH20x10f0
0b63JUMP
0b64JUMPDEST
0b65SWAP3
0b66DUP6
0b67PUSH10x60
0b69DUP9
0b6aSWAP5
0b6bSWAP4
0b6cSWAP5
0b6dMLOAD
0b6ePUSH20x0b76
0b71DUP2
0b72PUSH20x116e
0b75JUMP
0b76JUMPDEST
0b77DUP3
0b78DUP2
0b79MSTORE
0b7aDUP3
0b7bPUSH10x20
0b7dDUP3
0b7eADD
0b7fMSTORE
0b80DUP3
0b81DUP11
0b82DUP3
0b83ADD
0b84MSTORE
0b85ADD
0b86MSTORE
0b87PUSH20x1ab7
0b8aJUMP
0b8bJUMPDEST
0b8cDUP2
0b8dMSTORE
0b8ePUSH10x03
0b90PUSH10x20
0b92MSTORE
0b93KECCAK256
0b94PUSH10x40
0b96MLOAD
0b97PUSH20x0b9f
0b9aDUP2
0b9bPUSH20x116e
0b9eJUMP
0b9fJUMPDEST
0ba0DUP2
0ba1SLOAD
0ba2SWAP2
0ba3PUSH10x01
0ba5PUSH10x01
0ba7PUSH10x40
0ba9SHL
0baaSUB
0babDUP1
0bacDUP5
0badAND
0baeSWAP4
0bafDUP5
0bb0DUP5
0bb1MSTORE
0bb2DUP2
0bb3PUSH10x20
0bb5DUP6
0bb6ADD
0bb7SWAP2
0bb8PUSH10x40
0bbaSHR
0bbbAND
0bbcDUP2
0bbdMSTORE
0bbePUSH10x60
0bc0PUSH10x02
0bc2PUSH10x01
0bc4DUP6
0bc5ADD
0bc6SLOAD
0bc7SWAP5
0bc8PUSH10x40
0bcaDUP8
0bcbADD
0bccSWAP6
0bcdDUP7
0bceMSTORE
0bcfADD
0bd0SLOAD
0bd1SWAP5
0bd2ADD
0bd3SWAP4
0bd4DUP5
0bd5MSTORE
0bd6PUSH10x40
0bd8MLOAD
0bd9SWAP5
0bdaDUP6
0bdbMSTORE
0bdcMLOAD
0bddAND
0bdePUSH10x20
0be0DUP5
0be1ADD
0be2MSTORE
0be3MLOAD
0be4PUSH10x40
0be6DUP4
0be7ADD
0be8MSTORE
0be9MLOAD
0beaPUSH10x60
0becDUP3
0bedADD
0beeMSTORE
0befRETURN
0bf0JUMPDEST
0bf1POP
0bf2CALLVALUE
0bf3PUSH20x013c
0bf6JUMPI
0bf7DUP1
0bf8PUSH10x03
0bfaNOT
0bfbCALLDATASIZE
0bfcADD
0bfdSLT
0bfePUSH20x013c
0c01JUMPI
0c02PUSH10x20
0c04SWAP1
0c05SLOAD
0c06PUSH10x40
0c08MLOAD
0c09SWAP1
0c0aDUP2
0c0bMSTORE
0c0cRETURN
0c0dJUMPDEST
0c0ePOP
0c0fCALLVALUE
0c10PUSH20x013c
0c13JUMPI
0c14PUSH10x20
0c16PUSH20x0c27
0c19PUSH20x0c21
0c1cCALLDATASIZE
0c1dPUSH20x10b6
0c20JUMP
0c21JUMPDEST
0c22SWAP2
0c23PUSH20x18bd
0c26JUMP
0c27JUMPDEST
0c28PUSH10x40
0c2aMLOAD
0c2bSWAP1
0c2cISZERO
0c2dISZERO
0c2eDUP2
0c2fMSTORE
0c30RETURN
0c31JUMPDEST
0c32POP
0c33CALLVALUE
0c34PUSH20x0eab
0c37JUMPI
0c38PUSH10x80
0c3aCALLDATASIZE
0c3bPUSH10x03
0c3dNOT
0c3eADD
0c3fSLT
0c40PUSH20x0eab
0c43JUMPI
0c44PUSH10x04
0c46CALLDATALOAD
0c47PUSH10x24
0c49CALLDATALOAD
0c4aPUSH10x44
0c4cCALLDATALOAD
0c4dPUSH10x01
0c4fPUSH10x01
0c51PUSH10x40
0c53SHL
0c54SUB
0c55DUP2
0c56AND
0c57DUP1
0c58SWAP2
0c59SUB
0c5aPUSH20x0eab
0c5dJUMPI
0c5ePUSH10x64
0c60CALLDATALOAD
0c61PUSH10x01
0c63PUSH10x01
0c65PUSH10x40
0c67SHL
0c68SUB
0c69DUP2
0c6aGT
0c6bPUSH20x0eab
0c6eJUMPI
0c6fPUSH20x0c7c
0c72SWAP1
0c73CALLDATASIZE
0c74SWAP1
0c75PUSH10x04
0c77ADD
0c78PUSH20x113e
0c7bJUMP
0c7cJUMPDEST
0c7dPUSH10x40
0c7fMLOAD
0c80PUSH40x28305db1
0c85PUSH10xe2
0c87SHL
0c88DUP2
0c89MSTORE
0c8aPUSH320x000000000000000000000000c19d888a2f7ba65a8dcf8d03ad8f1af5cdc66430
0cabPUSH10x01
0cadPUSH10x01
0cafPUSH10xa0
0cb1SHL
0cb2SUB
0cb3AND
0cb4SWAP3
0cb5SWAP1
0cb6PUSH10x20
0cb8DUP2
0cb9PUSH10x04
0cbbDUP2
0cbcDUP8
0cbdGAS
0cbeSTATICCALL
0cbfSWAP1
0cc0DUP2
0cc1ISZERO
0cc2PUSH20x0e28
0cc5JUMPI
0cc6PUSH0
0cc7SWAP2
0cc8PUSH20x0f20
0ccbJUMPI
0cccJUMPDEST
0ccdPOP
0cceDUP1
0ccfISZERO
0cd0PUSH20x0eaf
0cd3JUMPI
0cd4JUMPDEST
0cd5PUSH20x0d50
0cd8JUMPI
0cd9JUMPDEST
0cdaPOP
0cdbPOP
0cdcPOP
0cddPOP
0cdeDUP2
0cdfISZERO
0ce0PUSH20x0d1d
0ce3JUMPI
0ce4DUP2
0ce5PUSH10x40
0ce7SWAP2
0ce8PUSH320x4391c666e2eec9f63c8148b09b0f6ef96b1b32168156ae728348c0e64c39e0fc
0d09SWAP4
0d0aDUP6
0d0bSSTORE
0d0cDUP1
0d0dPUSH10x01
0d0fSSTORE
0d10DUP3
0d11MLOAD
0d12SWAP2
0d13DUP3
0d14MSTORE
0d15PUSH10x20
0d17DUP3
0d18ADD
0d19MSTORE
0d1aLOG1
0d1bDUP1
0d1cRETURN
0d1dJUMPDEST
0d1ePUSH10x40
0d20MLOAD
0d21PUSH40x1a89d6e5
0d26PUSH10xe2
0d28SHL
0d29DUP2
0d2aMSTORE
0d2bPUSH10x20
0d2dPUSH10x04
0d2fDUP3
0d30ADD
0d31MSTORE
0d32PUSH10x0a
0d34PUSH10x24
0d36DUP3
0d37ADD
0d38MSTORE
0d39PUSH100x1c1a1a505cdcd95d1259
0d44PUSH10xb2
0d46SHL
0d47PUSH10x44
0d49DUP3
0d4aADD
0d4bMSTORE
0d4cPUSH10x64
0d4eSWAP1
0d4fREVERT
0d50JUMPDEST
0d51PUSH10x40
0d53MLOAD
0d54PUSH10x20
0d56DUP2
0d57ADD
0d58SWAP1
0d59DUP8
0d5aDUP3
0d5bMSTORE
0d5cDUP7
0d5dPUSH10x40
0d5fDUP3
0d60ADD
0d61MSTORE
0d62PUSH10x40
0d64DUP2
0d65MSTORE
0d66PUSH20x0d70
0d69PUSH10x60
0d6bDUP3
0d6cPUSH20x119d
0d6fJUMP
0d70JUMPDEST
0d71MLOAD
0d72SWAP1
0d73KECCAK256
0d74DUP4
0d75EXTCODESIZE
0d76ISZERO
0d77PUSH20x0eab
0d7aJUMPI
0d7bSWAP4
0d7cSWAP2
0d7dSWAP1
0d7eDUP2
0d7fPUSH10x40
0d81MLOAD
0d82SWAP6
0d83DUP7
0d84SWAP5
0d85PUSH40x22f3f447
0d8aPUSH10xe1
0d8cSHL
0d8dDUP7
0d8eMSTORE
0d8fPUSH10x84
0d91DUP7
0d92ADD
0d93SWAP2
0d94PUSH320x8e862443f1a13f07889f0179e58d71eba838b3af3539500b8c4d084c6bed7be8
0db5PUSH10x04
0db7DUP9
0db8ADD
0db9MSTORE
0dbaPUSH10x24
0dbcDUP8
0dbdADD
0dbeMSTORE
0dbfPUSH10x44
0dc1DUP7
0dc2ADD
0dc3MSTORE
0dc4PUSH10x80
0dc6PUSH10x64
0dc8DUP7
0dc9ADD
0dcaMSTORE
0dcbMSTORE
0dccPUSH10xa4
0dceDUP4
0dcfADD
0dd0PUSH10xa0
0dd2PUSH10x04
0dd4DUP5
0dd5PUSH10x05
0dd7SHL
0dd8DUP7
0dd9ADD
0ddaADD
0ddbADD
0ddcSWAP3
0dddDUP3
0ddePUSH0
0ddfSWAP1
0de0PUSH10x7e
0de2NOT
0de3DUP2
0de4CALLDATASIZE
0de5SUB
0de6ADD
0de7JUMPDEST
0de8DUP4
0de9DUP4
0deaLT
0debPUSH20x0e33
0deeJUMPI
0defPOP
0df0POP
0df1POP
0df2POP
0df3POP
0df4POP
0df5SWAP2
0df6DUP2
0df7PUSH0
0df8DUP2
0df9DUP6
0dfaDUP3
0dfbSWAP7
0dfcPOP
0dfdSUB
0dfeSWAP3
0dffGAS
0e00CALL
0e01DUP1
0e02ISZERO
0e03PUSH20x0e28
0e06JUMPI
0e07PUSH20x0e13
0e0aJUMPI
0e0bJUMPDEST
0e0cDUP1
0e0dDUP1
0e0eDUP1
0e0fPUSH20x0cd9
0e12JUMP
0e13JUMPDEST
0e14PUSH20x0e20
0e17SWAP2
0e18SWAP4
0e19POP
0e1aPUSH0
0e1bSWAP1
0e1cPUSH20x119d
0e1fJUMP
0e20JUMPDEST
0e21PUSH0
0e22SWAP2
0e23PUSH0
0e24PUSH20x0e0b
0e27JUMP
0e28JUMPDEST
0e29PUSH10x40
0e2bMLOAD
0e2cRETURNDATASIZE
0e2dPUSH0
0e2eDUP3
0e2fRETURNDATACOPY
0e30RETURNDATASIZE
0e31SWAP1
0e32REVERT
0e33JUMPDEST
0e34PUSH10xa3
0e36NOT
0e37DUP11
0e38DUP9
0e39SUB
0e3aADD
0e3bDUP6
0e3cMSTORE
0e3dSWAP5
0e3eSWAP7
0e3fPOP
0e40SWAP3
0e41SWAP5
0e42SWAP2
0e43SWAP4
0e44SWAP1
0e45SWAP3
0e46SWAP2
0e47DUP7
0e48CALLDATALOAD
0e49DUP3
0e4aDUP2
0e4bSLT
0e4cISZERO
0e4dPUSH20x0eab
0e50JUMPI
0e51DUP4
0e52ADD
0e53PUSH10x01
0e55PUSH10x01
0e57PUSH10xa0
0e59SHL
0e5aSUB
0e5bPUSH20x0e63
0e5eDUP3
0e5fPUSH20x1061
0e62JUMP
0e63JUMPDEST
0e64AND
0e65DUP3
0e66MSTORE
0e67PUSH10x20
0e69DUP2
0e6aADD
0e6bCALLDATALOAD
0e6cSWAP2
0e6dPUSH10xff
0e6fDUP4
0e70AND
0e71DUP1
0e72SWAP4
0e73SUB
0e74PUSH20x0eab
0e77JUMPI
0e78PUSH20x0e99
0e7bPUSH10x20
0e7dSWAP3
0e7eDUP3
0e7fPUSH10x01
0e81SWAP6
0e82DUP6
0e83DUP1
0e84SWAP6
0e85ADD
0e86MSTORE
0e87PUSH20x05be
0e8aPUSH20x05b3
0e8dPUSH20x05a2
0e90PUSH10x40
0e92DUP6
0e93ADD
0e94DUP6
0e95PUSH20x186c
0e98JUMP
0e99JUMPDEST
0e9aSWAP9
0e9bADD
0e9cSWAP7
0e9dADD
0e9eSWAP4
0e9fADD
0ea0SWAP1
0ea1SWAP2
0ea2DUP9
0ea3SWAP7
0ea4SWAP6
0ea5SWAP5
0ea6SWAP3
0ea7PUSH20x0de7
0eaaJUMP
0eabJUMPDEST
0eacPUSH0
0eadDUP1
0eaeREVERT
0eafJUMPDEST
0eb0POP
0eb1PUSH10x40
0eb3MLOAD
0eb4PUSH40xf5778b03
0eb9PUSH10xe0
0ebbSHL
0ebcDUP2
0ebdMSTORE
0ebePUSH10x20
0ec0DUP2
0ec1PUSH10x04
0ec3DUP2
0ec4DUP8
0ec5GAS
0ec6STATICCALL
0ec7SWAP1
0ec8DUP2
0ec9ISZERO
0ecaPUSH20x0e28
0ecdJUMPI
0ecePUSH0
0ecfSWAP2
0ed0PUSH20x0ee6
0ed3JUMPI
0ed4JUMPDEST
0ed5POP
0ed6PUSH10x01
0ed8PUSH10x01
0edaPUSH10xa0
0edcSHL
0eddSUB
0edeAND
0edfCALLER
0ee0EQ
0ee1ISZERO
0ee2PUSH20x0cd4
0ee5JUMP
0ee6JUMPDEST
0ee7SWAP1
0ee8POP
0ee9PUSH10x20
0eebDUP2
0eecRETURNDATASIZE
0eedPUSH10x20
0eefGT
0ef0PUSH20x0f18
0ef3JUMPI
0ef4JUMPDEST
0ef5DUP2
0ef6PUSH20x0f01
0ef9PUSH10x20
0efbSWAP4
0efcDUP4
0efdPUSH20x119d
0f00JUMP
0f01JUMPDEST
0f02DUP2
0f03ADD
0f04SUB
0f05SLT
0f06PUSH20x0eab
0f09JUMPI
0f0aPUSH20x0f12
0f0dSWAP1
0f0ePUSH20x11be
0f11JUMP
0f12JUMPDEST
0f13PUSH0
0f14PUSH20x0ed4
0f17JUMP
0f18JUMPDEST
0f19RETURNDATASIZE
0f1aSWAP2
0f1bPOP
0f1cPUSH20x0ef4
0f1fJUMP
0f20JUMPDEST
0f21SWAP1
0f22POP
0f23PUSH10x20
0f25DUP2
0f26RETURNDATASIZE
0f27PUSH10x20
0f29GT
0f2aPUSH20x0f52
0f2dJUMPI
0f2eJUMPDEST
0f2fDUP2
0f30PUSH20x0f3b
0f33PUSH10x20
0f35SWAP4
0f36DUP4
0f37PUSH20x119d
0f3aJUMP
0f3bJUMPDEST
0f3cDUP2
0f3dADD
0f3eSUB
0f3fSLT
0f40PUSH20x0eab
0f43JUMPI
0f44PUSH20x0f4c
0f47SWAP1
0f48PUSH20x11e6
0f4bJUMP
0f4cJUMPDEST
0f4dPUSH0
0f4ePUSH20x0ccc
0f51JUMP
0f52JUMPDEST
0f53RETURNDATASIZE
0f54SWAP2
0f55POP
0f56PUSH20x0f2e
0f59JUMP
0f5aJUMPDEST
0f5bCALLVALUE
0f5cPUSH20x0eab
0f5fJUMPI
0f60PUSH20x0f6b
0f63PUSH20x0b06
0f66CALLDATASIZE
0f67PUSH20x10f0
0f6aJUMP
0f6bJUMPDEST
0f6cPUSH0
0f6dMSTORE
0f6ePUSH10x03
0f70PUSH10x20
0f72MSTORE
0f73PUSH10x20
0f75PUSH10x40
0f77PUSH0
0f78KECCAK256
0f79SLOAD
0f7aPUSH10x01
0f7cPUSH10x01
0f7ePUSH10x40
0f80SHL
0f81SUB
0f82DUP2
0f83AND
0f84ISZERO
0f85ISZERO
0f86SWAP1
0f87DUP2
0f88PUSH20x0f97
0f8bJUMPI
0f8cJUMPDEST
0f8dPOP
0f8ePUSH10x40
0f90MLOAD
0f91SWAP1
0f92ISZERO
0f93ISZERO
0f94DUP2
0f95MSTORE
0f96RETURN
0f97JUMPDEST
0f98PUSH10x01
0f9aPUSH10x01
0f9cPUSH10x40
0f9eSHL
0f9fSUB
0fa0SWAP2
0fa1POP
0fa2PUSH10x40
0fa4SHR
0fa5AND
0fa6ISZERO
0fa7DUP3
0fa8PUSH20x0f8c
0fabJUMP
0facJUMPDEST
0fadCALLVALUE
0faePUSH20x0eab
0fb1JUMPI
0fb2PUSH20x0fc3
0fb5PUSH20x0fbd
0fb8CALLDATASIZE
0fb9PUSH20x10b6
0fbcJUMP
0fbdJUMPDEST
0fbeSWAP2
0fbfPUSH20x14f7
0fc2JUMP
0fc3JUMPDEST
0fc4STOP
0fc5JUMPDEST
0fc6CALLVALUE
0fc7PUSH20x0eab
0fcaJUMPI
0fcbPUSH10x80
0fcdCALLDATASIZE
0fcePUSH10x03
0fd0NOT
0fd1ADD
0fd2SLT
0fd3PUSH20x0eab
0fd6JUMPI
0fd7PUSH20x0fde
0fdaPUSH20x1035
0fddJUMP
0fdeJUMPDEST
0fdfPOP
0fe0PUSH20x0fe7
0fe3PUSH20x104b
0fe6JUMP
0fe7JUMPDEST
0fe8POP
0fe9PUSH10x64
0febCALLDATALOAD
0fecPUSH10x01
0feePUSH10x01
0ff0PUSH10x40
0ff2SHL
0ff3SUB
0ff4DUP2
0ff5GT
0ff6PUSH20x0eab
0ff9JUMPI
0ffaPUSH20x1007
0ffdSWAP1
0ffeCALLDATASIZE
0fffSWAP1
1000PUSH10x04
1002ADD
1003PUSH20x1075
1006JUMP
1007JUMPDEST
1008POP
1009POP
100aPUSH10x40
100cMLOAD
100dPUSH40x0a85bd01
1012PUSH10xe1
1014SHL
1015DUP2
1016MSTORE
1017PUSH10x20
1019SWAP1
101aRETURN
101bJUMPDEST
101cCALLVALUE
101dPUSH20x0eab
1020JUMPI
1021PUSH0
1022CALLDATASIZE
1023PUSH10x03
1025NOT
1026ADD
1027SLT
1028PUSH20x0eab
102bJUMPI
102cPUSH10x20
102eSWAP1
102fPUSH10x01
1031SLOAD
1032DUP2
1033MSTORE
1034RETURN
1035JUMPDEST
1036PUSH10x04
1038CALLDATALOAD
1039SWAP1
103aPUSH10x01
103cPUSH10x01
103ePUSH10xa0
1040SHL
1041SUB
1042DUP3
1043AND
1044DUP3
1045SUB
1046PUSH20x0eab
1049JUMPI
104aJUMP
104bJUMPDEST
104cPUSH10x24
104eCALLDATALOAD
104fSWAP1
1050PUSH10x01
1052PUSH10x01
1054PUSH10xa0
1056SHL
1057SUB
1058DUP3
1059AND
105aDUP3
105bSUB
105cPUSH20x0eab
105fJUMPI
1060JUMP
1061JUMPDEST
1062CALLDATALOAD
1063SWAP1
1064PUSH10x01
1066PUSH10x01
1068PUSH10xa0
106aSHL
106bSUB
106cDUP3
106dAND
106eDUP3
106fSUB
1070PUSH20x0eab
1073JUMPI
1074JUMP
1075JUMPDEST
1076SWAP2
1077DUP2
1078PUSH10x1f
107aDUP5
107bADD
107cSLT
107dISZERO
107ePUSH20x0eab
1081JUMPI
1082DUP3
1083CALLDATALOAD
1084SWAP2
1085PUSH10x01
1087PUSH10x01
1089PUSH10x40
108bSHL
108cSUB
108dDUP4
108eGT
108fPUSH20x0eab
1092JUMPI
1093PUSH10x20
1095DUP4
1096DUP2
1097DUP7
1098ADD
1099SWAP6
109aADD
109bADD
109cGT
109dPUSH20x0eab
10a0JUMPI
10a1JUMP
10a2JUMPDEST
10a3CALLDATALOAD
10a4SWAP1
10a5PUSH10x01
10a7PUSH10x01
10a9PUSH10x40
10abSHL
10acSUB
10adDUP3
10aeAND
10afDUP3
10b0SUB
10b1PUSH20x0eab
10b4JUMPI
10b5JUMP
10b6JUMPDEST
10b7PUSH10x60
10b9SWAP1
10baPUSH10x03
10bcNOT
10bdADD
10beSLT
10bfPUSH20x0eab
10c2JUMPI
10c3PUSH10x04
10c5CALLDATALOAD
10c6PUSH10x01
10c8PUSH10x01
10caPUSH10xa0
10ccSHL
10cdSUB
10ceDUP2
10cfAND
10d0DUP2
10d1SUB
10d2PUSH20x0eab
10d5JUMPI
10d6SWAP1
10d7PUSH10x24
10d9CALLDATALOAD
10daPUSH10x01
10dcPUSH10x01
10dePUSH10x40
10e0SHL
10e1SUB
10e2DUP2
10e3AND
10e4DUP2
10e5SUB
10e6PUSH20x0eab
10e9JUMPI
10eaSWAP1
10ebPUSH10x44
10edCALLDATALOAD
10eeSWAP1
10efJUMP
10f0JUMPDEST
10f1PUSH10x80
10f3SWAP1
10f4PUSH10x03
10f6NOT
10f7ADD
10f8SLT
10f9PUSH20x0eab
10fcJUMPI
10fdPUSH10x04
10ffCALLDATALOAD
1100PUSH10x01
1102PUSH10x01
1104PUSH10xa0
1106SHL
1107SUB
1108DUP2
1109AND
110aDUP2
110bSUB
110cPUSH20x0eab
110fJUMPI
1110SWAP1
1111PUSH10x24
1113CALLDATALOAD
1114PUSH10x01
1116PUSH10x01
1118PUSH10x40
111aSHL
111bSUB
111cDUP2
111dAND
111eDUP2
111fSUB
1120PUSH20x0eab
1123JUMPI
1124SWAP1
1125PUSH10x44
1127CALLDATALOAD
1128SWAP1
1129PUSH10x64
112bCALLDATALOAD
112cPUSH10x01
112ePUSH10x01
1130PUSH10x40
1132SHL
1133SUB
1134DUP2
1135AND
1136DUP2
1137SUB
1138PUSH20x0eab
113bJUMPI
113cSWAP1
113dJUMP
113eJUMPDEST
113fSWAP2
1140DUP2
1141PUSH10x1f
1143DUP5
1144ADD
1145SLT
1146ISZERO
1147PUSH20x0eab
114aJUMPI
114bDUP3
114cCALLDATALOAD
114dSWAP2
114ePUSH10x01
1150PUSH10x01
1152PUSH10x40
1154SHL
1155SUB
1156DUP4
1157GT
1158PUSH20x0eab
115bJUMPI
115cPUSH10x20
115eDUP1
115fDUP6
1160ADD
1161SWAP5
1162DUP5
1163PUSH10x05
1165SHL
1166ADD
1167ADD
1168GT
1169PUSH20x0eab
116cJUMPI
116dJUMP
116eJUMPDEST
116fPUSH10x80
1171DUP2
1172ADD
1173SWAP1
1174DUP2
1175LT
1176PUSH10x01
1178PUSH10x01
117aPUSH10x40
117cSHL
117dSUB
117eDUP3
117fGT
1180OR
1181PUSH20x1189
1184JUMPI
1185PUSH10x40
1187MSTORE
1188JUMP
1189JUMPDEST
118aPUSH40x4e487b71
118fPUSH10xe0
1191SHL
1192PUSH0
1193MSTORE
1194PUSH10x41
1196PUSH10x04
1198MSTORE
1199PUSH10x24
119bPUSH0
119cREVERT
119dJUMPDEST
119eSWAP1
119fPUSH10x1f
11a1DUP1
11a2NOT
11a3SWAP2
11a4ADD
11a5AND
11a6DUP2
11a7ADD
11a8SWAP1
11a9DUP2
11aaLT
11abPUSH10x01
11adPUSH10x01
11afPUSH10x40
11b1SHL
11b2SUB
11b3DUP3
11b4GT
11b5OR
11b6PUSH20x1189
11b9JUMPI
11baPUSH10x40
11bcMSTORE
11bdJUMP
11beJUMPDEST
11bfMLOAD
11c0SWAP1
11c1PUSH10x01
11c3PUSH10x01
11c5PUSH10xa0
11c7SHL
11c8SUB
11c9DUP3
11caAND
11cbDUP3
11ccSUB
11cdPUSH20x0eab
11d0JUMPI
11d1JUMP
11d2JUMPDEST
11d3MLOAD
11d4SWAP1
11d5PUSH10x01
11d7PUSH10x01
11d9PUSH10x40
11dbSHL
11dcSUB
11ddDUP3
11deAND
11dfDUP3
11e0SUB
11e1PUSH20x0eab
11e4JUMPI
11e5JUMP
11e6JUMPDEST
11e7MLOAD
11e8SWAP1
11e9DUP2
11eaISZERO
11ebISZERO
11ecDUP3
11edSUB
11eePUSH20x0eab
11f1JUMPI
11f2JUMP
11f3JUMPDEST
11f4PUSH10x01
11f6PUSH10x01
11f8PUSH10x40
11faSHL
11fbSUB
11fcDUP2
11fdGT
11fePUSH20x1189
1201JUMPI
1202PUSH10x05
1204SHL
1205PUSH10x20
1207ADD
1208SWAP1
1209JUMP
120aJUMPDEST
120bMLOAD
120cSWAP1
120dPUSH10xff
120fDUP3
1210AND
1211DUP3
1212SUB
1213PUSH20x0eab
1216JUMPI
1217JUMP
1218JUMPDEST
1219SWAP2
121aSWAP1
121bPUSH10x40
121dDUP4
121eDUP3
121fSUB
1220SLT
1221PUSH20x0eab
1224JUMPI
1225DUP3
1226MLOAD
1227PUSH10x01
1229PUSH10x01
122bPUSH10x40
122dSHL
122eSUB
122fDUP2
1230GT
1231PUSH20x0eab
1234JUMPI
1235DUP4
1236ADD
1237SWAP1
1238PUSH20x01a0
123bDUP3
123cDUP3
123dSUB
123eSLT
123fPUSH20x0eab
1242JUMPI
1243PUSH10x40
1245MLOAD
1246SWAP2
1247PUSH20x01a0
124aDUP4
124bADD
124cDUP4
124dDUP2
124eLT
124fPUSH10x01
1251PUSH10x01
1253PUSH10x40
1255SHL
1256SUB
1257DUP3
1258GT
1259OR
125aPUSH20x1189
125dJUMPI
125ePUSH10x40
1260MSTORE
1261PUSH20x1269
1264DUP2
1265PUSH20x11be
1268JUMP
1269JUMPDEST
126aDUP4
126bMSTORE
126cPUSH20x1277
126fPUSH10x20
1271DUP3
1272ADD
1273PUSH20x11d2
1276JUMP
1277JUMPDEST
1278PUSH10x20
127aDUP5
127bADD
127cMSTORE
127dPUSH10x40
127fDUP2
1280ADD
1281MLOAD
1282PUSH10x40
1284DUP5
1285ADD
1286MSTORE
1287PUSH10x60
1289DUP2
128aADD
128bMLOAD
128cPUSH10x60
128eDUP5
128fADD
1290MSTORE
1291PUSH20x129c
1294PUSH10x80
1296DUP3
1297ADD
1298PUSH20x11d2
129bJUMP
129cJUMPDEST
129dPUSH10x80
129fDUP5
12a0ADD
12a1MSTORE
12a2PUSH20x12ad
12a5PUSH10xa0
12a7DUP3
12a8ADD
12a9PUSH20x11e6
12acJUMP
12adJUMPDEST
12aePUSH10xa0
12b0DUP5
12b1ADD
12b2MSTORE
12b3PUSH10xc0
12b5DUP2
12b6ADD
12b7MLOAD
12b8PUSH10x01
12baPUSH10x01
12bcPUSH10x40
12beSHL
12bfSUB
12c0DUP2
12c1GT
12c2PUSH20x0eab
12c5JUMPI
12c6DUP2
12c7ADD
12c8DUP3
12c9PUSH10x1f
12cbDUP3
12ccADD
12cdSLT
12ceISZERO
12cfPUSH20x0eab
12d2JUMPI
12d3DUP1
12d4MLOAD
12d5SWAP1
12d6PUSH20x12de
12d9DUP3
12daPUSH20x11f3
12ddJUMP
12deJUMPDEST
12dfSWAP2
12e0PUSH20x12ec
12e3PUSH10x40
12e5MLOAD
12e6SWAP4
12e7DUP5
12e8PUSH20x119d
12ebJUMP
12ecJUMPDEST
12edDUP1
12eeDUP4
12efMSTORE
12f0PUSH10x20
12f2DUP1
12f3DUP5
12f4ADD
12f5SWAP2
12f6PUSH10x05
12f8SHL
12f9DUP4
12faADD
12fbADD
12fcSWAP2
12fdDUP6
12feDUP4
12ffGT
1300PUSH20x0eab
1303JUMPI
1304PUSH10x20
1306ADD
1307SWAP1
1308JUMPDEST
1309DUP3
130aDUP3
130bLT
130cPUSH20x1498
130fJUMPI
1310POP
1311POP
1312POP
1313PUSH10xc0
1315DUP5
1316ADD
1317MSTORE
1318PUSH10xe0
131aDUP2
131bADD
131cMLOAD
131dPUSH10xe0
131fDUP5
1320ADD
1321MSTORE
1322PUSH20x0100
1325DUP2
1326ADD
1327MLOAD
1328PUSH20x0100
132bDUP5
132cADD
132dMSTORE
132ePUSH20x133a
1331PUSH20x0120
1334DUP3
1335ADD
1336PUSH20x11d2
1339JUMP
133aJUMPDEST
133bPUSH20x0120
133eDUP5
133fADD
1340MSTORE
1341PUSH20x134d
1344PUSH20x0140
1347DUP3
1348ADD
1349PUSH20x11d2
134cJUMP
134dJUMPDEST
134ePUSH20x0140
1351DUP5
1352ADD
1353MSTORE
1354PUSH20x1360
1357PUSH20x0160
135aDUP3
135bADD
135cPUSH20x11d2
135fJUMP
1360JUMPDEST
1361PUSH20x0160
1364DUP5
1365ADD
1366MSTORE
1367PUSH20x0180
136aDUP2
136bADD
136cMLOAD
136dSWAP1
136ePUSH10x01
1370PUSH10x01
1372PUSH10x40
1374SHL
1375SUB
1376DUP3
1377GT
1378PUSH20x0eab
137bJUMPI
137cADD
137dDUP2
137ePUSH10x1f
1380DUP3
1381ADD
1382SLT
1383ISZERO
1384PUSH20x0eab
1387JUMPI
1388DUP1
1389MLOAD
138aSWAP1
138bPUSH20x1393
138eDUP3
138fPUSH20x11f3
1392JUMP
1393JUMPDEST
1394SWAP3
1395PUSH20x13a1
1398PUSH10x40
139aMLOAD
139bSWAP5
139cDUP6
139dPUSH20x119d
13a0JUMP
13a1JUMPDEST
13a2DUP3
13a3DUP5
13a4MSTORE
13a5PUSH10x20
13a7PUSH20x0120
13aaDUP2
13abDUP7
13acADD
13adSWAP5
13aeMUL
13afDUP4
13b0ADD
13b1ADD
13b2SWAP2
13b3DUP2
13b4DUP4
13b5GT
13b6PUSH20x0eab
13b9JUMPI
13baPUSH10x20
13bcADD
13bdSWAP3
13beJUMPDEST
13bfDUP3
13c0DUP5
13c1LT
13c2PUSH20x13df
13c5JUMPI
13c6POP
13c7POP
13c8POP
13c9POP
13caPUSH20x0180
13cdDUP3
13ceADD
13cfMSTORE
13d0SWAP2
13d1PUSH20x13dc
13d4SWAP1
13d5PUSH10x20
13d7ADD
13d8PUSH20x11e6
13dbJUMP
13dcJUMPDEST
13ddSWAP1
13deJUMP
13dfJUMPDEST
13e0PUSH20x0120
13e3DUP5
13e4DUP4
13e5SUB
13e6SLT
13e7PUSH20x0eab
13eaJUMPI
13ebPUSH10x40
13edMLOAD
13eeSWAP1
13efPUSH20x0120
13f2DUP3
13f3ADD
13f4DUP3
13f5DUP2
13f6LT
13f7PUSH10x01
13f9PUSH10x01
13fbPUSH10x40
13fdSHL
13feSUB
13ffDUP3
1400GT
1401OR
1402PUSH20x1189
1405JUMPI
1406PUSH10x40
1408MSTORE
1409DUP5
140aMLOAD
140bDUP3
140cMSTORE
140dPUSH10x20
140fDUP6
1410ADD
1411MLOAD
1412PUSH10x20
1414DUP4
1415ADD
1416MSTORE
1417PUSH10x40
1419DUP6
141aADD
141bMLOAD
141cSWAP1
141dPUSH20xffff
1420DUP3
1421AND
1422DUP3
1423SUB
1424PUSH20x0eab
1427JUMPI
1428DUP3
1429PUSH10x20
142bSWAP3
142cPUSH10x40
142ePUSH20x0120
1431SWAP6
1432ADD
1433MSTORE
1434PUSH10x60
1436DUP8
1437ADD
1438MLOAD
1439PUSH10x60
143bDUP3
143cADD
143dMSTORE
143ePUSH10x80
1440DUP8
1441ADD
1442MLOAD
1443PUSH10x80
1445DUP3
1446ADD
1447MSTORE
1448PUSH20x1453
144bPUSH10xa0
144dDUP9
144eADD
144fPUSH20x11d2
1452JUMP
1453JUMPDEST
1454PUSH10xa0
1456DUP3
1457ADD
1458MSTORE
1459PUSH20x1464
145cPUSH10xc0
145eDUP9
145fADD
1460PUSH20x120a
1463JUMP
1464JUMPDEST
1465PUSH10xc0
1467DUP3
1468ADD
1469MSTORE
146aPUSH20x1475
146dPUSH10xe0
146fDUP9
1470ADD
1471PUSH20x120a
1474JUMP
1475JUMPDEST
1476PUSH10xe0
1478DUP3
1479ADD
147aMSTORE
147bPUSH20x1487
147ePUSH20x0100
1481DUP9
1482ADD
1483PUSH20x120a
1486JUMP
1487JUMPDEST
1488PUSH20x0100
148bDUP3
148cADD
148dMSTORE
148eDUP2
148fMSTORE
1490ADD
1491SWAP4
1492ADD
1493SWAP3
1494PUSH20x13be
1497JUMP
1498JUMPDEST
1499DUP2
149aMLOAD
149bDUP2
149cMSTORE
149dPUSH10x20
149fSWAP2
14a0DUP3
14a1ADD
14a2SWAP2
14a3ADD
14a4PUSH20x1308
14a7JUMP
14a8JUMPDEST
14a9DUP1
14aaMLOAD
14abDUP3
14acLT
14adISZERO
14aePUSH20x14bc
14b1JUMPI
14b2PUSH10x20
14b4SWAP2
14b5PUSH10x05
14b7SHL
14b8ADD
14b9ADD
14baSWAP1
14bbJUMP
14bcJUMPDEST
14bdPUSH40x4e487b71
14c2PUSH10xe0
14c4SHL
14c5PUSH0
14c6MSTORE
14c7PUSH10x32
14c9PUSH10x04
14cbMSTORE
14ccPUSH10x24
14cePUSH0
14cfREVERT
14d0JUMPDEST
14d1DUP2
14d2DUP2
14d3MUL
14d4SWAP3
14d5SWAP2
14d6DUP2
14d7ISZERO
14d8SWAP2
14d9DUP5
14daDIV
14dbEQ
14dcOR
14ddISZERO
14dePUSH20x14e3
14e1JUMPI
14e2JUMP
14e3JUMPDEST
14e4PUSH40x4e487b71
14e9PUSH10xe0
14ebSHL
14ecPUSH0
14edMSTORE
14eePUSH10x11
14f0PUSH10x04
14f2MSTORE
14f3PUSH10x24
14f5PUSH0
14f6REVERT
14f7JUMPDEST
14f8SWAP1
14f9PUSH0
14faSLOAD
14fbISZERO
14fcPUSH20x185d
14ffJUMPI
1500PUSH10x40
1502MLOAD
1503PUSH40x76a90213
1508PUSH10xe0
150aSHL
150bDUP2
150cMSTORE
150dPUSH10x01
150fPUSH10x01
1511PUSH10xa0
1513SHL
1514SUB
1515DUP4
1516DUP2
1517AND
1518PUSH10x04
151aDUP4
151bADD
151cMSTORE
151dPUSH10x01
151fPUSH10x01
1521PUSH10x40
1523SHL
1524SUB
1525DUP4
1526AND
1527PUSH10x24
1529DUP4
152aADD
152bMSTORE
152cPUSH320x00000000000000000000000064d56ce8ace5840970ad2fe0b4ed78bf0951333a
154dAND
154eSWAP1
154fPUSH10x20
1551DUP2
1552PUSH10x44
1554DUP2
1555DUP6
1556GAS
1557STATICCALL
1558DUP1
1559ISZERO
155aPUSH20x0e28
155dJUMPI
155ePUSH0
155fSWAP1
1560PUSH20x182b
1563JUMPI
1564JUMPDEST
1565PUSH0
1566SWAP2
1567POP
1568PUSH10x24
156aPUSH10x40
156cMLOAD
156dDUP1
156eSWAP5
156fDUP2
1570SWAP4
1571PUSH40xbbba03b9
1576PUSH10xe0
1578SHL
1579DUP4
157aMSTORE
157bPUSH10x04
157dDUP4
157eADD
157fMSTORE
1580GAS
1581STATICCALL
1582DUP1
1583ISZERO
1584PUSH20x0e28
1587JUMPI
1588PUSH0
1589SWAP2
158aPUSH0
158bSWAP2
158cPUSH20x1807
158fJUMPI
1590JUMPDEST
1591POP
1592ISZERO
1593PUSH20x17db
1596JUMPI
1597PUSH20x0180
159aADD
159bDUP1
159cMLOAD
159dMLOAD
159eDUP5
159fLT
15a0ISZERO
15a1PUSH20x17b0
15a4JUMPI
15a5DUP4
15a6PUSH20x15af
15a9SWAP2
15aaMLOAD
15abPUSH20x14a8
15aeJUMP
15afJUMPDEST
15b0MLOAD
15b1SWAP1
15b2PUSH10xff
15b4PUSH10xe0
15b6DUP4
15b7ADD
15b8MLOAD
15b9AND
15baPUSH10x01
15bcDUP2
15bdSUB
15bePUSH20x179e
15c1JUMPI
15c2POP
15c3PUSH10xa0
15c5DUP3
15c6ADD
15c7PUSH20x15dc
15caPUSH10x01
15ccPUSH10x01
15cePUSH10x40
15d0SHL
15d1SUB
15d2DUP3
15d3MLOAD
15d4AND
15d5DUP7
15d6DUP5
15d7DUP8
15d8PUSH20x1ab7
15dbJUMP
15dcJUMPDEST
15ddSWAP1
15deDUP2
15dfPUSH0
15e0MSTORE
15e1PUSH10x03
15e3PUSH10x20
15e5MSTORE
15e6PUSH10x01
15e8PUSH10x01
15eaPUSH10x40
15ecSHL
15edSUB
15eePUSH10x40
15f0PUSH0
15f1KECCAK256
15f2SLOAD
15f3AND
15f4ISZERO
15f5ISZERO
15f6DUP1
15f7PUSH20x177e
15faJUMPI
15fbJUMPDEST
15fcPUSH20x176b
15ffJUMPI
1600PUSH20x1608
1603DUP5
1604PUSH20x1c80
1607JUMP
1608JUMPDEST
1609SWAP5
160aSWAP2
160bSWAP6
160cSWAP1
160dSWAP6
160eISZERO
160fPUSH20x173d
1612JUMPI
1613PUSH20x161c
1616DUP6
1617DUP3
1618PUSH20x1b72
161bJUMP
161cJUMPDEST
161dSWAP1
161eISZERO
161fPUSH20x172e
1622JUMPI
1623PUSH20x1642
1626SWAP1
1627PUSH20x163d
162aPUSH10x60
162cDUP5
162dADD
162eMLOAD
162fPUSH20xffff
1632PUSH10x40
1634DUP7
1635ADD
1636MLOAD
1637AND
1638SWAP1
1639PUSH20x14d0
163cJUMP
163dJUMPDEST
163ePUSH20x14d0
1641JUMP
1642JUMPDEST
1643SWAP1
1644DUP7
1645PUSH10x64
1647MUL
1648PUSH10x64
164aDUP2
164bDIV
164cDUP9
164dSUB
164ePUSH20x14e3
1651JUMPI
1652PUSH10x80
1654PUSH20x165f
1657SWAP3
1658ADD
1659MLOAD
165aSWAP1
165bPUSH20x14d0
165eJUMP
165fJUMPDEST
1660GT
1661PUSH20x172e
1664JUMPI
1665PUSH320xeaacb149d0730aaa7f48dd4ebaa6c481fee2c2cb90fff9b2cdd12972396a6d2a
1686SWAP4
1687PUSH10x01
1689PUSH10x01
168bPUSH10x40
168dSHL
168eSUB
168fDUP1
1690SWAP4
1691PUSH10x60
1693SWAP6
1694PUSH10x02
1696PUSH10x40
1698MLOAD
1699SWAP2
169aPUSH20x16a2
169dDUP4
169ePUSH20x116e
16a1JUMP
16a2JUMPDEST
16a3DUP5
16a4NUMBER
16a5AND
16a6DUP4
16a7MSTORE
16a8DUP11
16a9PUSH20x1703
16acDUP7
16adPUSH10x20
16afDUP7
16b0ADD
16b1PUSH0
16b2DUP2
16b3MSTORE
16b4PUSH10x40
16b6DUP8
16b7ADD
16b8SWAP4
16b9DUP5
16baMSTORE
16bbDUP13
16bcDUP8
16bdADD
16beSWAP5
16bfDUP11
16c0DUP7
16c1MSTORE
16c2PUSH0
16c3MSTORE
16c4PUSH10x03
16c6PUSH10x20
16c8MSTORE
16c9DUP2
16caPUSH10x40
16ccPUSH0
16cdKECCAK256
16ceSWAP8
16cfMLOAD
16d0AND
16d1DUP3
16d2NOT
16d3DUP9
16d4SLOAD
16d5AND
16d6OR
16d7DUP8
16d8SSTORE
16d9MLOAD
16daAND
16dbDUP6
16dcSWAP1
16ddPUSH80xffffffffffffffff
16e6PUSH10x40
16e8SHL
16e9DUP3
16eaSLOAD
16ebSWAP2
16ecPUSH10x40
16eeSHL
16efAND
16f0SWAP1
16f1PUSH80xffffffffffffffff
16faPUSH10x40
16fcSHL
16fdNOT
16feAND
16ffOR
1700SWAP1
1701SSTORE
1702JUMP
1703JUMPDEST
1704MLOAD
1705PUSH10x01
1707DUP5
1708ADD
1709SSTORE
170aMLOAD
170bSWAP2
170cADD
170dSSTORE
170eMLOAD
170fAND
1710SWAP6
1711PUSH10x40
1713MLOAD
1714SWAP7
1715DUP8
1716MSTORE
1717PUSH10x20
1719DUP8
171aADD
171bMSTORE
171cPUSH10x40
171eDUP7
171fADD
1720MSTORE
1721AND
1722SWAP4
1723PUSH10x01
1725DUP1
1726PUSH10xa0
1728SHL
1729SUB
172aAND
172bSWAP3
172cLOG4
172dJUMP
172eJUMPDEST
172fPUSH40x03e49141
1734PUSH10xe4
1736SHL
1737PUSH0
1738MSTORE
1739PUSH10x04
173bPUSH0
173cREVERT
173dJUMPDEST
173ePUSH10x40
1740MLOAD
1741PUSH40x1a89d6e5
1746PUSH10xe2
1748SHL
1749DUP2
174aMSTORE
174bPUSH10x20
174dPUSH10x04
174fDUP3
1750ADD
1751MSTORE
1752PUSH10x05
1754PUSH10x24
1756DUP3
1757ADD
1758MSTORE
1759PUSH50x7072696365
175fPUSH10xd8
1761SHL
1762PUSH10x44
1764DUP3
1765ADD
1766MSTORE
1767PUSH10x64
1769SWAP1
176aREVERT
176bJUMPDEST
176cPOP
176dPUSH40x05cb5cd5
1772PUSH10xe1
1774SHL
1775PUSH0
1776MSTORE
1777PUSH10x04
1779MSTORE
177aPUSH10x24
177cPUSH0
177dREVERT
177eJUMPDEST
177fPOP
1780DUP2
1781PUSH0
1782MSTORE
1783PUSH10x03
1785PUSH10x20
1787MSTORE
1788PUSH10x01
178aPUSH10x01
178cPUSH10x40
178eSHL
178fSUB
1790PUSH10x40
1792PUSH0
1793KECCAK256
1794SLOAD
1795PUSH10x40
1797SHR
1798AND
1799ISZERO
179aPUSH20x15fb
179dJUMP
179eJUMPDEST
179fPUSH40x0b1bf397
17a4PUSH10xe0
17a6SHL
17a7PUSH0
17a8MSTORE
17a9PUSH10x04
17abMSTORE
17acPUSH10x24
17aePUSH0
17afREVERT
17b0JUMPDEST
17b1POP
17b2PUSH10x01
17b4PUSH10x01
17b6PUSH10x40
17b8SHL
17b9SUB
17baSWAP2
17bbPUSH40xe0671a19
17c0PUSH10xe0
17c2SHL
17c3PUSH0
17c4MSTORE
17c5PUSH10x01
17c7DUP1
17c8PUSH10xa0
17caSHL
17cbSUB
17ccAND
17cdPUSH10x04
17cfMSTORE
17d0AND
17d1PUSH10x24
17d3MSTORE
17d4PUSH10x44
17d6MSTORE
17d7PUSH10x64
17d9PUSH0
17daREVERT
17dbJUMPDEST
17dcPOP
17ddPUSH40x3bcd856f
17e2PUSH10xe2
17e4SHL
17e5PUSH0
17e6SWAP1