Final Testnetexplorer K_J · Final Testnet · 48359
en

Contract

0x4c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d

Address
0x4c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d
Kind
verified contract FinalStateTrees
Balance
0 vETH
Nonce
1
Code
29,615 bytes codehash 0xa752594483cba8212264e77a1a9b6488340eabc0ef54f6650fc0229d1b2c1640

account tree

Tree
1 · accounts
Present
no leaf
Key
0xf2af25bceec7d84fddd553d2c4257ac017c3b07362dbe8ffc9db43123f751e56
Live root
0x0a08b5139d5865ad7350389e6fee558ae5579447b1094084d4afdfd3e993e984
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
FinalStateTrees exact match · immutables masked
Compiler
v0.8.33+commit.64118f21
Optimizer
enabled · 200 runs
EVM version
prague
Verified
2026-09-10T07:09:55.553Z
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 execution-class quorum
    ///         decisions.
    /// @dev Distinct from the access key, and carried by SERVICE certificates only — a user's wallet never
    ///      seals. Optional in the format, so a certificate without it parses unchanged.
    /// @dev Outside the folded key commitment: a seal is operational, rotated by issuing a new live
    ///      certificate, and it must not move a wallet address it plays no part in deriving.
    uint16 internal constant PURPOSE_ACTIVE_SEAL = 0x0016;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

contracts/finalchain/FinalChainPrecompiles.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

contracts/finalchain/FinalChainTime.sol

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

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

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

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

contracts/finalchain/FinalIdentityRegistry.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        accountOfCertificate[live.certHash] = account;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

contracts/finalchain/FinalPlaneSweep.sol

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

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

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

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

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

contracts/finalchain/FinalPqQuorum.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            valid++;
        }

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

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

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

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

contracts/finalchain/FinalStateTrees.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

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

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

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

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

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

        _bump(TREE_ACCOUNTS, leaves.length);
    }

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

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

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

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        _leaf[treeId][idx] = leaf;

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

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

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

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

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

contracts/utils/FinalSweep.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

abi

[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "registry_",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "ACTION_CONFIGURE_TREE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SEED_COUNTERS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SET_CHAIN_SOURCE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SET_CONFIG",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SET_ENDPOINT_SOURCE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SET_SLOT_KEY_SOURCE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SET_TREE_WRITER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SET_TYPED_WRITER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_BITS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_CAPACITY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_CONFIG",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_COUNT",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_DEPTH",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_ENDPOINTS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_MAIN",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_OWNER_INDEX",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_SLOT_KEYS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "CAPACITY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DEPTH",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_ACCOUNT_STATE_LEAF",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_CONFIG_LEAF",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_OWNER_INDEX_LEAF",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "FOREST_BITS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROUND_DEPTH",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "SLOT_KEY_RING",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_ACCOUNTS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_ALLOWLIST",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_COMPLIANCE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_COUNT",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_IDENTITY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_INTENTS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_ORACLE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_PHI",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_SETTLEMENT",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_VASSET",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountKeyFor",
    "inputs": [
      {
        "name": "wallet",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "accountOn",
    "inputs": [
      {
        "name": "leaf",
        "type": "tuple",
        "internalType": "struct FinalStateTrees.AccountStateLeaf",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "liveAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "pqEnabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "frozen",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "deployedChains",
            "type": "tuple[]",
            "internalType": "struct FinalStateTrees.ChainAccount[]",
            "components": [
              {
                "name": "chainRef",
                "type": "bytes32",
                "internalType": "bytes32"
              },
              {
                "name": "account",
                "type": "bytes32",
                "internalType": "bytes32"
              }
            ]
          },
          {
            "name": "dormantChains",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "version",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "accountStateLeafHash",
    "inputs": [
      {
        "name": "leaf",
        "type": "tuple",
        "internalType": "struct FinalStateTrees.AccountStateLeaf",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "liveAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "pqEnabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "frozen",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "deployedChains",
            "type": "tuple[]",
            "internalType": "struct FinalStateTrees.ChainAccount[]",
            "components": [
              {
                "name": "chainRef",
                "type": "bytes32",
                "internalType": "bytes32"
              },
              {
                "name": "account",
                "type": "bytes32",
                "internalType": "bytes32"
              }
            ]
          },
          {
            "name": "dormantChains",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "version",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "branchProofFor",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "branchRoot",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "branch",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "branchSlotsUsed",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "branch",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "chainSource",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "configKey",
    "inputs": [
      {
        "name": "name",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "sub",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "configLeafHash",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "value",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "configValue",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "value",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "present",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "configureTree",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "role",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "k",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "emptyRoot",
    "inputs": [
      {
        "name": "level",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "endpointKeyFor",
    "inputs": [
      {
        "name": "endpointId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "endpointSource",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "identityKeyFor",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "keyAt",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "slot",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "latestRound",
    "inputs": [],
    "outputs": [
      {
        "name": "which",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "roots",
        "type": "bytes32[10]",
        "internalType": "bytes32[10]"
      },
      {
        "name": "blockNumber",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "timestamp",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "leafOf",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "present",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "liveRoot",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "nonce",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "onERC1155BatchReceived",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC1155Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC721Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "ownerIndexKeyFor",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "ownerIndexLeafHash",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "wallets",
        "type": "address[]",
        "internalType": "address[]"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "proofFor",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "publishRound",
    "inputs": [],
    "outputs": [
      {
        "name": "published",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "quorumHealth",
    "inputs": [],
    "outputs": [
      {
        "name": "live",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "required",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "ok",
        "type": "bool[]",
        "internalType": "bool[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registry",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "rootAt",
    "inputs": [
      {
        "name": "which",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "rootsAt",
    "inputs": [
      {
        "name": "which",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32[10]",
        "internalType": "bytes32[10]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "round",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "roundProofFor",
    "inputs": [
      {
        "name": "which",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "path",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "roundRootAt",
    "inputs": [
      {
        "name": "which",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "seedCounters",
    "inputs": [
      {
        "name": "versions",
        "type": "uint64[]",
        "internalType": "uint64[]"
      },
      {
        "name": "round_",
        "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": "setAccountStates",
    "inputs": [
      {
        "name": "leaves",
        "type": "tuple[]",
        "internalType": "struct FinalStateTrees.AccountStateLeaf[]",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "liveAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "pqEnabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "frozen",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "deployedChains",
            "type": "tuple[]",
            "internalType": "struct FinalStateTrees.ChainAccount[]",
            "components": [
              {
                "name": "chainRef",
                "type": "bytes32",
                "internalType": "bytes32"
              },
              {
                "name": "account",
                "type": "bytes32",
                "internalType": "bytes32"
              }
            ]
          },
          {
            "name": "dormantChains",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "version",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      },
      {
        "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": "setAccountStatesAsWriter",
    "inputs": [
      {
        "name": "leaves",
        "type": "tuple[]",
        "internalType": "struct FinalStateTrees.AccountStateLeaf[]",
        "components": [
          {
            "name": "wallet",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "liveAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryAccess",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryTransaction",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "liveKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryKem",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "pqEnabled",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "frozen",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "deployedChains",
            "type": "tuple[]",
            "internalType": "struct FinalStateTrees.ChainAccount[]",
            "components": [
              {
                "name": "chainRef",
                "type": "bytes32",
                "internalType": "bytes32"
              },
              {
                "name": "account",
                "type": "bytes32",
                "internalType": "bytes32"
              }
            ]
          },
          {
            "name": "dormantChains",
            "type": "uint32",
            "internalType": "uint32"
          },
          {
            "name": "version",
            "type": "uint64",
            "internalType": "uint64"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "setChainSource",
    "inputs": [
      {
        "name": "source",
        "type": "address",
        "internalType": "address"
      },
      {
        "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": "setConfig",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "keys",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "values",
        "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": "setEndpointSource",
    "inputs": [
      {
        "name": "source",
        "type": "address",
        "internalType": "address"
      },
      {
        "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": "setLeaves",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "branch",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "keys",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "leaves",
        "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": "setLeavesAsWriter",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "branch",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "keys",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "leaves",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "setSlotKeySource",
    "inputs": [
      {
        "name": "source",
        "type": "address",
        "internalType": "address"
      },
      {
        "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": "setTreeWriter",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "writer",
        "type": "address",
        "internalType": "address"
      },
      {
        "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": "setTypedWriter",
    "inputs": [
      {
        "name": "writer",
        "type": "address",
        "internalType": "address"
      },
      {
        "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": "slotKeyFor",
    "inputs": [
      {
        "name": "member",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "slotIndex",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "slotKeySource",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "slotOf",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "slotsUsed",
    "inputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "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": "syncEndpointLeaves",
    "inputs": [
      {
        "name": "endpointIds",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "syncIdentities",
    "inputs": [
      {
        "name": "accounts",
        "type": "address[]",
        "internalType": "address[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "syncIdentityLeaves",
    "inputs": [
      {
        "name": "accounts",
        "type": "address[]",
        "internalType": "address[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "syncSlotKeyLeaves",
    "inputs": [
      {
        "name": "member",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "slotIndexes",
        "type": "uint64[]",
        "internalType": "uint64[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "threshold",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "treeVersion",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "treeWriter",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "typedWriter",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "writeTyped",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "keys",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "hashes",
        "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": "writeTypedInBranch",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "branch",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "keys",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "hashes",
        "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": "writerRole",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "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": "ChainSourceSet",
    "inputs": [
      {
        "name": "source",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "ConfigSet",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "indexed": true,
        "internalType": "uint8"
      },
      {
        "name": "key",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "value",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "CountersSeeded",
    "inputs": [
      {
        "name": "round",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "versions",
        "type": "uint64[]",
        "indexed": false,
        "internalType": "uint64[]"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "EndpointSourceSet",
    "inputs": [
      {
        "name": "source",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "LeavesSet",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "indexed": true,
        "internalType": "uint8"
      },
      {
        "name": "count",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "newRoot",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      },
      {
        "name": "treeVersion",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "RoundPublished",
    "inputs": [
      {
        "name": "round",
        "type": "uint64",
        "indexed": true,
        "internalType": "uint64"
      },
      {
        "name": "blockNumber",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "timestamp",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "SlotKeySourceSet",
    "inputs": [
      {
        "name": "source",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "TreeConfigured",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "indexed": true,
        "internalType": "uint8"
      },
      {
        "name": "writerRole",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "threshold",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "TreeWriterSet",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "indexed": true,
        "internalType": "uint8"
      },
      {
        "name": "writer",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "TypedWriterSet",
    "inputs": [
      {
        "name": "writer",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "error",
    "name": "AnchorAhead",
    "inputs": [
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "blockNumber",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "AnchorStale",
    "inputs": [
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "blockNumber",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSeal",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSignature",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "algorithm",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "BranchFull",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "branch",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "BranchMismatch",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "have",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "want",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "ConfigBranchReserved",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "EndpointSourceUnset",
    "inputs": []
  },
  {
    "type": "error",
    "name": "InvalidChainAccount",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "account",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "LengthMismatch",
    "inputs": [
      {
        "name": "keys",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "leaves",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "NoRounds",
    "inputs": []
  },
  {
    "type": "error",
    "name": "NotAuthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotFresh",
    "inputs": []
  },
  {
    "type": "error",
    "name": "NothingToPublish",
    "inputs": []
  },
  {
    "type": "error",
    "name": "SignerLacksRole",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "roleMask",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SignersNotAscending",
    "inputs": [
      {
        "name": "previous",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "next",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SlotKeySourceUnset",
    "inputs": []
  },
  {
    "type": "error",
    "name": "SweepAboveSurplus",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "requested",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "surplus",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepDestinationNotAllowed",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepTransferFailed",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepUnauthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepZeroAmount",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ThresholdIsZero",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ThresholdNotMet",
    "inputs": [
      {
        "name": "valid",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "ThresholdUnreachable",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "live",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "TreeNotConfigured",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "TypedTreeOnly",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "UnknownBranch",
    "inputs": [
      {
        "name": "branch",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "UnknownKey",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "UnknownTree",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "VersionCountMismatch",
    "inputs": [
      {
        "name": "given",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "WriterOnlyTree",
    "inputs": [
      {
        "name": "treeId",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "WrongAlgorithm",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "got",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "required",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  }
]

read contract

bytecode · 29,615 bytes

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

disassembly (first 4,000 ops)

pcopoperand
0000PUSH10x80
0002PUSH10x40
0004MSTORE
0005PUSH10x04
0007CALLDATASIZE
0008LT
0009ISZERO
000aPUSH20x0011
000dJUMPI
000ePUSH0
000fDUP1
0010REVERT
0011JUMPDEST
0012PUSH0
0013CALLDATALOAD
0014PUSH10xe0
0016SHR
0017DUP1
0018PUSH40x01e8a3a7
001dEQ
001ePUSH20x42d5
0021JUMPI
0022DUP1
0023PUSH40x02ffb43a
0028EQ
0029PUSH20x4258
002cJUMPI
002dDUP1
002ePUSH40x06ff9a01
0033EQ
0034PUSH20x3f29
0037JUMPI
0038DUP1
0039PUSH40x07465c21
003eEQ
003fPUSH20x3ee6
0042JUMPI
0043DUP1
0044PUSH40x08699369
0049EQ
004aPUSH20x3eab
004dJUMPI
004eDUP1
004fPUSH40x0acd12b5
0054EQ
0055PUSH20x3e2e
0058JUMPI
0059DUP1
005aPUSH40x0b543248
005fEQ
0060PUSH20x3df4
0063JUMPI
0064DUP1
0065PUSH40x0dc85691
006aEQ
006bPUSH20x3dd9
006eJUMPI
006fDUP1
0070PUSH40x10229c9b
0075EQ
0076PUSH20x3d9f
0079JUMPI
007aDUP1
007bPUSH40x11cb42a6
0080EQ
0081PUSH20x3d65
0084JUMPI
0085DUP1
0086PUSH40x146ca531
008bEQ
008cPUSH20x3d3f
008fJUMPI
0090DUP1
0091PUSH40x150b7a02
0096EQ
0097PUSH20x3ce9
009aJUMPI
009bDUP1
009cPUSH40x158f59b6
00a1EQ
00a2PUSH20x3cae
00a5JUMPI
00a6DUP1
00a7PUSH40x159735ac
00acEQ
00adPUSH20x3c74
00b0JUMPI
00b1DUP1
00b2PUSH40x177b99da
00b7EQ
00b8PUSH20x3c4d
00bbJUMPI
00bcDUP1
00bdPUSH40x1bc6dca7
00c2EQ
00c3PUSH20x3c1c
00c6JUMPI
00c7DUP1
00c8PUSH40x1c37f8be
00cdEQ
00cePUSH20x3871
00d1JUMPI
00d2DUP1
00d3PUSH40x1cb78cf6
00d8EQ
00d9PUSH20x380e
00dcJUMPI
00ddDUP1
00dePUSH40x1ed23e25
00e3EQ
00e4PUSH20x37d0
00e7JUMPI
00e8DUP1
00e9PUSH40x1f1ee274
00eeEQ
00efPUSH20x35e5
00f2JUMPI
00f3DUP1
00f4PUSH40x1ff31495
00f9EQ
00faPUSH20x35ca
00fdJUMPI
00feDUP1
00ffPUSH40x21985270
0104EQ
0105PUSH20x3590
0108JUMPI
0109DUP1
010aPUSH40x2211d9eb
010fEQ
0110PUSH20x3513
0113JUMPI
0114DUP1
0115PUSH40x227cab87
011aEQ
011bPUSH20x3438
011eJUMPI
011fDUP1
0120PUSH40x227d5d7a
0125EQ
0126PUSH20x30b8
0129JUMPI
012aDUP1
012bPUSH40x24a47334
0130EQ
0131PUSH20x3090
0134JUMPI
0135DUP1
0136PUSH40x25030f27
013bEQ
013cPUSH20x3068
013fJUMPI
0140DUP1
0141PUSH40x2737f33e
0146EQ
0147PUSH20x2f48
014aJUMPI
014bDUP1
014cPUSH40x36179208
0151EQ
0152PUSH20x2f0e
0155JUMPI
0156DUP1
0157PUSH40x38902300
015cEQ
015dPUSH20x2ef0
0160JUMPI
0161DUP1
0162PUSH40x3fe9eec5
0167EQ
0168PUSH20x2e73
016bJUMPI
016cDUP1
016dPUSH40x427fbf5a
0172EQ
0173PUSH20x2e55
0176JUMPI
0177DUP1
0178PUSH40x45763154
017dEQ
017ePUSH20x2e37
0181JUMPI
0182DUP1
0183PUSH40x4d686726
0188EQ
0189PUSH20x2c44
018cJUMPI
018dDUP1
018ePUSH40x4e4d143c
0193EQ
0194PUSH20x2c06
0197JUMPI
0198DUP1
0199PUSH40x4e74a404
019eEQ
019fPUSH20x2bbe
01a2JUMPI
01a3DUP1
01a4PUSH40x563a6c07
01a9EQ
01aaPUSH20x2ba3
01adJUMPI
01aeDUP1
01afPUSH40x57c45d9c
01b4EQ
01b5PUSH20x2b7b
01b8JUMPI
01b9DUP1
01baPUSH40x5807879d
01bfEQ
01c0PUSH20x2b41
01c3JUMPI
01c4DUP1
01c5PUSH40x5ea96231
01caEQ
01cbPUSH20x0ed1
01ceJUMPI
01cfDUP1
01d0PUSH40x60a18008
01d5EQ
01d6PUSH20x2b0d
01d9JUMPI
01daDUP1
01dbPUSH40x614a5c34
01e0EQ
01e1PUSH20x2ad6
01e4JUMPI
01e5DUP1
01e6PUSH40x668a0f02
01ebEQ
01ecPUSH20x2a53
01efJUMPI
01f0DUP1
01f1PUSH40x6a404030
01f6EQ
01f7PUSH20x0444
01faJUMPI
01fbDUP1
01fcPUSH40x6b6a2682
0201EQ
0202PUSH20x13fc
0205JUMPI
0206DUP1
0207PUSH40x6d2e5ca0
020cEQ
020dPUSH20x2a19
0210JUMPI
0211DUP1
0212PUSH40x7203b807
0217EQ
0218PUSH20x240b
021bJUMPI
021cDUP1
021dPUSH40x76e058a8
0222EQ
0223PUSH20x0830
0226JUMPI
0227DUP1
0228PUSH40x79fee94a
022dEQ
022ePUSH20x13fc
0231JUMPI
0232DUP1
0233PUSH40x7a9730d7
0238EQ
0239PUSH20x23d1
023cJUMPI
023dDUP1
023ePUSH40x7b103999
0243EQ
0244PUSH20x238d
0247JUMPI
0248DUP1
0249PUSH40x7d211cba
024eEQ
024fPUSH20x2208
0252JUMPI
0253DUP1
0254PUSH40x80d78ea9
0259EQ
025aPUSH20x1f8c
025dJUMPI
025eDUP1
025fPUSH40x825c45cd
0264EQ
0265PUSH20x1f5a
0268JUMPI
0269DUP1
026aPUSH40x82edfbd9
026fEQ
0270PUSH20x1f37
0273JUMPI
0274DUP1
0275PUSH40x87a2d8ae
027aEQ
027bPUSH20x1efd
027eJUMPI
027fDUP1
0280PUSH40x87bba45c
0285EQ
0286PUSH20x1ea6
0289JUMPI
028aDUP1
028bPUSH40x87c28345
0290EQ
0291PUSH20x1e8b
0294JUMPI
0295DUP1
0296PUSH40x89106905
029bEQ
029cPUSH20x1de5
029fJUMPI
02a0DUP1
02a1PUSH40x8c3712dc
02a6EQ
02a7PUSH20x13fc
02aaJUMPI
02abDUP1
02acPUSH40x8ca9a268
02b1EQ
02b2PUSH20x1d8c
02b5JUMPI
02b6DUP1
02b7PUSH40x8e808c09
02bcEQ
02bdPUSH20x1d61
02c0JUMPI
02c1DUP1
02c2PUSH40x915ed0bf
02c7EQ
02c8PUSH20x1d39
02cbJUMPI
02ccDUP1
02cdPUSH40x96f51f3a
02d2EQ
02d3PUSH20x1a4b
02d6JUMPI
02d7DUP1
02d8PUSH40x98366e35
02ddEQ
02dePUSH20x1a30
02e1JUMPI
02e2DUP1
02e3PUSH40x9ca3c9c4
02e8EQ
02e9PUSH20x178e
02ecJUMPI
02edDUP1
02eePUSH40xa12af04d
02f3EQ
02f4PUSH20x1401
02f7JUMPI
02f8DUP1
02f9PUSH40xa2f2d26a
02feEQ
02ffPUSH20x13fc
0302JUMPI
0303DUP1
0304PUSH40xa7b0e4d0
0309EQ
030aPUSH20x13c7
030dJUMPI
030eDUP1
030fPUSH40xab50578e
0314EQ
0315PUSH20x11fb
0318JUMPI
0319DUP1
031aPUSH40xabf1570d
031fEQ
0320PUSH20x1100
0323JUMPI
0324DUP1
0325PUSH40xac81af2f
032aEQ
032bPUSH20x10a4
032eJUMPI
032fDUP1
0330PUSH40xae1be128
0335EQ
0336PUSH20x106a
0339JUMPI
033aDUP1
033bPUSH40xb31ec19e
0340EQ
0341PUSH20x0f2b
0344JUMPI
0345DUP1
0346PUSH40xb5635867
034bEQ
034cPUSH20x0ef1
034fJUMPI
0350DUP1
0351PUSH40xb7e7e92e
0356EQ
0357PUSH20x0ed6
035aJUMPI
035bDUP1
035cPUSH40xbab83782
0361EQ
0362PUSH20x0ed1
0365JUMPI
0366DUP1
0367PUSH40xbc197c81
036cEQ
036dPUSH20x0e39
0370JUMPI
0371DUP1
0372PUSH40xc0347686
0377EQ
0378PUSH20x0e1c
037bJUMPI
037cDUP1
037dPUSH40xc161552b
0382EQ
0383PUSH20x09b0
0386JUMPI
0387DUP1
0388PUSH40xc1dac071
038dEQ
038ePUSH20x07b1
0391JUMPI
0392DUP1
0393PUSH40xc2b9f48e
0398EQ
0399PUSH20x0949
039cJUMPI
039dDUP1
039ePUSH40xc5eeeb39
03a3EQ
03a4PUSH20x092e
03a7JUMPI
03a8DUP1
03a9PUSH40xcf3add5d
03aeEQ
03afPUSH20x08fc
03b2JUMPI
03b3DUP1
03b4PUSH40xd2430e0e
03b9EQ
03baPUSH20x0850
03bdJUMPI
03beDUP1
03bfPUSH40xd5284ee3
03c4EQ
03c5PUSH20x0835
03c8JUMPI
03c9DUP1
03caPUSH40xd8dea2bf
03cfEQ
03d0PUSH20x0830
03d3JUMPI
03d4DUP1
03d5PUSH40xe0236d37
03daEQ
03dbPUSH20x07fc
03deJUMPI
03dfDUP1
03e0PUSH40xe0b180f7
03e5EQ
03e6PUSH20x07e2
03e9JUMPI
03eaDUP1
03ebPUSH40xe9750fcf
03f0EQ
03f1PUSH20x07b6
03f4JUMPI
03f5DUP1
03f6PUSH40xeaff2dda
03fbEQ
03fcPUSH20x07b1
03ffJUMPI
0400DUP1
0401PUSH40xeb75174b
0406EQ
0407PUSH20x0795
040aJUMPI
040bDUP1
040cPUSH40xec0bb249
0411EQ
0412PUSH20x0510
0415JUMPI
0416DUP1
0417PUSH40xf23a6e61
041cEQ
041dPUSH20x04ba
0420JUMPI
0421DUP1
0422PUSH40xf501cff3
0427EQ
0428PUSH20x0488
042bJUMPI
042cDUP1
042dPUSH40xf5951975
0432EQ
0433PUSH20x0449
0436JUMPI
0437PUSH40xfd1d0819
043cEQ
043dPUSH20x0444
0440JUMPI
0441PUSH0
0442DUP1
0443REVERT
0444JUMPDEST
0445PUSH20x4aba
0448JUMP
0449JUMPDEST
044aCALLVALUE
044bPUSH20x0484
044eJUMPI
044fPUSH10x20
0451CALLDATASIZE
0452PUSH10x03
0454NOT
0455ADD
0456SLT
0457PUSH20x0484
045aJUMPI
045bPUSH10xff
045dPUSH20x0464
0460PUSH20x47af
0463JUMP
0464JUMPDEST
0465AND
0466PUSH0
0467MSTORE
0468PUSH10x2b
046aPUSH10x20
046cMSTORE
046dPUSH10x20
046fPUSH10x01
0471PUSH10x01
0473PUSH10x40
0475SHL
0476SUB
0477PUSH10x40
0479PUSH0
047aKECCAK256
047bSLOAD
047cAND
047dPUSH10x40
047fMLOAD
0480SWAP1
0481DUP2
0482MSTORE
0483RETURN
0484JUMPDEST
0485PUSH0
0486DUP1
0487REVERT
0488JUMPDEST
0489CALLVALUE
048aPUSH20x0484
048dJUMPI
048ePUSH10x20
0490CALLDATASIZE
0491PUSH10x03
0493NOT
0494ADD
0495SLT
0496PUSH20x0484
0499JUMPI
049aPUSH10xff
049cPUSH20x04a3
049fPUSH20x47af
04a2JUMP
04a3JUMPDEST
04a4AND
04a5PUSH0
04a6MSTORE
04a7PUSH10x23
04a9PUSH10x20
04abMSTORE
04acPUSH10x20
04aePUSH10x40
04b0PUSH0
04b1KECCAK256
04b2SLOAD
04b3PUSH10x40
04b5MLOAD
04b6SWAP1
04b7DUP2
04b8MSTORE
04b9RETURN
04baJUMPDEST
04bbCALLVALUE
04bcPUSH20x0484
04bfJUMPI
04c0PUSH10xa0
04c2CALLDATASIZE
04c3PUSH10x03
04c5NOT
04c6ADD
04c7SLT
04c8PUSH20x0484
04cbJUMPI
04ccPUSH20x04d3
04cfPUSH20x4697
04d2JUMP
04d3JUMPDEST
04d4POP
04d5PUSH20x04dc
04d8PUSH20x46ad
04dbJUMP
04dcJUMPDEST
04ddPOP
04dePUSH10x84
04e0CALLDATALOAD
04e1PUSH10x01
04e3PUSH10x01
04e5PUSH10x40
04e7SHL
04e8SUB
04e9DUP2
04eaGT
04ebPUSH20x0484
04eeJUMPI
04efPUSH20x04fc
04f2SWAP1
04f3CALLDATASIZE
04f4SWAP1
04f5PUSH10x04
04f7ADD
04f8PUSH20x48a9
04fbJUMP
04fcJUMPDEST
04fdPOP
04fePOP
04ffPUSH10x40
0501MLOAD
0502PUSH40xf23a6e61
0507PUSH10xe0
0509SHL
050aDUP2
050bMSTORE
050cPUSH10x20
050eSWAP1
050fRETURN
0510JUMPDEST
0511CALLVALUE
0512PUSH20x0484
0515JUMPI
0516PUSH0
0517CALLDATASIZE
0518PUSH10x03
051aNOT
051bADD
051cSLT
051dPUSH20x0484
0520JUMPI
0521PUSH0
0522PUSH10x01
0524JUMPDEST
0525PUSH10xff
0527DUP2
0528AND
0529PUSH10x09
052bDUP2
052cGT
052dPUSH20x078e
0530JUMPI
0531DUP1
0532PUSH0
0533MSTORE
0534PUSH10x27
0536PUSH10x20
0538MSTORE
0539PUSH10x01
053bPUSH10x01
053dPUSH10x40
053fSHL
0540SUB
0541PUSH10x40
0543PUSH0
0544KECCAK256
0545SLOAD
0546AND
0547SWAP1
0548PUSH0
0549MSTORE
054aPUSH10x2a
054cPUSH10x20
054eMSTORE
054fPUSH10x01
0551PUSH10x01
0553PUSH10x40
0555SHL
0556SUB
0557PUSH10x40
0559PUSH0
055aKECCAK256
055bSLOAD
055cAND
055dSUB
055ePUSH20x056f
0561JUMPI
0562PUSH20x056a
0565SWAP1
0566PUSH20x4cf0
0569JUMP
056aJUMPDEST
056bPUSH20x0524
056eJUMP
056fJUMPDEST
0570POP
0571POP
0572PUSH10x01
0574JUMPDEST
0575ISZERO
0576PUSH20x077f
0579JUMPI
057aPUSH10x01
057cPUSH10x01
057ePUSH10x40
0580SHL
0581SUB
0582PUSH20x058e
0585DUP2
0586PUSH10x29
0588SLOAD
0589AND
058aPUSH20x4b57
058dJUMP
058eJUMPDEST
058fAND
0590DUP1
0591PUSH0
0592MSTORE
0593PUSH10x28
0595PUSH10x20
0597MSTORE
0598PUSH10x40
059aPUSH0
059bKECCAK256
059cSWAP1
059dPUSH10x01
059fJUMPDEST
05a0PUSH10xff
05a2DUP2
05a3AND
05a4SWAP1
05a5PUSH10x09
05a7DUP3
05a8GT
05a9PUSH20x0621
05acJUMPI
05adDUP2
05aePUSH20x061c
05b1SWAP3
05b2PUSH0
05b3MSTORE
05b4PUSH10x26
05b6PUSH10x20
05b8MSTORE
05b9PUSH10x40
05bbPUSH0
05bcKECCAK256
05bdSLOAD
05bePUSH20x05c7
05c1DUP4
05c2DUP8
05c3PUSH20x4e3e
05c6JUMP
05c7JUMPDEST
05c8DUP2
05c9SWAP3
05caSWAP2
05cbSLOAD
05ccSWAP1
05cdPUSH10x03
05cfSHL
05d0SWAP2
05d1DUP3
05d2SHL
05d3SWAP2
05d4PUSH0
05d5NOT
05d6SWAP1
05d7SHL
05d8NOT
05d9AND
05daOR
05dbSWAP1
05dcSSTORE
05ddDUP1
05dePUSH0
05dfMSTORE
05e0PUSH10x27
05e2PUSH10x20
05e4MSTORE
05e5PUSH10x01
05e7PUSH10x01
05e9PUSH10x40
05ebSHL
05ecSUB
05edPUSH10x40
05efPUSH0
05f0KECCAK256
05f1SLOAD
05f2AND
05f3SWAP1
05f4PUSH0
05f5MSTORE
05f6PUSH10x2a
05f8PUSH10x20
05faMSTORE
05fbPUSH10x01
05fdPUSH10x01
05ffPUSH10x40
0601SHL
0602SUB
0603PUSH10x40
0605PUSH0
0606KECCAK256
0607SWAP2
0608AND
0609PUSH10x01
060bPUSH10x01
060dPUSH10x40
060fSHL
0610SUB
0611NOT
0612DUP3
0613SLOAD
0614AND
0615OR
0616SWAP1
0617SSTORE
0618PUSH20x4cf0
061bJUMP
061cJUMPDEST
061dPUSH20x059f
0620JUMP
0621JUMPDEST
0622POP
0623POP
0624PUSH20x0634
0627PUSH20x062f
062aDUP4
062bPUSH20x4d6b
062eJUMP
062fJUMPDEST
0630PUSH20x6eaf
0633JUMP
0634JUMPDEST
0635SWAP2
0636DUP3
0637MLOAD
0638JUMPDEST
0639PUSH10x01
063bDUP2
063cGT
063dPUSH20x0701
0640JUMPI
0641POP
0642DUP3
0643MLOAD
0644ISZERO
0645PUSH20x06ed
0648JUMPI
0649PUSH320x5a33796ad97fb8cf3289d33093f3deb1797b5bcf1e795e716194046784ca4d3c
066aPUSH10x40
066cPUSH10x0b
066eDUP5
066fSWAP4
0670PUSH10x20
0672DUP1
0673SWAP8
0674ADD
0675MLOAD
0676PUSH10x0a
0678DUP3
0679ADD
067aSSTORE
067bADD
067cPUSH10x01
067ePUSH10x01
0680PUSH10x40
0682SHL
0683SUB
0684DUP1
0685NUMBER
0686AND
0687AND
0688PUSH10x01
068aPUSH10x01
068cPUSH10x40
068eSHL
068fSUB
0690NOT
0691DUP3
0692SLOAD
0693AND
0694OR
0695DUP2
0696SSTORE
0697DUP1
0698SLOAD
0699PUSH80xffffffffffffffff
06a2PUSH10x40
06a4SHL
06a5TIMESTAMP
06a6DUP5
06a7SHL
06a8AND
06a9SWAP1
06aaPUSH80xffffffffffffffff
06b3PUSH10x40
06b5SHL
06b6NOT
06b7AND
06b8OR
06b9DUP2
06baSSTORE
06bbDUP4
06bcPUSH10x01
06bePUSH10x01
06c0PUSH10x40
06c2SHL
06c3SUB
06c4NOT
06c5PUSH10x29
06c7SLOAD
06c8AND
06c9OR
06caPUSH10x29
06ccSSTORE
06cdSLOAD
06cePUSH10x01
06d0PUSH10x01
06d2PUSH10x40
06d4SHL
06d5SUB
06d6DUP3
06d7MLOAD
06d8SWAP2
06d9DUP2
06daDUP2
06dbAND
06dcDUP4
06ddMSTORE
06deDUP4
06dfSHR
06e0AND
06e1DUP7
06e2DUP3
06e3ADD
06e4MSTORE
06e5LOG2
06e6PUSH10x40
06e8MLOAD
06e9SWAP1
06eaDUP2
06ebMSTORE
06ecRETURN
06edJUMPDEST
06eePUSH40x4e487b71
06f3PUSH10xe0
06f5SHL
06f6PUSH0
06f7MSTORE
06f8PUSH10x32
06faPUSH10x04
06fcMSTORE
06fdPUSH10x24
06ffPUSH0
0700REVERT
0701JUMPDEST
0702PUSH0
0703JUMPDEST
0704DUP2
0705PUSH10x01
0707SHR
0708DUP2
0709LT
070aPUSH20x0716
070dJUMPI
070ePOP
070fPUSH10x01
0711SHR
0712PUSH20x0638
0715JUMP
0716JUMPDEST
0717PUSH10x01
0719DUP2
071aSWAP1
071bSHL
071cSWAP1
071dPUSH10x01
071fPUSH10x01
0721PUSH10xff
0723SHL
0724SUB
0725DUP2
0726AND
0727DUP2
0728SUB
0729PUSH20x076b
072cJUMPI
072dPUSH20x0736
0730DUP3
0731DUP8
0732PUSH20x4b75
0735JUMP
0736JUMPDEST
0737MLOAD
0738SWAP2
0739PUSH10x01
073bDUP2
073cADD
073dDUP1
073eSWAP2
073fGT
0740PUSH20x076b
0743JUMPI
0744PUSH10x01
0746SWAP3
0747PUSH20x0753
074aPUSH20x075a
074dSWAP3
074eDUP10
074fPUSH20x4b75
0752JUMP
0753JUMPDEST
0754MLOAD
0755SWAP1
0756PUSH20x6f2b
0759JUMP
075aJUMPDEST
075bPUSH20x0764
075eDUP3
075fDUP9
0760PUSH20x4b75
0763JUMP
0764JUMPDEST
0765MSTORE
0766ADD
0767PUSH20x0703
076aJUMP
076bJUMPDEST
076cPUSH40x4e487b71
0771PUSH10xe0
0773SHL
0774PUSH0
0775MSTORE
0776PUSH10x11
0778PUSH10x04
077aMSTORE
077bPUSH10x24
077dPUSH0
077eREVERT
077fJUMPDEST
0780PUSH40x0eba0e1b
0785PUSH10xe2
0787SHL
0788PUSH0
0789MSTORE
078aPUSH10x04
078cPUSH0
078dREVERT
078eJUMPDEST
078fPOP
0790POP
0791PUSH20x0574
0794JUMP
0795JUMPDEST
0796CALLVALUE
0797PUSH20x0484
079aJUMPI
079bPUSH0
079cCALLDATASIZE
079dPUSH10x03
079fNOT
07a0ADD
07a1SLT
07a2PUSH20x0484
07a5JUMPI
07a6PUSH10x20
07a8PUSH10x40
07aaMLOAD
07abPUSH20x0400
07aeDUP2
07afMSTORE
07b0RETURN
07b1JUMPDEST
07b2PUSH20x4b18
07b5JUMP
07b6JUMPDEST
07b7CALLVALUE
07b8PUSH20x0484
07bbJUMPI
07bcPUSH10x20
07beCALLDATASIZE
07bfPUSH10x03
07c1NOT
07c2ADD
07c3SLT
07c4PUSH20x0484
07c7JUMPI
07c8PUSH10x04
07caCALLDATALOAD
07cbPUSH10x1d
07cdDUP2
07ceLT
07cfISZERO
07d0PUSH20x06ed
07d3JUMPI
07d4PUSH10x20
07d6SWAP1
07d7PUSH10x04
07d9ADD
07daSLOAD
07dbPUSH10x40
07ddMLOAD
07deSWAP1
07dfDUP2
07e0MSTORE
07e1RETURN
07e2JUMPDEST
07e3CALLVALUE
07e4PUSH20x0484
07e7JUMPI
07e8PUSH0
07e9CALLDATASIZE
07eaPUSH10x03
07ecNOT
07edADD
07eeSLT
07efPUSH20x0484
07f2JUMPI
07f3PUSH10x20
07f5PUSH10x40
07f7MLOAD
07f8PUSH0
07f9DUP2
07faMSTORE
07fbRETURN
07fcJUMPDEST
07fdCALLVALUE
07fePUSH20x0484
0801JUMPI
0802PUSH10x40
0804CALLDATASIZE
0805PUSH10x03
0807NOT
0808ADD
0809SLT
080aPUSH20x0484
080dJUMPI
080ePUSH10x20
0810PUSH20x0828
0813PUSH20x081a
0816PUSH20x4697
0819JUMP
081aJUMPDEST
081bPUSH20x0822
081ePUSH20x472f
0821JUMP
0822JUMPDEST
0823SWAP1
0824PUSH20x514c
0827JUMP
0828JUMPDEST
0829PUSH10x40
082bMLOAD
082cSWAP1
082dDUP2
082eMSTORE
082fRETURN
0830JUMPDEST
0831PUSH20x4af0
0834JUMP
0835JUMPDEST
0836CALLVALUE
0837PUSH20x0484
083aJUMPI
083bPUSH0
083cCALLDATASIZE
083dPUSH10x03
083fNOT
0840ADD
0841SLT
0842PUSH20x0484
0845JUMPI
0846PUSH10x20
0848PUSH10x40
084aMLOAD
084bPUSH10x14
084dDUP2
084eMSTORE
084fRETURN
0850JUMPDEST
0851CALLVALUE
0852PUSH20x0484
0855JUMPI
0856PUSH10x40
0858CALLDATASIZE
0859PUSH10x03
085bNOT
085cADD
085dSLT
085ePUSH20x0484
0861JUMPI
0862PUSH20x0869
0865PUSH20x47af
0868JUMP
0869JUMPDEST
086aPUSH20x0872
086dDUP2
086ePUSH20x690b
0871JUMP
0872JUMPDEST
0873PUSH20x087e
0876PUSH10x24
0878CALLDATALOAD
0879DUP3
087aPUSH20x4b89
087dJUMP
087eJUMPDEST
087fSWAP1
0880PUSH10x40
0882MLOAD
0883SWAP2
0884PUSH20x0320
0887PUSH20x0890
088aDUP2
088bDUP6
088cPUSH20x4814
088fJUMP
0890JUMPDEST
0891PUSH10x18
0893DUP5
0894MSTORE
0895PUSH10x1f
0897NOT
0898ADD
0899CALLDATASIZE
089aPUSH10x20
089cDUP6
089dADD
089eCALLDATACOPY
089fPUSH0
08a0SWAP1
08a1JUMPDEST
08a2PUSH10x18
08a4DUP3
08a5LT
08a6PUSH20x08c3
08a9JUMPI
08aaPUSH10x40
08acMLOAD
08adPUSH10x20
08afDUP1
08b0DUP3
08b1MSTORE
08b2DUP2
08b3SWAP1
08b4PUSH20x08bf
08b7SWAP1
08b8DUP3
08b9ADD
08baDUP8
08bbPUSH20x4a45
08beJUMP
08bfJUMPDEST
08c0SUB
08c1SWAP1
08c2RETURN
08c3JUMPDEST
08c4DUP1
08c5PUSH20x08d3
08c8PUSH10x01
08caDUP1
08cbSWAP4
08ccXOR
08cdDUP5
08ceDUP7
08cfPUSH20x695b
08d2JUMP
08d3JUMPDEST
08d4PUSH20x08dd
08d7DUP5
08d8DUP8
08d9PUSH20x4b75
08dcJUMP
08ddJUMPDEST
08deMSTORE
08dfDUP2
08e0SHR
08e1SWAP2
08e2ADD
08e3SWAP1
08e4PUSH20x08a1
08e7JUMP
08e8JUMPDEST
08e9PUSH40x4e487b71
08eePUSH10xe0
08f0SHL
08f1PUSH0
08f2MSTORE
08f3PUSH10x41
08f5PUSH10x04
08f7MSTORE
08f8PUSH10x24
08faPUSH0
08fbREVERT
08fcJUMPDEST
08fdCALLVALUE
08fePUSH20x0484
0901JUMPI
0902PUSH10x20
0904CALLDATASIZE
0905PUSH10x03
0907NOT
0908ADD
0909SLT
090aPUSH20x0484
090dJUMPI
090ePUSH10xff
0910PUSH20x0917
0913PUSH20x47af
0916JUMP
0917JUMPDEST
0918AND
0919PUSH0
091aMSTORE
091bPUSH10x01
091dPUSH10x20
091fMSTORE
0920PUSH10x20
0922PUSH10x40
0924PUSH0
0925KECCAK256
0926SLOAD
0927PUSH10x40
0929MLOAD
092aSWAP1
092bDUP2
092cMSTORE
092dRETURN
092eJUMPDEST
092fCALLVALUE
0930PUSH20x0484
0933JUMPI
0934PUSH0
0935CALLDATASIZE
0936PUSH10x03
0938NOT
0939ADD
093aSLT
093bPUSH20x0484
093eJUMPI
093fPUSH10x20
0941PUSH10x40
0943MLOAD
0944PUSH10x1c
0946DUP2
0947MSTORE
0948RETURN
0949JUMPDEST
094aCALLVALUE
094bPUSH20x0484
094eJUMPI
094fPUSH10x40
0951CALLDATASIZE
0952PUSH10x03
0954NOT
0955ADD
0956SLT
0957PUSH20x0484
095aJUMPI
095bPUSH10x20
095dPUSH10x40
095fMLOAD
0960DUP2
0961DUP2
0962ADD
0963SWAP1
0964PUSH320xf12bce0649327409848ceec32acf20a7e21971869cbf30f9d92357bbe85565b7
0985DUP3
0986MSTORE
0987PUSH10x04
0989CALLDATALOAD
098aPUSH10x40
098cDUP3
098dADD
098eMSTORE
098fPUSH10x24
0991CALLDATALOAD
0992PUSH10x60
0994DUP3
0995ADD
0996MSTORE
0997PUSH10x60
0999DUP2
099aMSTORE
099bPUSH20x09a5
099ePUSH10x80
09a0DUP3
09a1PUSH20x4814
09a4JUMP
09a5JUMPDEST
09a6MLOAD
09a7SWAP1
09a8KECCAK256
09a9PUSH10x40
09abMLOAD
09acSWAP1
09adDUP2
09aeMSTORE
09afRETURN
09b0JUMPDEST
09b1CALLVALUE
09b2PUSH20x0484
09b5JUMPI
09b6PUSH10x60
09b8CALLDATASIZE
09b9PUSH10x03
09bbNOT
09bcADD
09bdSLT
09bePUSH20x0484
09c1JUMPI
09c2PUSH10x04
09c4CALLDATALOAD
09c5PUSH10x01
09c7PUSH10x01
09c9PUSH10x40
09cbSHL
09ccSUB
09cdDUP2
09ceGT
09cfPUSH20x0484
09d2JUMPI
09d3PUSH20x09e0
09d6SWAP1
09d7CALLDATASIZE
09d8SWAP1
09d9PUSH10x04
09dbADD
09dcPUSH20x4667
09dfJUMP
09e0JUMPDEST
09e1PUSH20x09e8
09e4PUSH20x472f
09e7JUMP
09e8JUMPDEST
09e9PUSH10x44
09ebCALLDATALOAD
09ecPUSH10x01
09eePUSH10x01
09f0PUSH10x40
09f2SHL
09f3SUB
09f4DUP2
09f5GT
09f6PUSH20x0484
09f9JUMPI
09faPUSH20x0a07
09fdSWAP1
09feCALLDATASIZE
09ffSWAP1
0a00PUSH10x04
0a02ADD
0a03PUSH20x4667
0a06JUMP
0a07JUMPDEST
0a08PUSH10x01
0a0aPUSH0
0a0bSWAP1
0a0cDUP2
0a0dMSTORE
0a0ePUSH10x20
0a10MSTORE
0a11PUSH320xada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d
0a32SLOAD
0a33SWAP1
0a34SWAP3
0a35SWAP2
0a36DUP2
0a37ISZERO
0a38PUSH20x0e08
0a3bJUMPI
0a3cPUSH20x0a44
0a3fDUP6
0a40PUSH20x4cbe
0a43JUMP
0a44JUMPDEST
0a45SWAP4
0a46PUSH20x0a4e
0a49DUP7
0a4aPUSH20x4cbe
0a4dJUMP
0a4eJUMPDEST
0a4fSWAP7
0a50PUSH0
0a51JUMPDEST
0a52DUP8
0a53DUP2
0a54LT
0a55PUSH20x0db2
0a58JUMPI
0a59POP
0a5aPOP
0a5bSWAP3
0a5cPUSH20x0ac3
0a5fSWAP3
0a60PUSH20x0b11
0a63SWAP3
0a64PUSH20x0b17
0a67SWAP6
0a68PUSH10x01
0a6aPUSH0
0a6bMSTORE
0a6cPUSH10x2b
0a6ePUSH10x20
0a70MSTORE
0a71PUSH20x0adb
0a74DUP11
0a75PUSH20x0ab1
0a78PUSH20x0ad1
0a7bDUP12
0a7cPUSH10x01
0a7ePUSH10x01
0a80PUSH10x40
0a82SHL
0a83SUB
0a84PUSH10x40
0a86PUSH0
0a87KECCAK256
0a88SLOAD
0a89AND
0a8aSWAP11
0a8bDUP12
0a8cSWAP5
0a8dPUSH10x40
0a8fMLOAD
0a90SWAP5
0a91DUP6
0a92SWAP4
0a93PUSH10x20
0a95DUP6
0a96ADD
0a97SWAP8
0a98PUSH10x01
0a9aDUP10
0a9bMSTORE
0a9cPUSH10x40
0a9eDUP7
0a9fADD
0aa0MSTORE
0aa1PUSH10x80
0aa3PUSH10x60
0aa5DUP7
0aa6ADD
0aa7MSTORE
0aa8PUSH10xa0
0aaaDUP6
0aabADD
0aacSWAP1
0aadPUSH20x4a45
0ab0JUMP
0ab1JUMPDEST
0ab2DUP4
0ab3DUP2
0ab4SUB
0ab5PUSH10x1f
0ab7NOT
0ab8ADD
0ab9PUSH10x80
0abbDUP6
0abcADD
0abdMSTORE
0abeSWAP1
0abfPUSH20x4a45
0ac2JUMP
0ac3JUMPDEST
0ac4SUB
0ac5PUSH10x1f
0ac7NOT
0ac8DUP2
0ac9ADD
0acaDUP4
0acbMSTORE
0accDUP3
0acdPUSH20x4814
0ad0JUMP
0ad1JUMPDEST
0ad2MLOAD
0ad3SWAP1
0ad4KECCAK256
0ad5DUP6
0ad6ADDRESS
0ad7PUSH20x644e
0adaJUMP
0adbJUMPDEST
0adcSWAP1
0addPUSH10x01
0adfPUSH0
0ae0MSTORE
0ae1PUSH10x01
0ae3PUSH10x20
0ae5MSTORE
0ae6PUSH10x40
0ae8PUSH0
0ae9KECCAK256
0aeaSLOAD
0aebSWAP3
0aecPUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0b0dPUSH20x6771
0b10JUMP
0b11JUMPDEST
0b12POP
0b13PUSH20x4b57
0b16JUMP
0b17JUMPDEST
0b18PUSH10x01
0b1aPUSH0
0b1bMSTORE
0b1cPUSH10x2b
0b1ePUSH10x20
0b20MSTORE
0b21PUSH10x01
0b23PUSH10x01
0b25PUSH10x40
0b27SHL
0b28SUB
0b29PUSH10x40
0b2bPUSH0
0b2cKECCAK256
0b2dSWAP2
0b2eAND
0b2fPUSH10x01
0b31PUSH10x01
0b33PUSH10x40
0b35SHL
0b36SUB
0b37NOT
0b38DUP3
0b39SLOAD
0b3aAND
0b3bOR
0b3cSWAP1
0b3dSSTORE
0b3ePUSH0
0b3fJUMPDEST
0b40DUP3
0b41DUP2
0b42LT
0b43PUSH20x0b51
0b46JUMPI
0b47PUSH20x0b4f
0b4aDUP4
0b4bPUSH20x540f
0b4eJUMP
0b4fJUMPDEST
0b50STOP
0b51JUMPDEST
0b52PUSH20x0b5b
0b55DUP2
0b56DUP4
0b57PUSH20x4b75
0b5aJUMP
0b5bJUMPDEST
0b5cMLOAD
0b5dPUSH20x0b66
0b60DUP3
0b61DUP7
0b62PUSH20x4b75
0b65JUMP
0b66JUMPDEST
0b67MLOAD
0b68PUSH0
0b69DUP3
0b6aDUP2
0b6bMSTORE
0b6cPUSH0
0b6dMLOAD
0b6ePUSH10x20
0b70PUSH20x736f
0b73PUSH0
0b74CODECOPY
0b75PUSH0
0b76MLOAD
0b77SWAP1
0b78PUSH0
0b79MSTORE
0b7aPUSH10x20
0b7cMSTORE
0b7dPUSH10x40
0b7fSWAP1
0b80KECCAK256
0b81SLOAD
0b82DUP1
0b83PUSH20x0d64
0b86JUMPI
0b87POP
0b88PUSH10x01
0b8aPUSH0
0b8bMSTORE
0b8cPUSH320xbbbb3b1da0cb0951f34c5e9db4606f934b7367b5284f29163e9e6fe67e1e97d6
0badPUSH10x20
0bafMSTORE
0bb0PUSH0
0bb1MLOAD
0bb2PUSH10x20
0bb4PUSH20x734f
0bb7PUSH0
0bb8CODECOPY
0bb9PUSH0
0bbaMLOAD
0bbbSWAP1
0bbcPUSH0
0bbdMSTORE
0bbeSLOAD
0bbfSWAP2
0bc0PUSH30x100000
0bc4DUP4
0bc5LT
0bc6ISZERO
0bc7PUSH20x0d4b
0bcaJUMPI
0bcbDUP3
0bccPUSH30x100000
0bd0OR
0bd1SWAP3
0bd2PUSH10x01
0bd4DUP2
0bd5ADD
0bd6DUP1
0bd7SWAP2
0bd8GT
0bd9PUSH20x076b
0bdcJUMPI
0bddPUSH10x01
0bdfPUSH0
0be0DUP2
0be1SWAP1
0be2MSTORE
0be3PUSH0
0be4MLOAD
0be5PUSH10x20
0be7PUSH20x734f
0beaPUSH0
0bebCODECOPY
0becPUSH0
0bedMLOAD
0beeSWAP1
0befPUSH0
0bf0MSTORE
0bf1SWAP2
0bf2SWAP1
0bf3SWAP2
0bf4SSTORE
0bf5PUSH10x23
0bf7PUSH10x20
0bf9MSTORE
0bfaPUSH320xb361aea33a0348d043deace4a562cb920ac10508397ad80f12dfe9a2a063e047
0c1bDUP1
0c1cSLOAD
0c1dSWAP2
0c1eDUP3
0c1fADD
0c20SWAP2
0c21DUP3
0c22LT
0c23PUSH20x076b
0c26JUMPI
0c27SSTORE
0c28PUSH10x01
0c2aDUP4
0c2bADD
0c2cDUP1
0c2dDUP5
0c2eGT
0c2fPUSH20x076b
0c32JUMPI
0c33PUSH0
0c34DUP3
0c35DUP2
0c36MSTORE
0c37PUSH0
0c38MLOAD
0c39PUSH10x20
0c3bPUSH20x736f
0c3ePUSH0
0c3fCODECOPY
0c40PUSH0
0c41MLOAD
0c42SWAP1
0c43PUSH0
0c44MSTORE
0c45PUSH10x20
0c47SWAP1
0c48DUP2
0c49MSTORE
0c4aPUSH10x40
0c4cDUP1
0c4dDUP4
0c4eKECCAK256
0c4fSWAP4
0c50SWAP1
0c51SWAP4
0c52SSTORE
0c53DUP6
0c54DUP3
0c55MSTORE
0c56PUSH320xe39b43e4224876d80510ac9d8f190663bcce357e28a4aec26f3bf2e600bb40ec
0c77SWAP1
0c78MSTORE
0c79KECCAK256
0c7aSSTORE
0c7bJUMPDEST
0c7cPUSH0
0c7dDUP3
0c7eDUP2
0c7fMSTORE
0c80PUSH320xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0
0ca1PUSH10x20
0ca3SWAP1
0ca4DUP2
0ca5MSTORE
0ca6PUSH10x40
0ca8DUP1
0ca9DUP4
0caaKECCAK256
0cabDUP5
0cacSWAP1
0cadSSTORE
0caeMLOAD
0cafSWAP1
0cb0DUP2
0cb1ADD
0cb2SWAP2
0cb3DUP3
0cb4MSTORE
0cb5PUSH10x21
0cb7DUP1
0cb8DUP3
0cb9ADD
0cbaSWAP4
0cbbSWAP1
0cbcSWAP4
0cbdMSTORE
0cbeSWAP2
0cbfDUP3
0cc0MSTORE
0cc1SWAP1
0cc2PUSH20x0ccc
0cc5PUSH10x41
0cc7DUP3
0cc8PUSH20x4814
0ccbJUMP
0cccJUMPDEST
0ccdMLOAD
0cceSWAP1
0ccfKECCAK256
0cd0SWAP1
0cd1PUSH0
0cd2JUMPDEST
0cd3PUSH10x18
0cd5DUP2
0cd6LT
0cd7PUSH20x0cf3
0cdaJUMPI
0cdbPOP
0cdcPOP
0cddSWAP1
0cdePUSH10x01
0ce0SWAP2
0ce1DUP3
0ce2PUSH0
0ce3MSTORE
0ce4PUSH10x26
0ce6PUSH10x20
0ce8MSTORE
0ce9PUSH10x40
0cebPUSH0
0cecKECCAK256
0cedSSTORE
0ceeADD
0cefPUSH20x0b3f
0cf2JUMP
0cf3JUMPDEST
0cf4SWAP1
0cf5SWAP2
0cf6PUSH20x0d0e
0cf9SWAP1
0cfaPUSH20x0d08
0cfdPUSH10x01
0cffDUP6
0d00XOR
0d01DUP5
0d02PUSH10x01
0d04PUSH20x695b
0d07JUMP
0d08JUMPDEST
0d09SWAP1
0d0aPUSH20x6f2b
0d0dJUMP
0d0eJUMPDEST
0d0fSWAP2
0d10PUSH10x01
0d12SHR
0d13SWAP1
0d14PUSH10x01
0d16PUSH0
0d17MSTORE
0d18PUSH10x03
0d1aPUSH10x20
0d1cMSTORE
0d1dPUSH10x40
0d1fPUSH0
0d20KECCAK256
0d21SWAP1
0d22PUSH10x01
0d24DUP2
0d25ADD
0d26SWAP2
0d27DUP3
0d28DUP3
0d29GT
0d2aPUSH20x076b
0d2dJUMPI
0d2ePUSH10x01
0d30SWAP3
0d31PUSH0
0d32MSTORE
0d33PUSH10x20
0d35MSTORE
0d36PUSH10x40
0d38PUSH0
0d39KECCAK256
0d3aDUP4
0d3bPUSH0
0d3cMSTORE
0d3dPUSH10x20
0d3fMSTORE
0d40DUP4
0d41PUSH10x40
0d43PUSH0
0d44KECCAK256
0d45SSTORE
0d46ADD
0d47PUSH20x0cd2
0d4aJUMP
0d4bJUMPDEST
0d4cPUSH40x5633a85b
0d51PUSH10xe0
0d53SHL
0d54PUSH0
0d55MSTORE
0d56PUSH10x01
0d58PUSH10x04
0d5aMSTORE
0d5bPUSH10x01
0d5dPUSH10x24
0d5fMSTORE
0d60PUSH10x44
0d62PUSH0
0d63REVERT
0d64JUMPDEST
0d65PUSH0
0d66NOT
0d67DUP2
0d68ADD
0d69SWAP3
0d6aSWAP1
0d6bDUP4
0d6cGT
0d6dPUSH20x076b
0d70JUMPI
0d71PUSH10xff
0d73DUP4
0d74PUSH10x14
0d76SHR
0d77AND
0d78PUSH10x01
0d7aDUP2
0d7bSUB
0d7cPUSH20x0d86
0d7fJUMPI
0d80POP
0d81POP
0d82PUSH20x0c7b
0d85JUMP
0d86JUMPDEST
0d87PUSH10x84
0d89SWAP3
0d8aPOP
0d8bPUSH10x40
0d8dMLOAD
0d8eSWAP2
0d8fPUSH40x431ddf35
0d94PUSH10xe1
0d96SHL
0d97DUP4
0d98MSTORE
0d99PUSH10x01
0d9bPUSH10x04
0d9dDUP5
0d9eADD
0d9fMSTORE
0da0PUSH10x24
0da2DUP4
0da3ADD
0da4MSTORE
0da5PUSH10x44
0da7DUP3
0da8ADD
0da9MSTORE
0daaPUSH10x01
0dacPUSH10x64
0daeDUP3
0dafADD
0db0MSTORE
0db1REVERT
0db2JUMPDEST
0db3DUP1
0db4PUSH20x0dd0
0db7PUSH20x0dcb
0dbaPUSH20x0dc6
0dbdPUSH10x01
0dbfSWAP5
0dc0DUP13
0dc1DUP8
0dc2PUSH20x4ee5
0dc5JUMP
0dc6JUMPDEST
0dc7PUSH20x4b43
0dcaJUMP
0dcbJUMPDEST
0dccPUSH20x4c75
0dcfJUMP
0dd0JUMPDEST
0dd1PUSH20x0dda
0dd4DUP3
0dd5DUP11
0dd6PUSH20x4b75
0dd9JUMP
0ddaJUMPDEST
0ddbMSTORE
0ddcPUSH20x0df7
0ddfPUSH20x0df2
0de2CALLDATASIZE
0de3PUSH20x0ded
0de6DUP5
0de7DUP14
0de8DUP9
0de9PUSH20x4ee5
0decJUMP
0dedJUMPDEST
0deePUSH20x48e3
0df1JUMP
0df2JUMPDEST
0df3PUSH20x4f08
0df6JUMP
0df7JUMPDEST
0df8PUSH20x0e01
0dfbDUP3
0dfcDUP13
0dfdPUSH20x4b75
0e00JUMP
0e01JUMPDEST
0e02MSTORE
0e03ADD
0e04PUSH20x0a51
0e07JUMP
0e08JUMPDEST
0e09PUSH40x47c987b5
0e0ePUSH10xe1
0e10SHL
0e11PUSH0
0e12MSTORE
0e13PUSH10x01
0e15PUSH10x04
0e17MSTORE
0e18PUSH10x24
0e1aPUSH0
0e1bREVERT
0e1cJUMPDEST
0e1dCALLVALUE
0e1ePUSH20x0484
0e21JUMPI
0e22PUSH0
0e23CALLDATASIZE
0e24PUSH10x03
0e26NOT
0e27ADD
0e28SLT
0e29PUSH20x0484
0e2cJUMPI
0e2dPUSH10x20
0e2fPUSH10x40
0e31MLOAD
0e32PUSH30x100000
0e36DUP2
0e37MSTORE
0e38RETURN
0e39JUMPDEST
0e3aCALLVALUE
0e3bPUSH20x0484
0e3eJUMPI
0e3fPUSH10xa0
0e41CALLDATASIZE
0e42PUSH10x03
0e44NOT
0e45ADD
0e46SLT
0e47PUSH20x0484
0e4aJUMPI
0e4bPUSH20x0e52
0e4ePUSH20x4697
0e51JUMP
0e52JUMPDEST
0e53POP
0e54PUSH20x0e5b
0e57PUSH20x46ad
0e5aJUMP
0e5bJUMPDEST
0e5cPOP
0e5dPUSH10x44
0e5fCALLDATALOAD
0e60PUSH10x01
0e62PUSH10x01
0e64PUSH10x40
0e66SHL
0e67SUB
0e68DUP2
0e69GT
0e6aPUSH20x0484
0e6dJUMPI
0e6ePUSH20x0e7b
0e71SWAP1
0e72CALLDATASIZE
0e73SWAP1
0e74PUSH10x04
0e76ADD
0e77PUSH20x4667
0e7aJUMP
0e7bJUMPDEST
0e7cPOP
0e7dPOP
0e7ePUSH10x64
0e80CALLDATALOAD
0e81PUSH10x01
0e83PUSH10x01
0e85PUSH10x40
0e87SHL
0e88SUB
0e89DUP2
0e8aGT
0e8bPUSH20x0484
0e8eJUMPI
0e8fPUSH20x0e9c
0e92SWAP1
0e93CALLDATASIZE
0e94SWAP1
0e95PUSH10x04
0e97ADD
0e98PUSH20x4667
0e9bJUMP
0e9cJUMPDEST
0e9dPOP
0e9ePOP
0e9fPUSH10x84
0ea1CALLDATALOAD
0ea2PUSH10x01
0ea4PUSH10x01
0ea6PUSH10x40
0ea8SHL
0ea9SUB
0eaaDUP2
0eabGT
0eacPUSH20x0484
0eafJUMPI
0eb0PUSH20x0ebd
0eb3SWAP1
0eb4CALLDATASIZE
0eb5SWAP1
0eb6PUSH10x04
0eb8ADD
0eb9PUSH20x48a9
0ebcJUMP
0ebdJUMPDEST
0ebePOP
0ebfPOP
0ec0PUSH10x40
0ec2MLOAD
0ec3PUSH40xbc197c81
0ec8PUSH10xe0
0ecaSHL
0ecbDUP2
0eccMSTORE
0ecdPUSH10x20
0ecfSWAP1
0ed0RETURN
0ed1JUMPDEST
0ed2PUSH20x4a78
0ed5JUMP
0ed6JUMPDEST
0ed7CALLVALUE
0ed8PUSH20x0484
0edbJUMPI
0edcPUSH0
0eddCALLDATASIZE
0edePUSH10x03
0ee0NOT
0ee1ADD
0ee2SLT
0ee3PUSH20x0484
0ee6JUMPI
0ee7PUSH10x20
0ee9PUSH10x40
0eebMLOAD
0eecPUSH10x08
0eeeDUP2
0eefMSTORE
0ef0RETURN
0ef1JUMPDEST
0ef2CALLVALUE
0ef3PUSH20x0484
0ef6JUMPI
0ef7PUSH0
0ef8CALLDATASIZE
0ef9PUSH10x03
0efbNOT
0efcADD
0efdSLT
0efePUSH20x0484
0f01JUMPI
0f02PUSH10x20
0f04PUSH10x40
0f06MLOAD
0f07PUSH320xb3b4169a40f71a0b1b279bcd6443676c80bdb4ebe0b1db4478b6879321174391
0f28DUP2
0f29MSTORE
0f2aRETURN
0f2bJUMPDEST
0f2cCALLVALUE
0f2dPUSH20x0484
0f30JUMPI
0f31PUSH10x40
0f33CALLDATASIZE
0f34PUSH10x03
0f36NOT
0f37ADD
0f38SLT
0f39PUSH20x0484
0f3cJUMPI
0f3dPUSH20x0f44
0f40PUSH20x46ed
0f43JUMP
0f44JUMPDEST
0f45PUSH10x01
0f47PUSH10x01
0f49PUSH10x40
0f4bSHL
0f4cSUB
0f4dPUSH20x0f54
0f50PUSH20x47bf
0f53JUMP
0f54JUMPDEST
0f55SWAP2
0f56PUSH20x0f5e
0f59DUP4
0f5aPUSH20x690b
0f5dJUMP
0f5eJUMPDEST
0f5fAND
0f60DUP1
0f61ISZERO
0f62DUP1
0f63ISZERO
0f64PUSH20x1056
0f67JUMPI
0f68JUMPDEST
0f69PUSH20x1047
0f6cJUMPI
0f6dPUSH0
0f6eMSTORE
0f6fPUSH10x28
0f71PUSH10x20
0f73MSTORE
0f74PUSH20x0f82
0f77PUSH20x062f
0f7aPUSH10x40
0f7cPUSH0
0f7dKECCAK256
0f7ePUSH20x4d6b
0f81JUMP
0f82JUMPDEST
0f83SWAP1
0f84PUSH10xff
0f86PUSH10x40
0f88MLOAD
0f89SWAP2
0f8aPUSH20x0f94
0f8dPUSH10xa0
0f8fDUP5
0f90PUSH20x4814
0f93JUMP
0f94JUMPDEST
0f95PUSH10x04
0f97DUP4
0f98MSTORE
0f99PUSH10x80
0f9bCALLDATASIZE
0f9cPUSH10x20
0f9eDUP6
0f9fADD
0fa0CALLDATACOPY
0fa1AND
0fa2DUP3
0fa3MLOAD
0fa4PUSH0
0fa5SWAP1
0fa6JUMPDEST
0fa7PUSH10x04
0fa9DUP3
0faaLT
0fabPUSH20x0fc4
0faeJUMPI
0fafPUSH10x40
0fb1MLOAD
0fb2PUSH10x20
0fb4DUP1
0fb5DUP3
0fb6MSTORE
0fb7DUP2
0fb8SWAP1
0fb9PUSH20x08bf
0fbcSWAP1
0fbdDUP3
0fbeADD
0fbfDUP8
0fc0PUSH20x4a45
0fc3JUMP
0fc4JUMPDEST
0fc5PUSH20x0fd1
0fc8PUSH10x01
0fcaDUP5
0fcbXOR
0fccDUP7
0fcdPUSH20x4b75
0fd0JUMP
0fd1JUMPDEST
0fd2MLOAD
0fd3PUSH20x0fdc
0fd6DUP4
0fd7DUP7
0fd8PUSH20x4b75
0fdbJUMP
0fdcJUMPDEST
0fddMSTORE
0fdePUSH10x01
0fe0SHR
0fe1SWAP2
0fe2PUSH0
0fe3JUMPDEST
0fe4DUP4
0fe5DUP2
0fe6LT
0fe7PUSH20x0ff9
0feaJUMPI
0febPOP
0fecPUSH10x01
0feeSWAP1
0fefDUP2
0ff0SHR
0ff1SWAP2
0ff2ADD
0ff3SWAP1
0ff4SWAP2
0ff5PUSH20x0fa6
0ff8JUMP
0ff9JUMPDEST
0ffaPUSH10x01
0ffcDUP2
0ffdSWAP1
0ffeSHL
0fffSWAP1
1000PUSH10x01
1002PUSH10x01
1004PUSH10xff
1006SHL
1007SUB
1008DUP2
1009AND
100aDUP2
100bSUB
100cPUSH20x076b
100fJUMPI
1010PUSH20x1019
1013DUP3
1014DUP9
1015PUSH20x4b75
1018JUMP
1019JUMPDEST
101aMLOAD
101bSWAP2
101cPUSH10x01
101eDUP2
101fADD
1020DUP1
1021SWAP2
1022GT
1023PUSH20x076b
1026JUMPI
1027PUSH10x01
1029SWAP3
102aPUSH20x0753
102dPUSH20x1036
1030SWAP3
1031DUP11
1032PUSH20x4b75
1035JUMP
1036JUMPDEST
1037PUSH20x1040
103aDUP3
103bDUP10
103cPUSH20x4b75
103fJUMP
1040JUMPDEST
1041MSTORE
1042ADD
1043PUSH20x0fe3
1046JUMP
1047JUMPDEST
1048PUSH40x12c6ca05
104dPUSH10xe2
104fSHL
1050PUSH0
1051MSTORE
1052PUSH10x04
1054PUSH0
1055REVERT
1056JUMPDEST
1057POP
1058PUSH10x01
105aPUSH10x01
105cPUSH10x40
105eSHL
105fSUB
1060PUSH10x29
1062SLOAD
1063AND
1064DUP2
1065GT
1066PUSH20x0f68
1069JUMP
106aJUMPDEST
106bCALLVALUE
106cPUSH20x0484
106fJUMPI
1070PUSH0
1071CALLDATASIZE
1072PUSH10x03
1074NOT
1075ADD
1076SLT
1077PUSH20x0484
107aJUMPI
107bPUSH10x20
107dPUSH10x40
107fMLOAD
1080PUSH320xaac3f59af44d7d33ac1e055e30909d4c9da73d4ab294df533f38b5e23d376e61
10a1DUP2
10a2MSTORE
10a3RETURN
10a4JUMPDEST
10a5CALLVALUE
10a6PUSH20x0484
10a9JUMPI
10aaPUSH10x40
10acCALLDATASIZE
10adPUSH10x03
10afNOT
10b0ADD
10b1SLT
10b2PUSH20x0484
10b5JUMPI
10b6PUSH20x10bd
10b9PUSH20x47af
10bcJUMP
10bdJUMPDEST
10bePUSH10xff
10c0AND
10c1PUSH0
10c2DUP2
10c3DUP2
10c4MSTORE
10c5PUSH10x21
10c7PUSH10x20
10c9SWAP1
10caDUP2
10cbMSTORE
10ccPUSH10x40
10ceDUP1
10cfDUP4
10d0KECCAK256
10d1PUSH10x24
10d3CALLDATALOAD
10d4DUP1
10d5DUP6
10d6MSTORE
10d7SWAP1
10d8DUP4
10d9MSTORE
10daDUP2
10dbDUP5
10dcKECCAK256
10ddSLOAD
10deSWAP5
10dfDUP5
10e0MSTORE
10e1PUSH10x25
10e3DUP4
10e4MSTORE
10e5DUP2
10e6DUP5
10e7KECCAK256
10e8SWAP1
10e9DUP5
10eaMSTORE
10ebDUP3
10ecMSTORE
10edSWAP2
10eeDUP3
10efSWAP1
10f0KECCAK256
10f1SLOAD
10f2DUP3
10f3MLOAD
10f4SWAP1
10f5DUP2
10f6MSTORE
10f7SWAP3
10f8ISZERO
10f9ISZERO
10faSWAP1
10fbDUP4
10fcADD
10fdMSTORE
10feSWAP1
10ffRETURN
1100JUMPDEST
1101CALLVALUE
1102PUSH20x0484
1105JUMPI
1106PUSH10x80
1108CALLDATASIZE
1109PUSH10x03
110bNOT
110cADD
110dSLT
110ePUSH20x0484
1111JUMPI
1112PUSH20x1119
1115PUSH20x47af
1118JUMP
1119JUMPDEST
111aPUSH20x1121
111dPUSH20x47bf
1120JUMP
1121JUMPDEST
1122SWAP1
1123PUSH10x44
1125CALLDATALOAD
1126PUSH10x01
1128PUSH10x01
112aPUSH10x40
112cSHL
112dSUB
112eDUP2
112fGT
1130PUSH20x0484
1133JUMPI
1134PUSH20x1141
1137SWAP1
1138CALLDATASIZE
1139SWAP1
113aPUSH10x04
113cADD
113dPUSH20x4667
1140JUMP
1141JUMPDEST
1142SWAP2
1143SWAP1
1144PUSH10x64
1146CALLDATALOAD
1147PUSH10x01
1149PUSH10x01
114bPUSH10x40
114dSHL
114eSUB
114fDUP2
1150GT
1151PUSH20x0484
1154JUMPI
1155PUSH20x1162
1158SWAP1
1159CALLDATASIZE
115aSWAP1
115bPUSH10x04
115dADD
115ePUSH20x4667
1161JUMP
1162JUMPDEST
1163SWAP5
1164PUSH10xff
1166DUP5
1167AND
1168PUSH0
1169MSTORE
116aPUSH10x2c
116cPUSH10x20
116eMSTORE
116fPUSH10x01
1171DUP1
1172PUSH10xa0
1174SHL
1175SUB
1176PUSH10x40
1178PUSH0
1179KECCAK256
117aSLOAD
117bAND
117cCALLER
117dSUB
117ePUSH20x11e8
1181JUMPI
1182PUSH20x118b
1185DUP2
1186DUP6
1187PUSH20x69e7
118aJUMP
118bJUMPDEST
118cDUP6
118dDUP6
118eSUB
118fPUSH20x11d1
1192JUMPI
1193PUSH0
1194JUMPDEST
1195DUP6
1196DUP2
1197LT
1198PUSH20x11a5
119bJUMPI
119cPUSH20x0b4f
119fDUP7
11a0DUP7
11a1PUSH20x5454
11a4JUMP
11a5JUMPDEST
11a6DUP1
11a7PUSH20x11cb
11aaPUSH20x11b6
11adPUSH10x01
11afSWAP4
11b0DUP10
11b1DUP9
11b2PUSH20x4b33
11b5JUMP
11b6JUMPDEST
11b7CALLDATALOAD
11b8PUSH20x11c2
11bbDUP4
11bcDUP12
11bdDUP9
11bePUSH20x4b33
11c1JUMP
11c2JUMPDEST
11c3CALLDATALOAD
11c4SWAP1
11c5DUP6
11c6DUP10
11c7PUSH20x519e
11caJUMP
11cbJUMPDEST
11ccADD
11cdPUSH20x1194
11d0JUMP
11d1JUMPDEST
11d2DUP6
11d3DUP6
11d4PUSH40x55c5b3e3
11d9PUSH10xe1
11dbSHL
11dcPUSH0
11ddMSTORE
11dePUSH10x04
11e0MSTORE
11e1PUSH10x24
11e3MSTORE
11e4PUSH10x44
11e6PUSH0
11e7REVERT
11e8JUMPDEST
11e9PUSH40x4a0bfec1
11eePUSH10xe0
11f0SHL
11f1PUSH0
11f2MSTORE
11f3CALLER
11f4PUSH10x04
11f6MSTORE
11f7PUSH10x24
11f9PUSH0
11faREVERT
11fbJUMPDEST
11fcCALLVALUE
11fdPUSH20x0484
1200JUMPI
1201PUSH10x80
1203CALLDATASIZE
1204PUSH10x03
1206NOT
1207ADD
1208SLT
1209PUSH20x0484
120cJUMPI
120dPUSH10x04
120fCALLDATALOAD
1210PUSH10x01
1212PUSH10x01
1214PUSH10x40
1216SHL
1217SUB
1218DUP2
1219GT
121aPUSH20x0484
121dJUMPI
121ePUSH20x122b
1221SWAP1
1222CALLDATASIZE
1223SWAP1
1224PUSH10x04
1226ADD
1227PUSH20x4667
122aJUMP
122bJUMPDEST
122cSWAP1
122dPUSH20x1234
1230PUSH20x472f
1233JUMP
1234JUMPDEST
1235SWAP1
1236PUSH20x123d
1239PUSH20x4719
123cJUMP
123dJUMPDEST
123ePUSH10x64
1240CALLDATALOAD
1241SWAP1
1242PUSH10x01
1244PUSH10x01
1246PUSH10x40
1248SHL
1249SUB
124aDUP3
124bGT
124cPUSH20x0484
124fJUMPI
1250PUSH20x1260
1253PUSH20x12a6
1256SWAP3
1257CALLDATASIZE
1258SWAP1
1259PUSH10x04
125bADD
125cPUSH20x4667
125fJUMP
1260JUMPDEST
1261SWAP2
1262PUSH10x40
1264MLOAD
1265PUSH10x20
1267DUP2
1268ADD
1269SWAP1
126aPUSH10x40
126cDUP3
126dMSTORE
126ePUSH20x129e
1271DUP2
1272PUSH10x01
1274PUSH10x01
1276PUSH10x40
1278SHL
1279SUB
127aPUSH20x1287
127dPUSH10x60
127fDUP4
1280ADD
1281DUP14
1282DUP13
1283PUSH20x510b
1286JUMP
1287JUMPDEST
1288SWAP11
1289AND
128aSWAP10
128bDUP11
128cPUSH10x40
128eDUP4
128fADD
1290MSTORE
1291SUB
1292PUSH10x1f
1294NOT
1295DUP2
1296ADD
1297DUP4
1298MSTORE
1299DUP3
129aPUSH20x4814
129dJUMP
129eJUMPDEST
129fMLOAD
12a0SWAP1
12a1KECCAK256
12a2PUSH20x6272
12a5JUMP
12a6JUMPDEST
12a7PUSH10x09
12a9NOT
12aaDUP4
12abADD
12acPUSH20x13b4
12afJUMPI
12b0PUSH10x01
12b2PUSH10x01
12b4PUSH10x40
12b6SHL
12b7SUB
12b8PUSH10x29
12baSLOAD
12bbAND
12bcPUSH20x12f5
12bfJUMPI
12c0PUSH10x01
12c2JUMPDEST
12c3PUSH10xff
12c5DUP2
12c6AND
12c7PUSH10x09
12c9DUP2
12caGT
12cbPUSH20x1304
12ceJUMPI
12cfPUSH0
12d0MSTORE
12d1PUSH10x27
12d3PUSH10x20
12d5MSTORE
12d6PUSH10x01
12d8PUSH10x01
12daPUSH10x40
12dcSHL
12ddSUB
12dePUSH10x40
12e0PUSH0
12e1KECCAK256
12e2SLOAD
12e3AND
12e4PUSH20x12f5
12e7JUMPI
12e8PUSH20x12f0
12ebSWAP1
12ecPUSH20x4cf0
12efJUMP
12f0JUMPDEST
12f1PUSH20x12c2
12f4JUMP
12f5JUMPDEST
12f6PUSH40xdc63d81f
12fbPUSH10xe0
12fdSHL
12fePUSH0
12ffMSTORE
1300PUSH10x04
1302PUSH0
1303REVERT
1304JUMPDEST
1305DUP3
1306DUP6
1307DUP6
1308PUSH10x01
130aJUMPDEST
130bPUSH10xff
130dDUP2
130eAND
130fPUSH10x09
1311DUP2
1312GT
1313PUSH20x135d
1316JUMPI
1317SWAP1
1318DUP2
1319PUSH20x132e
131cPUSH20x1329
131fPUSH20x1358
1322SWAP5
1323DUP8
1324DUP10
1325PUSH20x4b33
1328JUMP
1329JUMPDEST
132aPUSH20x4bd0
132dJUMP
132eJUMPDEST
132fSWAP1
1330PUSH0
1331MSTORE
1332PUSH10x27
1334PUSH10x20
1336MSTORE
1337PUSH10x01
1339PUSH10x01
133bPUSH10x40
133dSHL
133eSUB
133fPUSH10x40
1341PUSH0
1342KECCAK256
1343SWAP2
1344AND
1345PUSH10x01
1347PUSH10x01
1349PUSH10x40
134bSHL
134cSUB
134dNOT
134eDUP3
134fSLOAD
1350AND
1351OR
1352SWAP1
1353SSTORE
1354PUSH20x4cf0
1357JUMP
1358JUMPDEST
1359PUSH20x130a
135cJUMP
135dJUMPDEST
135ePOP
135fPOP
1360PUSH20x13af
1363PUSH320x15fd51992bd825f38af10ca1ec217ca34f12b24fa98401404df53e3052deac48
1384SWAP4
1385DUP3
1386PUSH10x01
1388PUSH10x01
138aPUSH10x40
138cSHL
138dSUB
138eNOT
138fPUSH10x29
1391SLOAD
1392AND
1393OR
1394PUSH10x29
1396SSTORE
1397PUSH10x40
1399MLOAD
139aSWAP4
139bDUP5
139cSWAP4
139dDUP5
139eMSTORE
139fPUSH10x40
13a1PUSH10x20
13a3DUP6
13a4ADD
13a5MSTORE
13a6PUSH10x40
13a8DUP5
13a9ADD
13aaSWAP2
13abPUSH20x510b
13aeJUMP
13afJUMPDEST
13b0SUB
13b1SWAP1
13b2LOG1
13b3STOP
13b4JUMPDEST
13b5DUP3
13b6PUSH40x2c9979d1
13bbPUSH10xe1
13bdSHL
13bePUSH0
13bfMSTORE
13c0PUSH10x04
13c2MSTORE
13c3PUSH10x24
13c5PUSH0
13c6REVERT
13c7JUMPDEST
13c8CALLVALUE
13c9PUSH20x0484
13ccJUMPI
13cdPUSH10x20
13cfCALLDATASIZE
13d0PUSH10x03
13d2NOT
13d3ADD
13d4SLT
13d5PUSH20x0484
13d8JUMPI
13d9PUSH10x04
13dbCALLDATALOAD
13dcPUSH10x01
13dePUSH10x01
13e0PUSH10x40
13e2SHL
13e3SUB
13e4DUP2
13e5GT
13e6PUSH20x0484
13e9JUMPI
13eaPUSH20x0828
13edPUSH20x0df2
13f0PUSH10x20
13f2SWAP3
13f3CALLDATASIZE
13f4SWAP1
13f5PUSH10x04
13f7ADD
13f8PUSH20x48e3
13fbJUMP
13fcJUMPDEST
13fdPUSH20x4ad5
1400JUMP
1401JUMPDEST
1402CALLVALUE
1403PUSH20x0484
1406JUMPI
1407PUSH10xa0
1409CALLDATASIZE
140aPUSH10x03
140cNOT
140dADD
140eSLT
140fPUSH20x0484
1412JUMPI
1413PUSH20x141a
1416PUSH20x47af
1419JUMP
141aJUMPDEST
141bPUSH10x24
141dCALLDATALOAD
141ePUSH10x01
1420PUSH10x01
1422PUSH10x40
1424SHL
1425SUB
1426DUP2
1427GT
1428PUSH20x0484
142bJUMPI
142cPUSH20x1439
142fSWAP1
1430CALLDATASIZE
1431SWAP1
1432PUSH10x04
1434ADD
1435PUSH20x4667
1438JUMP
1439JUMPDEST
143aSWAP1
143bPUSH10x44
143dCALLDATALOAD
143ePUSH10x01
1440PUSH10x01
1442PUSH10x40
1444SHL
1445SUB
1446DUP2
1447GT
1448PUSH20x0484
144bJUMPI
144cPUSH20x1459
144fSWAP1
1450CALLDATASIZE
1451SWAP1
1452PUSH10x04
1454ADD
1455PUSH20x4667
1458JUMP
1459JUMPDEST
145aPUSH20x1461
145dPUSH20x46d7
1460JUMP
1461JUMPDEST
1462PUSH10x84
1464CALLDATALOAD
1465PUSH10x01
1467PUSH10x01
1469PUSH10x40
146bSHL
146cSUB
146dDUP2
146eGT
146fPUSH20x0484
1472JUMPI
1473PUSH20x1480
1476SWAP1
1477CALLDATASIZE
1478SWAP1
1479PUSH10x04
147bADD
147cPUSH20x4667
147fJUMP
1480JUMPDEST
1481SWAP2
1482PUSH20x148a
1485DUP9
1486PUSH20x690b
1489JUMP
148aJUMPDEST
148bDUP4
148cDUP8
148dEQ
148eDUP1
148fISZERO
1490SWAP1
1491PUSH20x1786
1494JUMPI
1495JUMPDEST
1496PUSH20x176f
1499JUMPI
149aSWAP2
149bPUSH20x14e8
149eSWAP2
149fDUP8
14a0SWAP5
14a1SWAP4
14a2DUP10
14a3PUSH10x40
14a5MLOAD
14a6PUSH20x14e0
14a9DUP2
14aaPUSH20x0ac3
14adPUSH20x14cd
14b0DUP14
14b1PUSH10xff
14b3PUSH10x20
14b5DUP6
14b6ADD
14b7SWAP8
14b8AND
14b9SWAP13
14baDUP14
14bbDUP9
14bcMSTORE
14bdPUSH10x60
14bfPUSH10x40
14c1DUP7
14c2ADD
14c3MSTORE
14c4PUSH10x80
14c6DUP6
14c7ADD
14c8SWAP2
14c9PUSH20x4dd1
14ccJUMP
14cdJUMPDEST
14ceDUP3
14cfDUP2
14d0SUB
14d1PUSH10x1f
14d3NOT
14d4ADD
14d5PUSH10x60
14d7DUP5
14d8ADD
14d9MSTORE
14daDUP11
14dbDUP14
14dcPUSH20x4dd1
14dfJUMP
14e0JUMPDEST
14e1MLOAD
14e2SWAP1
14e3KECCAK256
14e4PUSH20x6096
14e7JUMP
14e8JUMPDEST
14e9PUSH0
14eaJUMPDEST
14ebDUP6
14ecDUP2
14edLT
14eePUSH20x14fb
14f1JUMPI
14f2PUSH20x0b4f
14f5DUP7
14f6DUP9
14f7PUSH20x5454
14faJUMP
14fbJUMPDEST
14fcPUSH20x1506
14ffDUP2
1500DUP4
1501DUP7
1502PUSH20x4b33
1505JUMP
1506JUMPDEST
1507CALLDATALOAD
1508DUP4
1509PUSH0
150aMSTORE
150bPUSH10x25
150dPUSH10x20
150fMSTORE
1510PUSH10x40
1512PUSH0
1513KECCAK256
1514PUSH20x151e
1517DUP4
1518DUP10
1519DUP10
151aPUSH20x4b33
151dJUMP
151eJUMPDEST
151fCALLDATALOAD
1520PUSH0
1521MSTORE
1522PUSH10x20
1524MSTORE
1525PUSH10x40
1527PUSH0
1528KECCAK256
1529SSTORE
152aPUSH20x1534
152dDUP2
152eDUP8
152fDUP8
1530PUSH20x4b33
1533JUMP
1534JUMPDEST
1535CALLDATALOAD
1536PUSH20x1557
1539PUSH20x1543
153cDUP4
153dDUP10
153eDUP10
153fPUSH20x4b33
1542JUMP
1543JUMPDEST
1544CALLDATALOAD
1545PUSH20x154f
1548DUP5
1549DUP7
154aDUP10
154bPUSH20x4b33
154eJUMP
154fJUMPDEST
1550CALLDATALOAD
1551SWAP1
1552DUP11
1553PUSH20x4e4d
1556JUMP
1557JUMPDEST
1558DUP5
1559PUSH0
155aMSTORE
155bPUSH10x21
155dPUSH10x20
155fMSTORE
1560PUSH10x40
1562PUSH0
1563KECCAK256
1564DUP3
1565PUSH0
1566MSTORE
1567PUSH10x20
1569MSTORE
156aPUSH10x40
156cPUSH0
156dKECCAK256
156eSLOAD
156fDUP1
1570ISZERO
1571PUSH0
1572EQ
1573PUSH20x1725
1576JUMPI
1577POP
1578DUP5
1579PUSH0
157aMSTORE
157bPUSH10x24
157dPUSH10x20
157fMSTORE
1580PUSH10x40
1582PUSH0
1583KECCAK256
1584PUSH0
1585DUP1
1586MSTORE
1587PUSH10x20
1589MSTORE
158aPUSH10x40
158cPUSH0
158dKECCAK256
158eSLOAD
158fSWAP2
1590PUSH30x100000
1594DUP4
1595LT
1596ISZERO
1597PUSH20x170e
159aJUMPI
159bDUP3
159cPUSH10x01
159eDUP2
159fADD
15a0DUP1
15a1DUP3
15a2GT
15a3PUSH20x076b
15a6JUMPI
15a7DUP8
15a8PUSH0
15a9MSTORE
15aaPUSH10x24
15acPUSH10x20
15aeMSTORE
15afPUSH10x40
15b1PUSH0
15b2KECCAK256
15b3PUSH0
15b4DUP1
15b5MSTORE
15b6PUSH10x20
15b8MSTORE
15b9DUP1
15baPUSH10x40
15bcPUSH0
15bdKECCAK256
15beSSTORE
15bfDUP8
15c0PUSH0
15c1MSTORE
15c2PUSH10x23
15c4PUSH10x20
15c6MSTORE
15c7PUSH10x40
15c9PUSH0
15caKECCAK256
15cbDUP1
15ccSLOAD
15cdSWAP1
15cePUSH10x01
15d0DUP3
15d1ADD
15d2DUP1
15d3SWAP3
15d4GT
15d5PUSH20x076b
15d8JUMPI
15d9SSTORE
15daDUP8
15dbPUSH0
15dcMSTORE
15ddPUSH10x21
15dfPUSH10x20
15e1MSTORE
15e2PUSH10x40
15e4PUSH0
15e5KECCAK256
15e6DUP4
15e7PUSH0
15e8MSTORE
15e9PUSH10x20
15ebMSTORE
15ecPUSH10x40
15eePUSH0
15efKECCAK256
15f0SSTORE
15f1DUP7
15f2PUSH0
15f3MSTORE
15f4PUSH10x22
15f6PUSH10x20
15f8MSTORE
15f9PUSH10x40
15fbPUSH0
15fcKECCAK256
15fdSWAP1
15fePUSH0
15ffMSTORE
1600PUSH10x20
1602MSTORE
1603PUSH10x40
1605PUSH0
1606KECCAK256
1607SSTORE
1608JUMPDEST
1609DUP5
160aPUSH0
160bMSTORE
160cPUSH10x02
160ePUSH10x20
1610MSTORE
1611PUSH10x40
1613PUSH0
1614KECCAK256
1615DUP3
1616PUSH0
1617MSTORE
1618PUSH10x20
161aMSTORE
161bDUP1
161cPUSH10x40
161ePUSH0
161fKECCAK256
1620SSTORE
1621PUSH10x40
1623MLOAD
1624PUSH10x20
1626DUP2
1627ADD
1628SWAP2
1629PUSH0
162aDUP4
162bMSTORE
162cPUSH10x01
162eDUP4
162fADD
1630MSTORE
1631PUSH20x1646
1634DUP2
1635PUSH10x21
1637DUP5
1638ADD
1639SUB
163aPUSH10x1f
163cNOT
163dDUP2
163eADD
163fDUP4
1640MSTORE
1641DUP3
1642PUSH20x4814
1645JUMP
1646JUMPDEST
1647MLOAD
1648SWAP1
1649KECCAK256
164aSWAP1
164bPUSH0
164cJUMPDEST
164dPUSH10x18
164fDUP2
1650LT
1651PUSH20x16b0
1654JUMPI
1655POP
1656POP
1657SWAP1
1658PUSH10x01
165aSWAP2
165bDUP5
165cPUSH0
165dMSTORE
165ePUSH10x26
1660PUSH10x20
1662MSTORE
1663PUSH10x40
1665PUSH0
1666KECCAK256
1667SSTORE
1668PUSH20x1672
166bDUP2
166cDUP9
166dDUP9
166ePUSH20x4b33
1671JUMP
1672JUMPDEST
1673CALLDATALOAD
1674DUP5
1675PUSH320x2e0f0f49c5675cc0ca3b0631934e93ab749ffc07928459dd09921848e5cc2f54
1696PUSH10x20
1698PUSH20x16a2
169bDUP6
169cDUP9
169dDUP12
169ePUSH20x4b33
16a1JUMP
16a2JUMPDEST
16a3CALLDATALOAD
16a4PUSH10x40
16a6MLOAD
16a7SWAP1
16a8DUP2
16a9MSTORE
16aaLOG3
16abADD
16acPUSH20x14ea
16afJUMP
16b0JUMPDEST
16b1SWAP1
16b2SWAP2
16b3PUSH20x16cb
16b6SWAP1
16b7PUSH20x0d08
16baPUSH10x01
16bcDUP6
16bdSWAP12
16beSWAP8
16bfSWAP9
16c0SWAP10
16c1SWAP11
16c2SWAP7
16c3SWAP12
16c4XOR
16c5DUP5
16c6DUP14
16c7PUSH20x695b
16caJUMP
16cbJUMPDEST
16ccSWAP2
16cdPUSH10x01
16cfSHR
16d0SWAP1
16d1DUP7
16d2PUSH0
16d3MSTORE
16d4PUSH10x03
16d6PUSH10x20
16d8MSTORE
16d9PUSH10x40
16dbPUSH0
16dcKECCAK256
16ddSWAP1
16dePUSH10x01
16e0DUP2
16e1ADD
16e2SWAP2
16e3DUP3
16e4DUP3
16e5GT
16e6PUSH20x076b
16e9JUMPI
16eaPUSH10x01
16ecSWAP3
16edPUSH0
16eeMSTORE
16efPUSH10x20
16f1MSTORE
16f2PUSH10x40
16f4PUSH0
16f5KECCAK256
16f6DUP4
16f7PUSH0
16f8MSTORE
16f9PUSH10x20
16fbMSTORE
16fcDUP4
16fdPUSH10x40
16ffPUSH0
1700KECCAK256
1701SSTORE
1702ADD
1703SWAP8
1704SWAP3
1705SWAP7
1706SWAP6
1707SWAP5
1708SWAP4
1709SWAP8
170aPUSH20x164c
170dJUMP
170eJUMPDEST
170fDUP6
1710PUSH40x5633a85b
1715PUSH10xe0
1717SHL
1718PUSH0
1719MSTORE
171aPUSH10x04
171cMSTORE
171dPUSH0
171ePUSH10x24
1720MSTORE
1721PUSH10x44
1723PUSH0
1724REVERT
1725JUMPDEST
1726PUSH0
1727NOT
1728DUP2
1729ADD
172aSWAP3
172bSWAP1
172cDUP4
172dGT
172ePUSH20x076b
1731JUMPI
1732PUSH10xff
1734DUP4
1735PUSH10x14
1737SHR
1738AND
1739SWAP1
173aDUP2
173bPUSH20x1745
173eJUMPI
173fPOP
1740POP
1741PUSH20x1608
1744JUMP
1745JUMPDEST
1746PUSH10x84
1748SWAP2
1749DUP8
174aSWAP2
174bPUSH10x40
174dMLOAD
174eSWAP3
174fPUSH40x431ddf35
1754PUSH10xe1
1756SHL
1757DUP5
1758MSTORE
1759PUSH10x04
175bDUP5
175cADD
175dMSTORE
175ePUSH10x24
1760DUP4
1761ADD
1762MSTORE
1763PUSH10x44
1765DUP3
1766ADD
1767MSTORE
1768PUSH0
1769PUSH10x64
176bDUP3
176cADD
176dMSTORE
176eREVERT
176fJUMPDEST
1770DUP4
1771DUP8
1772PUSH40x55c5b3e3
1777PUSH10xe1
1779SHL
177aPUSH0
177bMSTORE
177cPUSH10x04
177eMSTORE
177fPUSH10x24
1781MSTORE
1782PUSH10x44
1784PUSH0
1785REVERT
1786JUMPDEST
1787POP
1788DUP7
1789ISZERO
178aPUSH20x1495
178dJUMP
178eJUMPDEST
178fCALLVALUE
1790PUSH20x0484
1793JUMPI
1794PUSH10x20
1796CALLDATASIZE
1797PUSH10x03
1799NOT
179aADD
179bSLT
179cPUSH20x0484
179fJUMPI
17a0PUSH10x04
17a2CALLDATALOAD
17a3PUSH10x01
17a5PUSH10x01
17a7PUSH10x40
17a9SHL
17aaSUB
17abDUP2
17acGT
17adPUSH20x0484
17b0JUMPI
17b1PUSH20x17be
17b4SWAP1
17b5CALLDATASIZE
17b6SWAP1
17b7PUSH10x04
17b9ADD
17baPUSH20x4667
17bdJUMP
17beJUMPDEST
17bfPUSH10x01
17c1PUSH0
17c2MSTORE
17c3PUSH10x2c
17c5PUSH10x20
17c7MSTORE
17c8PUSH320xa1f88ee5f5d946e3956f6291445d84cd8aea2bf6c57f4f4ac349f7a338882643
17e9SLOAD
17eaSWAP1
17ebSWAP2
17ecSWAP1
17edPUSH10x01
17efPUSH10x01
17f1PUSH10xa0
17f3SHL
17f4SUB
17f5AND
17f6CALLER
17f7SUB
17f8PUSH20x11e8
17fbJUMPI
17fcPUSH0
17fdJUMPDEST
17feDUP3
17ffDUP2
1800LT
1801PUSH20x180d
1804JUMPI
1805PUSH20x0b4f
1808DUP4
1809PUSH20x540f
180cJUMP
180dJUMPDEST
180ePUSH20x181e
1811PUSH20x0dcb
1814PUSH20x0dc6
1817DUP4
1818DUP7
1819DUP7
181aPUSH20x4ee5
181dJUMP
181eJUMPDEST
181fPUSH20x1830
1822PUSH20x0df2
1825CALLDATASIZE
1826PUSH20x0ded
1829DUP6
182aDUP9
182bDUP9
182cPUSH20x4ee5
182fJUMP
1830JUMPDEST
1831PUSH0
1832DUP3
1833DUP2
1834MSTORE
1835PUSH0
1836MLOAD
1837PUSH10x20
1839PUSH20x736f
183cPUSH0
183dCODECOPY
183ePUSH0
183fMLOAD
1840SWAP1
1841PUSH0
1842MSTORE
1843PUSH10x20
1845MSTORE
1846PUSH10x40
1848SWAP1
1849KECCAK256
184aSLOAD
184bDUP1
184cPUSH20x1a0e
184fJUMPI
1850POP
1851PUSH10x01
1853PUSH0
1854MSTORE
1855PUSH320xbbbb3b1da0cb0951f34c5e9db4606f934b7367b5284f29163e9e6fe67e1e97d6
1876PUSH10x20
1878MSTORE
1879PUSH0
187aMLOAD
187bPUSH10x20
187dPUSH20x734f
1880PUSH0
1881CODECOPY
1882PUSH0
1883MLOAD
1884SWAP1
1885PUSH0
1886MSTORE
1887SLOAD
1888SWAP2
1889PUSH30x100000
188dDUP4
188eLT
188fISZERO
1890PUSH20x0d4b
1893JUMPI
1894DUP3
1895PUSH30x100000
1899OR
189aSWAP3
189bPUSH10x01
189dDUP2
189eADD
189fDUP1
18a0SWAP2
18a1GT
18a2PUSH20x076b
18a5JUMPI
18a6PUSH10x01
18a8PUSH0
18a9DUP2
18aaSWAP1
18abMSTORE
18acPUSH0
18adMLOAD
18aePUSH10x20
18b0PUSH20x734f
18b3PUSH0
18b4CODECOPY
18b5PUSH0
18b6MLOAD
18b7SWAP1
18b8PUSH0
18b9MSTORE
18baSWAP2
18bbSWAP1
18bcSWAP2
18bdSSTORE
18bePUSH10x23
18c0PUSH10x20
18c2MSTORE
18c3PUSH320xb361aea33a0348d043deace4a562cb920ac10508397ad80f12dfe9a2a063e047
18e4DUP1
18e5SLOAD
18e6SWAP2
18e7DUP3
18e8ADD
18e9SWAP2
18eaDUP3
18ebLT
18ecPUSH20x076b
18efJUMPI
18f0SSTORE
18f1PUSH10x01
18f3DUP4
18f4ADD
18f5DUP1
18f6DUP5
18f7GT
18f8PUSH20x076b
18fbJUMPI
18fcPUSH0
18fdDUP3
18feDUP2
18ffMSTORE
1900PUSH0
1901MLOAD
1902PUSH10x20
1904PUSH20x736f
1907PUSH0
1908CODECOPY
1909PUSH0
190aMLOAD
190bSWAP1
190cPUSH0
190dMSTORE
190ePUSH10x20
1910SWAP1
1911DUP2
1912MSTORE
1913PUSH10x40
1915DUP1
1916DUP4
1917KECCAK256
1918SWAP4
1919SWAP1
191aSWAP4
191bSSTORE
191cDUP6
191dDUP3
191eMSTORE
191fPUSH320xe39b43e4224876d80510ac9d8f190663bcce357e28a4aec26f3bf2e600bb40ec
1940SWAP1
1941MSTORE
1942KECCAK256
1943SSTORE
1944JUMPDEST
1945PUSH0
1946DUP3
1947DUP2
1948MSTORE
1949PUSH320xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0
196aPUSH10x20
196cSWAP1
196dDUP2
196eMSTORE
196fPUSH10x40
1971DUP1
1972DUP4
1973KECCAK256
1974DUP5
1975SWAP1
1976SSTORE
1977MLOAD
1978SWAP1
1979DUP2
197aADD
197bSWAP2
197cDUP3
197dMSTORE
197ePUSH10x21
1980DUP1
1981DUP3
1982ADD
1983SWAP4
1984SWAP1
1985SWAP4
1986MSTORE
1987SWAP2
1988DUP3
1989MSTORE
198aSWAP1
198bPUSH20x1995
198ePUSH10x41
1990DUP3
1991PUSH20x4814
1994JUMP
1995JUMPDEST
1996MLOAD
1997SWAP1
1998KECCAK256
1999SWAP1
199aPUSH0
199bJUMPDEST
199cPUSH10x18
199eDUP2
199fLT
19a0PUSH20x19bc
19a3JUMPI
19a4POP
19a5POP
19a6SWAP1
19a7PUSH10x01
19a9SWAP2
19aaDUP3
19abPUSH0
19acMSTORE
19adPUSH10x26
19afPUSH10x20
19b1MSTORE
19b2PUSH10x40
19b4PUSH0
19b5KECCAK256
19b6SSTORE
19b7ADD
19b8PUSH20x17fd
19bbJUMP
19bcJUMPDEST
19bdSWAP1
19beSWAP2
19bfPUSH20x19d1
19c2SWAP1
19c3PUSH20x0d08
19c6PUSH10x01
19c8DUP6
19c9XOR
19caDUP5
19cbPUSH10x01
19cdPUSH20x695b
19d0JUMP
19d1JUMPDEST
19d2SWAP2
19d3PUSH10x01
19d5SHR
19d6SWAP1
19d7PUSH10x01
19d9PUSH0
19daMSTORE
19dbPUSH10x03
19ddPUSH10x20
19dfMSTORE
19e0PUSH10x40
19e2PUSH0
19e3KECCAK256
19e4SWAP1
19e5PUSH10x01
19e7DUP2
19e8ADD
19e9SWAP2
19eaDUP3
19ebDUP3
19ecGT