Final Testnetexplorer K_J · Final Testnet · 48359
en

Contract

0x70b4f3c06e5d93d695129f1255c55c01e7be13bf

Address
0x70b4f3c06e5d93d695129f1255c55c01e7be13bf
Kind
verified contract FinalIdentityRegistry
Balance
0 vETH
Nonce
1
Code
25,865 bytes codehash 0x4a3fe8ecf743a798c8a19c8dbd0bf927e549721145108e20fb8adfffdc20b058

account tree

Tree
1 · accounts
Present
no leaf
Key
0xb78bf56e1cbe256a715defd818c5657a6d68c2b7fe629c97121856d34831aec8
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
FinalIdentityRegistry exact match
Compiler
v0.8.33+commit.64118f21
Optimizer
enabled · 200 runs
EVM version
prague
Verified
2026-09-10T07:09:52.752Z
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/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/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": "admin",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "DOMAIN_IDENTITY_ADMISSION",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_ISSUER_CERT_REVOCATION",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_REGISTER_ISSUER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_REGISTER_LMS_KEY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_REGISTER_WALLET",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_REVOKE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_REVOKE_CERTIFICATE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_SET_REGISTRAR_THRESHOLD",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_SET_ROLES",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "MAX_ISSUER_VALIDITY_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_ACCOUNT_COSIGNER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_CERTIFICATE_AUTHORITY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_GUARDIAN",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_MMR_COSIGNER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_ORACLE_PUBLISHER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_PHI_PUBLISHER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_REGISTRAR",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_REGISTRY_PUBLISHER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_RELAYER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_SETTLEMENT_COSIGNER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ROLE_VASSET_PUBLISHER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountAt",
    "inputs": [
      {
        "name": "index",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountCount",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountOfCertificate",
    "inputs": [
      {
        "name": "certHash",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountOfSender",
    "inputs": [
      {
        "name": "sender",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountsWithRole",
    "inputs": [
      {
        "name": "roleMask",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "found",
        "type": "address[]",
        "internalType": "address[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "activeAccessKeyOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "activeSealKeyOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "activeTransactionKeyOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "bootstrapAdmin",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "bootstrapSealed",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "certificateRevoked",
    "inputs": [
      {
        "name": "certHash",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "certificateRevokedBy",
    "inputs": [
      {
        "name": "certHash",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "gateNonceOf",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "hasRole",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "roleMask",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "identityOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalIdentityRegistry.Identity",
        "components": [
          {
            "name": "certHash",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "recoveryCertHash",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "serial",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "subjectKeyId",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "roles",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "depth",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "maxDelegationDepth",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "notBefore",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "notAfter",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "version",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "revoked",
            "type": "bool",
            "internalType": "bool"
          },
          {
            "name": "registered",
            "type": "bool",
            "internalType": "bool"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "identityTreeLeafOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "isActive",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "kemCommitments",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "liveKem",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "recoveryKem",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "kemKeysOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "activeMlKem",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "activeHqc",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "keyCommitments",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "liveAccess",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "liveTransaction",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "recoveryAccess",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "recoveryTransaction",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "liveMemberCount",
    "inputs": [
      {
        "name": "roleMask",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "live",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "lmsBindingOf",
    "inputs": [
      {
        "name": "signerId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "lmsKeyOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "internalType": "struct FinalIdentityRegistry.LmsKey",
        "components": [
          {
            "name": "keyId",
            "type": "bytes16",
            "internalType": "bytes16"
          },
          {
            "name": "height",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "root",
            "type": "bytes32",
            "internalType": "bytes32"
          },
          {
            "name": "version",
            "type": "uint64",
            "internalType": "uint64"
          },
          {
            "name": "registered",
            "type": "bool",
            "internalType": "bool"
          }
        ]
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "lmsSignerId",
    "inputs": [
      {
        "name": "keyId",
        "type": "bytes16",
        "internalType": "bytes16"
      },
      {
        "name": "height",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "root",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "lmsSignerIsLive",
    "inputs": [
      {
        "name": "signerId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "live",
        "type": "bool",
        "internalType": "bool"
      },
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "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": "recoveryAccessKeyOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "recoveryTransactionKeyOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registerIssuer",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "tbs",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "parent",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "proof",
        "type": "tuple",
        "internalType": "struct FinalIdentityRegistry.AdmissionProof",
        "components": [
          {
            "name": "mlDsaSignature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "slhDsaSignature",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      },
      {
        "name": "roles",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "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": [
      {
        "name": "certHash",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "registerLmsKey",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "keyId",
        "type": "bytes16",
        "internalType": "bytes16"
      },
      {
        "name": "height",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "root",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "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": "registerWallet",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "liveTbs",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "recoveryTbs",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "proof",
        "type": "tuple",
        "internalType": "struct FinalIdentityRegistry.AdmissionProof",
        "components": [
          {
            "name": "mlDsaSignature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "slhDsaSignature",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      },
      {
        "name": "roles",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "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": [
      {
        "name": "certHash",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "registrarThreshold",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "requireRegistrarQuorum",
    "inputs": [
      {
        "name": "actionDomain",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "payloadDigest",
        "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": "revocationLog",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "revoke",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "chainIds",
        "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": "revokeCertificate",
    "inputs": [
      {
        "name": "certHash",
        "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": "revokeIssuedCertificate",
    "inputs": [
      {
        "name": "issuer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "certHash",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "proof",
        "type": "tuple",
        "internalType": "struct FinalIdentityRegistry.AdmissionProof",
        "components": [
          {
            "name": "mlDsaSignature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "slhDsaSignature",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "sealBootstrap",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "sealableMemberCount",
    "inputs": [
      {
        "name": "roleMask",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "sealable",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "senderFor",
    "inputs": [
      {
        "name": "transactionKey",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "senderHasRole",
    "inputs": [
      {
        "name": "sender",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "roleMask",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "senderOf",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "setRegistrarThreshold",
    "inputs": [
      {
        "name": "threshold",
        "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": "setRoles",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "roles",
        "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": "stateTrees",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "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": "wireStatePlane",
    "inputs": [
      {
        "name": "stateTrees_",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "revocationLog_",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "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": "BootstrapSealed",
    "inputs": [
      {
        "name": "sealedBy",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "CertificateRevoked",
    "inputs": [
      {
        "name": "certHash",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "revoker",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "IdentityRegistered",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "certHash",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "roles",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "version",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "IdentityRevoked",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "certHash",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "IdentityRolesChanged",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "previousRoles",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "newRoles",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "LmsKeyRegistered",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "signerId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "chainId",
        "type": "uint64",
        "indexed": true,
        "internalType": "uint64"
      },
      {
        "name": "keyId",
        "type": "bytes16",
        "indexed": false,
        "internalType": "bytes16"
      },
      {
        "name": "height",
        "type": "uint8",
        "indexed": false,
        "internalType": "uint8"
      },
      {
        "name": "root",
        "type": "bytes32",
        "indexed": false,
        "internalType": "bytes32"
      },
      {
        "name": "version",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "RegistrarQuorumApproved",
    "inputs": [
      {
        "name": "verifyingContract",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "actionDomain",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "nonce",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "valid",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "RegistrarThresholdSet",
    "inputs": [
      {
        "name": "threshold",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "StatePlaneWired",
    "inputs": [
      {
        "name": "stateTrees",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      },
      {
        "name": "revocationLog",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "error",
    "name": "AdmissionProofInvalid",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "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": "AuthorityKeyIdMismatch",
    "inputs": [
      {
        "name": "got",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "want",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSeal",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSignature",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "algorithm",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "BootstrapAlreadySealed",
    "inputs": []
  },
  {
    "type": "error",
    "name": "CertificateAlreadyBound",
    "inputs": [
      {
        "name": "certHash",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "boundTo",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "CertificateIsRevoked",
    "inputs": [
      {
        "name": "certHash",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "DelegationWidened",
    "inputs": [
      {
        "name": "child",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "issuer",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "IssuerCannotSign",
    "inputs": [
      {
        "name": "depth",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "maxDelegationDepth",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "IssuerMayNotSign",
    "inputs": [
      {
        "name": "issuer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "depth",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "maxDelegationDepth",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "IssuerMustExpire",
    "inputs": []
  },
  {
    "type": "error",
    "name": "IssuerNotACertificateAuthority",
    "inputs": [
      {
        "name": "issuer",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "IssuerValidityTooLong",
    "inputs": [
      {
        "name": "notBefore",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "notAfter",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "JurisdictionMismatch",
    "inputs": []
  },
  {
    "type": "error",
    "name": "JurisdictionMissing",
    "inputs": []
  },
  {
    "type": "error",
    "name": "LmsHeightOutOfRange",
    "inputs": [
      {
        "name": "height",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "LmsKeyAlreadyBound",
    "inputs": [
      {
        "name": "signerId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "boundTo",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "LmsRootIsZero",
    "inputs": []
  },
  {
    "type": "error",
    "name": "MalformedEncapsulationKey",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "algorithmId",
        "type": "uint16",
        "internalType": "uint16"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAnEndEntity",
    "inputs": [
      {
        "name": "depth",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "maxDelegationDepth",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAuthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotChainAttested",
    "inputs": [
      {
        "name": "authorityKeyId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "PrecompileUnavailable",
    "inputs": [
      {
        "name": "precompile",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "RegistrarThresholdIsZero",
    "inputs": []
  },
  {
    "type": "error",
    "name": "RegistrarThresholdUnreachable",
    "inputs": [
      {
        "name": "sealable",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "threshold",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SenderAlreadyBound",
    "inputs": [
      {
        "name": "sender",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "boundTo",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "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": "StagesDisagree",
    "inputs": [
      {
        "name": "liveSerial",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "recoverySerial",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "StatePlaneAlreadyWired",
    "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": "UnknownAccount",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "VersionNotNewer",
    "inputs": [
      {
        "name": "current",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "offered",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "WrongAlgorithm",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "got",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "required",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "WrongDepth",
    "inputs": [
      {
        "name": "got",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "want",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "WrongIssuerDn",
    "inputs": [
      {
        "name": "issuerDnHash",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "ZeroStatePlane",
    "inputs": []
  }
]

read contract

bytecode · 25,865 bytes

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

disassembly (first 4,000 ops)

pcopoperand
0000PUSH10x80
0002DUP1
0003PUSH10x40
0005MSTORE
0006PUSH10x04
0008CALLDATASIZE
0009LT
000aISZERO
000bPUSH20x0012
000eJUMPI
000fPUSH0
0010DUP1
0011REVERT
0012JUMPDEST
0013PUSH0
0014SWAP1
0015PUSH0
0016CALLDATALOAD
0017PUSH10xe0
0019SHR
001aSWAP1
001bDUP2
001cPUSH40x0408a87f
0021EQ
0022PUSH20x3f1f
0025JUMPI
0026POP
0027DUP1
0028PUSH40x04525da8
002dEQ
002ePUSH20x3ef7
0031JUMPI
0032DUP1
0033PUSH40x0d641df0
0038EQ
0039PUSH20x3e2b
003cJUMPI
003dDUP1
003ePUSH40x1479bc3a
0043EQ
0044PUSH20x3d55
0047JUMPI
0048DUP1
0049PUSH40x150b7a02
004eEQ
004fPUSH20x3cff
0052JUMPI
0053DUP1
0054PUSH40x15ba3c1d
0059EQ
005aPUSH20x3ce4
005dJUMPI
005eDUP1
005fPUSH40x1d4f1077
0064EQ
0065PUSH20x3ba6
0068JUMPI
0069DUP1
006aPUSH40x1f394fb3
006fEQ
0070PUSH20x3b71
0073JUMPI
0074DUP1
0075PUSH40x1f9dc0bb
007aEQ
007bPUSH20x3989
007eJUMPI
007fDUP1
0080PUSH40x209f8044
0085EQ
0086PUSH20x396e
0089JUMPI
008aDUP1
008bPUSH40x222e6f0e
0090EQ
0091PUSH20x393f
0094JUMPI
0095DUP1
0096PUSH40x228bf014
009bEQ
009cPUSH20x3924
009fJUMPI
00a0DUP1
00a1PUSH40x2741cb02
00a6EQ
00a7PUSH20x36fd
00aaJUMPI
00abDUP1
00acPUSH40x2ca02983
00b1EQ
00b2PUSH20x3653
00b5JUMPI
00b6DUP1
00b7PUSH40x3222383e
00bcEQ
00bdPUSH20x361b
00c0JUMPI
00c1DUP1
00c2PUSH40x32596d71
00c7EQ
00c8PUSH20x35fe
00cbJUMPI
00ccDUP1
00cdPUSH40x342f6163
00d2EQ
00d3PUSH20x358c
00d6JUMPI
00d7DUP1
00d8PUSH40x45e7e88e
00ddEQ
00dePUSH20x3543
00e1JUMPI
00e2DUP1
00e3PUSH40x461de6c1
00e8EQ
00e9PUSH20x3509
00ecJUMPI
00edDUP1
00eePUSH40x488429d9
00f3EQ
00f4PUSH20x34cf
00f7JUMPI
00f8DUP1
00f9PUSH40x4c4af1a4
00feEQ
00ffPUSH20x313e
0102JUMPI
0103DUP1
0104PUSH40x4ccf0005
0109EQ
010aPUSH20x30a5
010dJUMPI
010eDUP1
010fPUSH40x4f8d3216
0114EQ
0115PUSH20x3031
0118JUMPI
0119DUP1
011aPUSH40x5a232a39
011fEQ
0120PUSH20x2ff7
0123JUMPI
0124DUP1
0125PUSH40x5b882501
012aEQ
012bPUSH20x2ec4
012eJUMPI
012fDUP1
0130PUSH40x5c97f4a2
0135EQ
0136PUSH20x2e9d
0139JUMPI
013aDUP1
013bPUSH40x60a18008
0140EQ
0141PUSH20x2e69
0144JUMPI
0145DUP1
0146PUSH40x61dbd452
014bEQ
014cPUSH20x2e2f
014fJUMPI
0150DUP1
0151PUSH40x64d2218d
0156EQ
0157PUSH20x2e10
015aJUMPI
015bDUP1
015cPUSH40x70f64f0a
0161EQ
0162PUSH20x2dc5
0165JUMPI
0166DUP1
0167PUSH40x77e223b0
016cEQ
016dPUSH20x2daa
0170JUMPI
0171DUP1
0172PUSH40x86731755
0177EQ
0178PUSH20x2d70
017bJUMPI
017cDUP1
017dPUSH40x8c6820a9
0182EQ
0183PUSH20x1e0d
0186JUMPI
0187DUP1
0188PUSH40x8cfc6aa0
018dEQ
018ePUSH20x1ddb
0191JUMPI
0192DUP1
0193PUSH40x90ecddbf
0198EQ
0199PUSH20x1d9a
019cJUMPI
019dDUP1
019ePUSH40x926590d6
01a3EQ
01a4PUSH20x1cd9
01a7JUMPI
01a8DUP1
01a9PUSH40x94338765
01aeEQ
01afPUSH20x1c97
01b2JUMPI
01b3DUP1
01b4PUSH40x96f51f3a
01b9EQ
01baPUSH20x1a2e
01bdJUMPI
01beDUP1
01bfPUSH40x9aae87ad
01c4EQ
01c5PUSH20x19f4
01c8JUMPI
01c9DUP1
01caPUSH40x9c11e31b
01cfEQ
01d0PUSH20x19d9
01d3JUMPI
01d4DUP1
01d5PUSH40x9d07ae09
01daEQ
01dbPUSH20x18b1
01deJUMPI
01dfDUP1
01e0PUSH40x9e5adaeb
01e5EQ
01e6PUSH20x1879
01e9JUMPI
01eaDUP1
01ebPUSH40x9f8a13d7
01f0EQ
01f1PUSH20x184c
01f4JUMPI
01f5DUP1
01f6PUSH40xa0c176c4
01fbEQ
01fcPUSH20x1827
01ffJUMPI
0200DUP1
0201PUSH40xa760c295
0206EQ
0207PUSH20x17f5
020aJUMPI
020bDUP1
020cPUSH40xa874eea5
0211EQ
0212PUSH20x17da
0215JUMPI
0216DUP1
0217PUSH40xad20445d
021cEQ
021dPUSH20x17c0
0220JUMPI
0221DUP1
0222PUSH40xad84ad13
0227EQ
0228PUSH20x1788
022bJUMPI
022cDUP1
022dPUSH40xb0538d5a
0232EQ
0233PUSH20x176d
0236JUMPI
0237DUP1
0238PUSH40xb16abc6c
023dEQ
023ePUSH20x1735
0241JUMPI
0242DUP1
0243PUSH40xb3c26283
0248EQ
0249PUSH20x16d4
024cJUMPI
024dDUP1
024ePUSH40xb7af85d7
0253EQ
0254PUSH20x1684
0257JUMPI
0258DUP1
0259PUSH40xbaadbb32
025eEQ
025fPUSH20x164a
0262JUMPI
0263DUP1
0264PUSH40xbc197c81
0269EQ
026aPUSH20x15b2
026dJUMPI
026eDUP1
026fPUSH40xc0716729
0274EQ
0275PUSH20x1596
0278JUMPI
0279DUP1
027aPUSH40xc177a97e
027fEQ
0280PUSH20x1573
0283JUMPI
0284DUP1
0285PUSH40xc5768fe4
028aEQ
028bPUSH20x154b
028eJUMPI
028fDUP1
0290PUSH40xc6345626
0295EQ
0296PUSH20x1388
0299JUMPI
029aDUP1
029bPUSH40xcbe6ebb4
02a0EQ
02a1PUSH20x0704
02a4JUMPI
02a5DUP1
02a6PUSH40xce092d1c
02abEQ
02acPUSH20x06e7
02afJUMPI
02b0DUP1
02b1PUSH40xde043e95
02b6EQ
02b7PUSH20x06ac
02baJUMPI
02bbDUP1
02bcPUSH40xe0cd096a
02c1EQ
02c2PUSH20x0680
02c5JUMPI
02c6DUP1
02c7PUSH40xe14c465b
02ccEQ
02cdPUSH20x0663
02d0JUMPI
02d1DUP1
02d2PUSH40xe4af29fc
02d7EQ
02d8PUSH20x0645
02dbJUMPI
02dcDUP1
02ddPUSH40xe51442e3
02e2EQ
02e3PUSH20x060d
02e6JUMPI
02e7DUP1
02e8PUSH40xec964baa
02edEQ
02eePUSH20x0542
02f1JUMPI
02f2DUP1
02f3PUSH40xef23f9f8
02f8EQ
02f9PUSH20x0501
02fcJUMPI
02fdDUP1
02fePUSH40xf23a6e61
0303EQ
0304PUSH20x04a6
0307JUMPI
0308DUP1
0309PUSH40xf5778b03
030eEQ
030fPUSH20x047d
0312JUMPI
0313DUP1
0314PUSH40xfce072f8
0319EQ
031aPUSH20x0420
031dJUMPI
031ePUSH40xffd326f3
0323EQ
0324PUSH20x032b
0327JUMPI
0328PUSH0
0329DUP1
032aREVERT
032bJUMPDEST
032cCALLVALUE
032dPUSH20x041d
0330JUMPI
0331PUSH10x40
0333CALLDATASIZE
0334PUSH10x03
0336NOT
0337ADD
0338SLT
0339PUSH20x041d
033cJUMPI
033dPUSH10x01
033fPUSH10x01
0341PUSH10x40
0343SHL
0344SUB
0345PUSH10x40
0347PUSH20x034e
034aPUSH20x4021
034dJUMP
034eJUMPDEST
034fSWAP3
0350PUSH20x0357
0353PUSH20x3f57
0356JUMP
0357JUMPDEST
0358SWAP4
0359DUP2
035aPUSH10x80
035cDUP5
035dMLOAD
035ePUSH20x0366
0361DUP2
0362PUSH20x409e
0365JUMP
0366JUMPDEST
0367DUP3
0368DUP2
0369MSTORE
036aDUP3
036bPUSH10x20
036dDUP3
036eADD
036fMSTORE
0370DUP3
0371DUP7
0372DUP3
0373ADD
0374MSTORE
0375DUP3
0376PUSH10x60
0378DUP3
0379ADD
037aMSTORE
037bADD
037cMSTORE
037dPUSH10x01
037fDUP1
0380PUSH10xa0
0382SHL
0383SUB
0384AND
0385DUP2
0386MSTORE
0387DUP1
0388PUSH10x20
038aMSTORE
038bKECCAK256
038cSWAP2
038dAND
038ePUSH0
038fMSTORE
0390PUSH10x20
0392MSTORE
0393PUSH10xa0
0395PUSH10x40
0397PUSH0
0398KECCAK256
0399PUSH10x40
039bMLOAD
039cPUSH20x03a4
039fDUP2
03a0PUSH20x409e
03a3JUMP
03a4JUMPDEST
03a5PUSH10x01
03a7PUSH10x01
03a9PUSH10x40
03abSHL
03acSUB
03adDUP3
03aeSLOAD
03afSWAP2
03b0PUSH10x01
03b2PUSH10x01
03b4PUSH10x80
03b6SHL
03b7SUB
03b8NOT
03b9DUP4
03baPUSH10x80
03bcSHL
03bdAND
03beSWAP4
03bfDUP5
03c0DUP3
03c1MSTORE
03c2PUSH10xff
03c4PUSH10x20
03c6DUP4
03c7ADD
03c8SWAP5
03c9PUSH10x80
03cbSHR
03ccAND
03cdDUP5
03ceMSTORE
03cfPUSH10xff
03d1PUSH10x02
03d3PUSH10x01
03d5DUP4
03d6ADD
03d7SLOAD
03d8SWAP3
03d9PUSH10x40
03dbDUP6
03dcADD
03ddSWAP4
03deDUP5
03dfMSTORE
03e0ADD
03e1SLOAD
03e2SWAP5
03e3DUP2
03e4PUSH10x80
03e6PUSH10x60
03e8DUP7
03e9ADD
03eaSWAP6
03ebDUP8
03ecDUP10
03edAND
03eeDUP8
03efMSTORE
03f0ADD
03f1SWAP7
03f2PUSH10x40
03f4SHR
03f5AND
03f6ISZERO
03f7ISZERO
03f8DUP7
03f9MSTORE
03faPUSH10x40
03fcMLOAD
03fdSWAP7
03feDUP8
03ffMSTORE
0400MLOAD
0401AND
0402PUSH10x20
0404DUP7
0405ADD
0406MSTORE
0407MLOAD
0408PUSH10x40
040aDUP6
040bADD
040cMSTORE
040dMLOAD
040eAND
040fPUSH10x60
0411DUP4
0412ADD
0413MSTORE
0414MLOAD
0415ISZERO
0416ISZERO
0417PUSH10x80
0419DUP3
041aADD
041bMSTORE
041cRETURN
041dJUMPDEST
041eDUP1
041fREVERT
0420JUMPDEST
0421POP
0422CALLVALUE
0423PUSH20x041d
0426JUMPI
0427PUSH10x20
0429CALLDATASIZE
042aPUSH10x03
042cNOT
042dADD
042eSLT
042fPUSH20x041d
0432JUMPI
0433PUSH10x04
0435CALLDATALOAD
0436SWAP1
0437PUSH10x01
0439PUSH10x01
043bPUSH10x40
043dSHL
043eSUB
043fDUP3
0440GT
0441PUSH20x041d
0444JUMPI
0445CALLDATASIZE
0446PUSH10x23
0448DUP4
0449ADD
044aSLT
044bISZERO
044cPUSH20x041d
044fJUMPI
0450PUSH10x20
0452PUSH20x046b
0455PUSH20x0466
0458CALLDATASIZE
0459PUSH10x04
045bDUP7
045cADD
045dCALLDATALOAD
045ePUSH10x24
0460DUP8
0461ADD
0462PUSH20x4111
0465JUMP
0466JUMPDEST
0467PUSH20x4bd8
046aJUMP
046bJUMPDEST
046cPUSH10x40
046eMLOAD
046fPUSH10x01
0471PUSH10x01
0473PUSH10xa0
0475SHL
0476SUB
0477SWAP1
0478SWAP2
0479AND
047aDUP2
047bMSTORE
047cRETURN
047dJUMPDEST
047ePOP
047fCALLVALUE
0480PUSH20x041d
0483JUMPI
0484DUP1
0485PUSH10x03
0487NOT
0488CALLDATASIZE
0489ADD
048aSLT
048bPUSH20x041d
048eJUMPI
048fPUSH10x10
0491SLOAD
0492PUSH10x40
0494MLOAD
0495PUSH10x01
0497PUSH10x01
0499PUSH10xa0
049bSHL
049cSUB
049dSWAP1
049eSWAP2
049fAND
04a0DUP2
04a1MSTORE
04a2PUSH10x20
04a4SWAP1
04a5RETURN
04a6JUMPDEST
04a7POP
04a8CALLVALUE
04a9PUSH20x041d
04acJUMPI
04adPUSH10xa0
04afCALLDATASIZE
04b0PUSH10x03
04b2NOT
04b3ADD
04b4SLT
04b5PUSH20x041d
04b8JUMPI
04b9PUSH20x04c0
04bcPUSH20x4021
04bfJUMP
04c0JUMPDEST
04c1POP
04c2PUSH20x04c9
04c5PUSH20x4037
04c8JUMP
04c9JUMPDEST
04caPOP
04cbPUSH10x84
04cdCALLDATALOAD
04cePUSH10x01
04d0PUSH10x01
04d2PUSH10x40
04d4SHL
04d5SUB
04d6DUP2
04d7GT
04d8PUSH20x04fd
04dbJUMPI
04dcPUSH20x04e9
04dfSWAP1
04e0CALLDATASIZE
04e1SWAP1
04e2PUSH10x04
04e4ADD
04e5PUSH20x404d
04e8JUMP
04e9JUMPDEST
04eaPOP
04ebPOP
04ecPUSH10x40
04eeMLOAD
04efPUSH40xf23a6e61
04f4PUSH10xe0
04f6SHL
04f7DUP2
04f8MSTORE
04f9PUSH10x20
04fbSWAP1
04fcRETURN
04fdJUMPDEST
04fePOP
04ffDUP1
0500REVERT
0501JUMPDEST
0502POP
0503CALLVALUE
0504PUSH20x041d
0507JUMPI
0508PUSH10x20
050aCALLDATASIZE
050bPUSH10x03
050dNOT
050eADD
050fSLT
0510PUSH20x041d
0513JUMPI
0514PUSH10x20
0516SWAP1
0517PUSH10x01
0519PUSH10x01
051bPUSH10xa0
051dSHL
051eSUB
051fPUSH20x0526
0522PUSH20x4021
0525JUMP
0526JUMPDEST
0527AND
0528DUP2
0529MSTORE
052aPUSH10x15
052cDUP3
052dMSTORE
052ePUSH10x40
0530PUSH10x01
0532DUP1
0533PUSH10xa0
0535SHL
0536SUB
0537SWAP2
0538KECCAK256
0539SLOAD
053aAND
053bPUSH10x40
053dMLOAD
053eSWAP1
053fDUP2
0540MSTORE
0541RETURN
0542JUMPDEST
0543POP
0544CALLVALUE
0545PUSH20x041d
0548JUMPI
0549DUP1
054aPUSH10x03
054cNOT
054dCALLDATASIZE
054eADD
054fSLT
0550PUSH20x041d
0553JUMPI
0554PUSH10x10
0556SLOAD
0557PUSH10x01
0559PUSH10x01
055bPUSH10xa0
055dSHL
055eSUB
055fDUP2
0560AND
0561CALLER
0562SUB
0563PUSH20x05fa
0566JUMPI
0567PUSH10xff
0569DUP2
056aPUSH10xa0
056cSHR
056dAND
056ePUSH20x05eb
0571JUMPI
0572PUSH10x13
0574SLOAD
0575DUP1
0576ISZERO
0577PUSH20x05dc
057aJUMPI
057bPUSH20x0582
057ePUSH20x4394
0581JUMP
0582JUMPDEST
0583DUP2
0584DUP2
0585LT
0586PUSH20x05c6
0589JUMPI
058aPOP
058bPOP
058cPUSH10x01
058ePUSH10x01
0590PUSH10xa8
0592SHL
0593SUB
0594NOT
0595AND
0596PUSH10x01
0598PUSH10xa0
059aSHL
059bOR
059cPUSH10x10
059eSSTORE
059fCALLER
05a0PUSH320xdd5280e26bdef754c085fd1fe40c0ab76df98368bb6014f85b49f8852a81be57
05c1DUP3
05c2DUP1
05c3LOG2
05c4DUP1
05c5RETURN
05c6JUMPDEST
05c7PUSH40x04951c21
05ccPUSH10xe1
05ceSHL
05cfDUP5
05d0MSTORE
05d1PUSH10x04
05d3MSTORE
05d4PUSH10x24
05d6MSTORE
05d7POP
05d8PUSH10x44
05daSWAP1
05dbREVERT
05dcJUMPDEST
05ddPUSH40x1de31ea5
05e2PUSH10xe1
05e4SHL
05e5DUP4
05e6MSTORE
05e7PUSH10x04
05e9DUP4
05eaREVERT
05ebJUMPDEST
05ecPUSH40x0f80bc05
05f1PUSH10xe3
05f3SHL
05f4DUP3
05f5MSTORE
05f6PUSH10x04
05f8DUP3
05f9REVERT
05faJUMPDEST
05fbPUSH40x4a0bfec1
0600PUSH10xe0
0602SHL
0603DUP3
0604MSTORE
0605CALLER
0606PUSH10x04
0608MSTORE
0609PUSH10x24
060bDUP3
060cREVERT
060dJUMPDEST
060ePOP
060fCALLVALUE
0610PUSH20x041d
0613JUMPI
0614PUSH10x20
0616CALLDATASIZE
0617PUSH10x03
0619NOT
061aADD
061bSLT
061cPUSH20x041d
061fJUMPI
0620PUSH10x20
0622PUSH20x062c
0625PUSH10x04
0627CALLDATALOAD
0628PUSH20x4190
062bJUMP
062cJUMPDEST
062dSWAP1
062eSLOAD
062fPUSH10x40
0631MLOAD
0632PUSH10x03
0634SWAP3
0635SWAP1
0636SWAP3
0637SHL
0638SHR
0639PUSH10x01
063bPUSH10x01
063dPUSH10xa0
063fSHL
0640SUB
0641AND
0642DUP2
0643MSTORE
0644RETURN
0645JUMPDEST
0646POP
0647CALLVALUE
0648PUSH20x041d
064bJUMPI
064cDUP1
064dPUSH10x03
064fNOT
0650CALLDATASIZE
0651ADD
0652SLT
0653PUSH20x041d
0656JUMPI
0657PUSH10x20
0659PUSH10x0f
065bSLOAD
065cPUSH10x40
065eMLOAD
065fSWAP1
0660DUP2
0661MSTORE
0662RETURN
0663JUMPDEST
0664POP
0665CALLVALUE
0666PUSH20x041d
0669JUMPI
066aDUP1
066bPUSH10x03
066dNOT
066eCALLDATASIZE
066fADD
0670SLT
0671PUSH20x041d
0674JUMPI
0675PUSH10x20
0677PUSH10x40
0679MLOAD
067aPUSH20x0100
067dDUP2
067eMSTORE
067fRETURN
0680JUMPDEST
0681POP
0682CALLVALUE
0683PUSH20x041d
0686JUMPI
0687PUSH10x20
0689CALLDATASIZE
068aPUSH10x03
068cNOT
068dADD
068eSLT
068fPUSH20x041d
0692JUMPI
0693PUSH10x20
0695PUSH20x06a4
0698PUSH20x069f
069bPUSH20x4021
069eJUMP
069fJUMPDEST
06a0PUSH20x4980
06a3JUMP
06a4JUMPDEST
06a5PUSH10x40
06a7MLOAD
06a8SWAP1
06a9DUP2
06aaMSTORE
06abRETURN
06acJUMPDEST
06adPOP
06aeCALLVALUE
06afPUSH20x041d
06b2JUMPI
06b3DUP1
06b4PUSH10x03
06b6NOT
06b7CALLDATASIZE
06b8ADD
06b9SLT
06baPUSH20x041d
06bdJUMPI
06bePUSH10x20
06c0PUSH10x40
06c2MLOAD
06c3PUSH320x664b0e72d3e91e88f8d9d2c0d916d51aff408110b8760394f6d677a5e6b24a2e
06e4DUP2
06e5MSTORE
06e6RETURN
06e7JUMPDEST
06e8POP
06e9CALLVALUE
06eaPUSH20x041d
06edJUMPI
06eeDUP1
06efPUSH10x03
06f1NOT
06f2CALLDATASIZE
06f3ADD
06f4SLT
06f5PUSH20x041d
06f8JUMPI
06f9PUSH10x20
06fbPUSH10x40
06fdMLOAD
06fePUSH20x0200
0701DUP2
0702MSTORE
0703RETURN
0704JUMPDEST
0705CALLVALUE
0706PUSH20x1384
0709JUMPI
070aPUSH20x0100
070dCALLDATASIZE
070ePUSH10x03
0710NOT
0711ADD
0712SLT
0713PUSH20x1384
0716JUMPI
0717PUSH20x071e
071aPUSH20x4021
071dJUMP
071eJUMPDEST
071fPUSH10x24
0721CALLDATALOAD
0722PUSH10x01
0724PUSH10x01
0726PUSH10x40
0728SHL
0729SUB
072aDUP2
072bGT
072cPUSH20x1384
072fJUMPI
0730PUSH20x073d
0733SWAP1
0734CALLDATASIZE
0735SWAP1
0736PUSH10x04
0738ADD
0739PUSH20x404d
073cJUMP
073dJUMPDEST
073eSWAP1
073fSWAP2
0740PUSH10x44
0742CALLDATALOAD
0743PUSH10x01
0745PUSH10x01
0747PUSH10x40
0749SHL
074aSUB
074bDUP2
074cGT
074dPUSH20x1384
0750JUMPI
0751PUSH20x075e
0754SWAP1
0755CALLDATASIZE
0756SWAP1
0757PUSH10x04
0759ADD
075aPUSH20x404d
075dJUMP
075eJUMPDEST
075fPUSH10x64
0761SWAP3
0762SWAP2
0763SWAP3
0764CALLDATALOAD
0765SWAP4
0766PUSH10x01
0768PUSH10x01
076aPUSH10x40
076cSHL
076dSUB
076eDUP6
076fGT
0770PUSH20x1384
0773JUMPI
0774DUP5
0775PUSH10x04
0777ADD
0778SWAP4
0779PUSH10x40
077bPUSH10x03
077dNOT
077eDUP8
077fCALLDATASIZE
0780SUB
0781ADD
0782SLT
0783PUSH20x1384
0786JUMPI
0787PUSH10x84
0789CALLDATALOAD
078aSWAP6
078bPUSH20x0792
078ePUSH20x3f83
0791JUMP
0792JUMPDEST
0793SWAP6
0794PUSH20x079b
0797PUSH20x3f99
079aJUMP
079bJUMPDEST
079cSWAP4
079dPUSH10xe4
079fCALLDATALOAD
07a0SWAP6
07a1PUSH10x01
07a3PUSH10x01
07a5PUSH10x40
07a7SHL
07a8SUB
07a9DUP8
07aaGT
07abPUSH20x1384
07aeJUMPI
07afDUP10
07b0SWAP7
07b1PUSH20x07be
07b4SWAP1
07b5CALLDATASIZE
07b6SWAP1
07b7PUSH10x04
07b9ADD
07baPUSH20x3faf
07bdJUMP
07beJUMPDEST
07bfSWAP3
07c0SWAP1
07c1SWAP7
07c2ADDRESS
07c3PUSH0
07c4MSTORE
07c5PUSH10x14
07c7PUSH10x20
07c9MSTORE
07caPUSH10x40
07ccPUSH0
07cdKECCAK256
07ceSLOAD
07cfPUSH10x01
07d1PUSH10x01
07d3PUSH10x40
07d5SHL
07d6SUB
07d7AND
07d8SWAP4
07d9DUP14
07daDUP4
07dbCALLDATASIZE
07dcSWAP1
07ddPUSH20x07e5
07e0SWAP3
07e1PUSH20x4111
07e4JUMP
07e5JUMPDEST
07e6DUP1
07e7MLOAD
07e8SWAP1
07e9PUSH10x20
07ebADD
07ecKECCAK256
07edSWAP9
07eeDUP12
07efDUP14
07f0CALLDATASIZE
07f1PUSH20x07fb
07f4SWAP1
07f5DUP9
07f6DUP14
07f7PUSH20x4111
07faJUMP
07fbJUMPDEST
07fcDUP1
07fdMLOAD
07feSWAP1
07ffPUSH10x20
0801ADD
0802KECCAK256
0803SWAP13
0804PUSH10x40
0806MLOAD
0807DUP1
0808SWAP15
0809PUSH10x20
080bDUP3
080cADD
080dSWAP5
080ePUSH10x01
0810PUSH10xa0
0812SHL
0813PUSH10x01
0815SWAP1
0816SUB
0817AND
0818SWAP15
0819DUP16
081aDUP7
081bMSTORE
081cPUSH10x40
081eDUP4
081fADD
0820MSTORE
0821PUSH10x60
0823DUP3
0824ADD
0825MSTORE
0826PUSH10x80
0828ADD
0829MSTORE
082aPUSH10x01
082cPUSH10x01
082ePUSH10x40
0830SHL
0831SUB
0832AND
0833SWAP12
0834DUP13
0835PUSH10xa0
0837DUP3
0838ADD
0839MSTORE
083aPUSH10xa0
083cDUP2
083dMSTORE
083ePUSH20x0848
0841PUSH10xc0
0843DUP3
0844PUSH20x40d5
0847JUMP
0848JUMPDEST
0849MLOAD
084aSWAP1
084bKECCAK256
084cSWAP3
084dPUSH20x0855
0850SWAP4
0851PUSH20x4e35
0854JUMP
0855JUMPDEST
0856PUSH200x5a7be3e057b3cf85dbf4e8d79208c6a5343e9c49
086bDUP1
086cSWAP2
086dPUSH10x40
086fMLOAD
0870DUP1
0871SWAP15
0872DUP2
0873SWAP3
0874PUSH40x25f1701d
0879PUSH10xe2
087bSHL
087cDUP4
087dMSTORE
087ePUSH10x04
0880DUP4
0881ADD
0882SWAP2
0883PUSH20x088b
0886SWAP3
0887PUSH20x47ab
088aJUMP
088bJUMPDEST
088cSUB
088dDUP2
088eGAS
088fSWAP4
0890PUSH0
0891SWAP5
0892DELEGATECALL
0893SWAP6
0894DUP7
0895ISZERO
0896PUSH20x1357
0899JUMPI
089aPUSH20x08c5
089dSWAP13
089ePUSH0
089fSWAP8
08a0PUSH20x1362
08a3JUMPI
08a4JUMPDEST
08a5POP
08a6SWAP1
08a7PUSH0
08a8SWAP3
08a9SWAP2
08aaPUSH10x40
08acMLOAD
08adDUP1
08aeSWAP15
08afDUP2
08b0SWAP5
08b1DUP3
08b2SWAP4
08b3PUSH40xb389b84f
08b8PUSH10xe0
08baSHL
08bbDUP5
08bcMSTORE
08bdPUSH10x04
08bfDUP5
08c0ADD
08c1PUSH20x47ab
08c4JUMP
08c5JUMPDEST
08c6SUB
08c7SWAP2
08c8GAS
08c9DELEGATECALL
08caSWAP10
08cbDUP11
08ccISZERO
08cdPUSH20x1357
08d0JUMPI
08d1PUSH0
08d2SWAP11
08d3PUSH20x1333
08d6JUMPI
08d7JUMPDEST
08d8POP
08d9PUSH10x20
08dbDUP5
08dcADD
08ddSWAP2
08deDUP3
08dfMLOAD
08e0PUSH10x20
08e2DUP13
08e3ADD
08e4MLOAD
08e5SWAP1
08e6DUP2
08e7DUP2
08e8SUB
08e9PUSH20x131e
08ecJUMPI
08edPOP
08eePOP
08efPUSH20x08f7
08f2DUP6
08f3PUSH20x597f
08f6JUMP
08f7JUMPDEST
08f8PUSH20x0900
08fbDUP12
08fcPUSH20x597f
08ffJUMP
0900JUMPDEST
0901DUP11
0902MLOAD
0903DUP6
0904MLOAD
0905SWAP1
0906PUSH10x40
0908MLOAD
0909SWAP1
090aPUSH10x20
090cDUP3
090dADD
090eSWAP3
090fPUSH320x6e3591285df1e4e62815fc41f5a94072fc4e3ab79993ede42c87f8995a9f8168
0930DUP5
0931MSTORE
0932CHAINID
0933PUSH10x40
0935DUP5
0936ADD
0937MSTORE
0938ADDRESS
0939PUSH10x60
093bDUP5
093cADD
093dMSTORE
093ePUSH10x80
0940DUP4
0941ADD
0942MSTORE
0943PUSH10xa0
0945DUP3
0946ADD
0947MSTORE
0948DUP4
0949PUSH10xc0
094bDUP3
094cADD
094dMSTORE
094ePUSH10xc0
0950DUP2
0951MSTORE
0952PUSH20x095c
0955PUSH10xe0
0957DUP3
0958PUSH20x40d5
095bJUMP
095cJUMPDEST
095dMLOAD
095eSWAP1
095fKECCAK256
0960SWAP4
0961PUSH10x40
0963MLOAD
0964SWAP5
0965PUSH10x20
0967DUP7
0968ADD
0969MSTORE
096aPUSH10x20
096cDUP6
096dMSTORE
096ePUSH20x0978
0971PUSH10x40
0973DUP7
0974PUSH20x40d5
0977JUMP
0978JUMPDEST
0979PUSH20x0160
097cDUP7
097dADD
097eSWAP5
097fPUSH20x099e
0982DUP7
0983MLOAD
0984DUP3
0985PUSH20x0998
0988PUSH20x0991
098bDUP8
098cDUP1
098dPUSH20x428a
0990JUMP
0991JUMPDEST
0992CALLDATASIZE
0993SWAP2
0994PUSH20x4111
0997JUMP
0998JUMPDEST
0999SWAP2
099aPUSH20x5009
099dJUMP
099eJUMPDEST
099fISZERO
09a0SWAP3
09a1DUP4
09a2ISZERO
09a3PUSH20x12f3
09a6JUMPI
09a7JUMPDEST
09a8POP
09a9POP
09aaPOP
09abPUSH20x12e0
09aeJUMPI
09afADDRESS
09b0PUSH0
09b1MSTORE
09b2PUSH10x14
09b4PUSH10x20
09b6MSTORE
09b7DUP1
09b8PUSH10x01
09baPUSH10x01
09bcPUSH10x40
09beSHL
09bfSUB
09c0PUSH10x40
09c2PUSH0
09c3KECCAK256
09c4SLOAD
09c5AND
09c6EQ
09c7PUSH20x12ac
09caJUMPI
09cbJUMPDEST
09ccPOP
09cdDUP3
09ceMLOAD
09cfSWAP7
09d0DUP5
09d1ISZERO
09d2PUSH20x1299
09d5JUMPI
09d6DUP4
09d7MLOAD
09d8PUSH0
09d9MSTORE
09daPUSH10x0d
09dcPUSH10x20
09deMSTORE
09dfPUSH10xff
09e1PUSH10x40
09e3PUSH0
09e4KECCAK256
09e5SLOAD
09e6AND
09e7PUSH20x1285
09eaJUMPI
09ebDUP4
09ecMLOAD
09edPUSH0
09eeSWAP1
09efDUP2
09f0MSTORE
09f1PUSH10x0c
09f3PUSH10x20
09f5MSTORE
09f6PUSH10x40
09f8SWAP1
09f9KECCAK256
09faSLOAD
09fbPUSH10x01
09fdPUSH10x01
09ffPUSH10xa0
0a01SHL
0a02SUB
0a03AND
0a04DUP1
0a05ISZERO
0a06ISZERO
0a07DUP1
0a08PUSH20x127b
0a0bJUMPI
0a0cJUMPDEST
0a0dPUSH20x1265
0a10JUMPI
0a11POP
0a12DUP5
0a13PUSH0
0a14MSTORE
0a15PUSH10x02
0a17PUSH10x20
0a19MSTORE
0a1aPUSH10x40
0a1cPUSH0
0a1dKECCAK256
0a1ePUSH10x05
0a20DUP2
0a21ADD
0a22SWAP3
0a23DUP4
0a24SLOAD
0a25PUSH10xff
0a27DUP2
0a28PUSH10xd8
0a2aSHR
0a2bAND
0a2cISZERO
0a2dPUSH0
0a2eEQ
0a2fPUSH20x11e3
0a32JUMPI
0a33POP
0a34SWAP1
0a35PUSH10x0f
0a37SLOAD
0a38SWAP1
0a39PUSH10x01
0a3bPUSH10x40
0a3dSHL
0a3eDUP3
0a3fLT
0a40ISZERO
0a41PUSH20x0ee2
0a44JUMPI
0a45DUP12
0a46SWAP3
0a47PUSH20x0a80
0a4aDUP12
0a4bPUSH20x0a5c
0a4eDUP6
0a4fPUSH10x01
0a51PUSH10x04
0a53SWAP8
0a54ADD
0a55PUSH10x0f
0a57SSTORE
0a58PUSH20x4190
0a5bJUMP
0a5cJUMPDEST
0a5dDUP2
0a5eSLOAD
0a5fPUSH10x01
0a61PUSH10x01
0a63PUSH10xa0
0a65SHL
0a66SUB
0a67SWAP4
0a68DUP5
0a69AND
0a6aPUSH10x03
0a6cSWAP3
0a6dSWAP1
0a6eSWAP3
0a6fSHL
0a70SWAP2
0a71DUP3
0a72SHL
0a73SWAP4
0a74SWAP1
0a75SWAP2
0a76SHL
0a77NOT
0a78AND
0a79SWAP2
0a7aSWAP1
0a7bSWAP2
0a7cOR
0a7dSWAP1
0a7eSSTORE
0a7fJUMP
0a80JUMPDEST
0a81DUP6
0a82SLOAD
0a83PUSH10xff
0a85PUSH10xd8
0a87SHL
0a88NOT
0a89AND
0a8aPUSH10x01
0a8cPUSH10xd8
0a8eSHL
0a8fOR
0a90DUP7
0a91SSTORE
0a92JUMPDEST
0a93DUP8
0a94MLOAD
0a95DUP3
0a96SSTORE
0a97DUP14
0a98MLOAD
0a99PUSH10x01
0a9bDUP4
0a9cADD
0a9dSSTORE
0a9eMLOAD
0a9fPUSH10x02
0aa1DUP3
0aa2ADD
0aa3SSTORE
0aa4PUSH10xc0
0aa6DUP8
0aa7ADD
0aa8MLOAD
0aa9PUSH10x03
0aabDUP3
0aacADD
0aadSSTORE
0aaeADD
0aafSSTORE
0ab0PUSH10xe0
0ab2DUP5
0ab3ADD
0ab4MLOAD
0ab5DUP3
0ab6SLOAD
0ab7PUSH20x0100
0abaDUP7
0abbADD
0abcMLOAD
0abdPUSH20x0120
0ac0DUP8
0ac1ADD
0ac2MLOAD
0ac3PUSH20x0140
0ac6DUP9
0ac7ADD
0ac8MLOAD
0ac9PUSH10x01
0acbPUSH10x01
0acdPUSH10xd0
0acfSHL
0ad0SUB
0ad1NOT
0ad2SWAP1
0ad3SWAP4
0ad4AND
0ad5PUSH10xff
0ad7SWAP5
0ad8SWAP1
0ad9SWAP5
0adaAND
0adbSWAP4
0adcSWAP1
0addSWAP4
0adeOR
0adfPUSH10x08
0ae1SWAP2
0ae2SWAP1
0ae3SWAP2
0ae4SHL
0ae5PUSH20xff00
0ae8AND
0ae9OR
0aeaPUSH10x10
0aecSWAP3
0aedSWAP1
0aeeSWAP3
0aefSHL
0af0PUSH100xffffffffffffffff0000
0afbAND
0afcSWAP2
0afdSWAP1
0afeSWAP2
0affOR
0b00PUSH10x50
0b02SWAP2
0b03SWAP1
0b04SWAP2
0b05SHL
0b06PUSH80xffffffffffffffff
0b0fPUSH10x50
0b11SHL
0b12AND
0b13OR
0b14PUSH10x90
0b16SWAP2
0b17SWAP1
0b18SWAP2
0b19SHL
0b1aPUSH80xffffffffffffffff
0b23PUSH10x90
0b25SHL
0b26AND
0b27OR
0b28SWAP1
0b29SSTORE
0b2aDUP1
0b2bMLOAD
0b2cPUSH10x01
0b2ePUSH10x01
0b30PUSH10xa0
0b32SHL
0b33SUB
0b34SWAP1
0b35PUSH20x0b3d
0b38SWAP1
0b39PUSH20x4bd8
0b3cJUMP
0b3dJUMPDEST
0b3eAND
0b3fPUSH0
0b40DUP2
0b41DUP2
0b42MSTORE
0b43PUSH10x15
0b45PUSH10x20
0b47MSTORE
0b48PUSH10x40
0b4aSWAP1
0b4bKECCAK256
0b4cSLOAD
0b4dPUSH10x01
0b4fPUSH10x01
0b51PUSH10xa0
0b53SHL
0b54SUB
0b55AND
0b56DUP1
0b57ISZERO
0b58ISZERO
0b59DUP1
0b5aPUSH20x11d9
0b5dJUMPI
0b5eJUMPDEST
0b5fPUSH20x11c3
0b62JUMPI
0b63POP
0b64DUP4
0b65PUSH0
0b66MSTORE
0b67PUSH10x03
0b69PUSH10x20
0b6bMSTORE
0b6cPUSH20x0b78
0b6fPUSH10x40
0b71PUSH0
0b72KECCAK256
0b73SLOAD
0b74PUSH20x42bc
0b77JUMP
0b78JUMPDEST
0b79PUSH20x1175
0b7cJUMPI
0b7dJUMPDEST
0b7ePUSH0
0b7fMSTORE
0b80PUSH10x15
0b82PUSH10x20
0b84MSTORE
0b85PUSH10x40
0b87PUSH0
0b88KECCAK256
0b89DUP4
0b8aPUSH10x01
0b8cPUSH10x01
0b8ePUSH10x60
0b90SHL
0b91SUB
0b92PUSH10xa0
0b94SHL
0b95DUP3
0b96SLOAD
0b97AND
0b98OR
0b99SWAP1
0b9aSSTORE
0b9bMLOAD
0b9cDUP3
0b9dPUSH0
0b9eMSTORE
0b9fPUSH10x03
0ba1PUSH10x20
0ba3MSTORE
0ba4PUSH10x40
0ba6PUSH0
0ba7KECCAK256
0ba8SWAP1
0ba9DUP1
0baaMLOAD
0babSWAP1
0bacPUSH10x01
0baePUSH10x01
0bb0PUSH10x40
0bb2SHL
0bb3SUB
0bb4DUP3
0bb5GT
0bb6PUSH20x0ee2
0bb9JUMPI
0bbaPUSH20x0bc3
0bbdDUP4
0bbeSLOAD
0bbfPUSH20x42bc
0bc2JUMP
0bc3JUMPDEST
0bc4PUSH10x1f
0bc6DUP2
0bc7GT
0bc8PUSH20x113b
0bcbJUMPI
0bccJUMPDEST
0bcdPOP
0bcePUSH10x20
0bd0SWAP1
0bd1PUSH10x1f
0bd3DUP4
0bd4GT
0bd5PUSH10x01
0bd7EQ
0bd8PUSH20x10d8
0bdbJUMPI
0bdcPUSH20x0bfc
0bdfSWAP3
0be0SWAP2
0be1PUSH0
0be2SWAP2
0be3DUP4
0be4PUSH20x0ef6
0be7JUMPI
0be8JUMPDEST
0be9POP
0beaPOP
0bebDUP2
0becPUSH10x01
0beeSHL
0befSWAP2
0bf0PUSH0
0bf1NOT
0bf2SWAP1
0bf3PUSH10x03
0bf5SHL
0bf6SHR
0bf7NOT
0bf8AND
0bf9OR
0bfaSWAP1
0bfbJUMP
0bfcJUMPDEST
0bfdSWAP1
0bfeSSTORE
0bffJUMPDEST
0c00PUSH20x0180
0c03DUP2
0c04ADD
0c05MLOAD
0c06DUP3
0c07PUSH0
0c08MSTORE
0c09PUSH10x04
0c0bPUSH10x20
0c0dMSTORE
0c0ePUSH10x40
0c10PUSH0
0c11KECCAK256
0c12SWAP1
0c13DUP1
0c14MLOAD
0c15SWAP1
0c16PUSH10x01
0c18PUSH10x01
0c1aPUSH10x40
0c1cSHL
0c1dSUB
0c1eDUP3
0c1fGT
0c20PUSH20x0ee2
0c23JUMPI
0c24PUSH20x0c2d
0c27DUP4
0c28SLOAD
0c29PUSH20x42bc
0c2cJUMP
0c2dJUMPDEST
0c2ePUSH10x1f
0c30DUP2
0c31GT
0c32PUSH20x109e
0c35JUMPI
0c36JUMPDEST
0c37POP
0c38PUSH10x20
0c3aSWAP1
0c3bPUSH10x1f
0c3dDUP4
0c3eGT
0c3fPUSH10x01
0c41EQ
0c42PUSH20x103b
0c45JUMPI
0c46PUSH20x0c65
0c49SWAP3
0c4aSWAP2
0c4bPUSH0
0c4cSWAP2
0c4dDUP4
0c4ePUSH20x0ef6
0c51JUMPI
0c52POP
0c53POP
0c54DUP2
0c55PUSH10x01
0c57SHL
0c58SWAP2
0c59PUSH0
0c5aNOT
0c5bSWAP1
0c5cPUSH10x03
0c5eSHL
0c5fSHR
0c60NOT
0c61AND
0c62OR
0c63SWAP1
0c64JUMP
0c65JUMPDEST
0c66SWAP1
0c67SSTORE
0c68JUMPDEST
0c69PUSH20x0160
0c6cDUP8
0c6dADD
0c6eMLOAD
0c6fDUP3
0c70PUSH0
0c71MSTORE
0c72PUSH10x05
0c74PUSH10x20
0c76MSTORE
0c77PUSH10x40
0c79PUSH0
0c7aKECCAK256
0c7bSWAP1
0c7cDUP1
0c7dMLOAD
0c7eSWAP1
0c7fPUSH10x01
0c81PUSH10x01
0c83PUSH10x40
0c85SHL
0c86SUB
0c87DUP3
0c88GT
0c89PUSH20x0ee2
0c8cJUMPI
0c8dPUSH20x0c96
0c90DUP4
0c91SLOAD
0c92PUSH20x42bc
0c95JUMP
0c96JUMPDEST
0c97PUSH10x1f
0c99DUP2
0c9aGT
0c9bPUSH20x1001
0c9eJUMPI
0c9fJUMPDEST
0ca0POP
0ca1PUSH10x20
0ca3SWAP1
0ca4PUSH10x1f
0ca6DUP4
0ca7GT
0ca8PUSH10x01
0caaEQ
0cabPUSH20x0f9e
0caeJUMPI
0cafPUSH20x0cce
0cb2SWAP3
0cb3SWAP2
0cb4PUSH0
0cb5SWAP2
0cb6DUP4
0cb7PUSH20x0ef6
0cbaJUMPI
0cbbPOP
0cbcPOP
0cbdDUP2
0cbePUSH10x01
0cc0SHL
0cc1SWAP2
0cc2PUSH0
0cc3NOT
0cc4SWAP1
0cc5PUSH10x03
0cc7SHL
0cc8SHR
0cc9NOT
0ccaAND
0ccbOR
0cccSWAP1
0ccdJUMP
0cceJUMPDEST
0ccfSWAP1
0cd0SSTORE
0cd1JUMPDEST
0cd2PUSH20x0180
0cd5DUP8
0cd6ADD
0cd7MLOAD
0cd8DUP3
0cd9PUSH0
0cdaMSTORE
0cdbPUSH10x06
0cddPUSH10x20
0cdfMSTORE
0ce0PUSH10x40
0ce2PUSH0
0ce3KECCAK256
0ce4SWAP1
0ce5DUP1
0ce6MLOAD
0ce7SWAP1
0ce8PUSH10x01
0ceaPUSH10x01
0cecPUSH10x40
0ceeSHL
0cefSUB
0cf0DUP3
0cf1GT
0cf2PUSH20x0ee2
0cf5JUMPI
0cf6PUSH20x0cff
0cf9DUP4
0cfaSLOAD
0cfbPUSH20x42bc
0cfeJUMP
0cffJUMPDEST
0d00PUSH10x1f
0d02DUP2
0d03GT
0d04PUSH20x0f64
0d07JUMPI
0d08JUMPDEST
0d09POP
0d0aPUSH10x20
0d0cSWAP1
0d0dPUSH10x1f
0d0fDUP4
0d10GT
0d11PUSH10x01
0d13EQ
0d14PUSH20x0f01
0d17JUMPI
0d18PUSH20x0d37
0d1bSWAP3
0d1cSWAP2
0d1dPUSH0
0d1eSWAP2
0d1fDUP4
0d20PUSH20x0ef6
0d23JUMPI
0d24POP
0d25POP
0d26DUP2
0d27PUSH10x01
0d29SHL
0d2aSWAP2
0d2bPUSH0
0d2cNOT
0d2dSWAP1
0d2ePUSH10x03
0d30SHL
0d31SHR
0d32NOT
0d33AND
0d34OR
0d35SWAP1
0d36JUMP
0d37JUMPDEST
0d38SWAP1
0d39SSTORE
0d3aJUMPDEST
0d3bPUSH20x01e0
0d3eDUP2
0d3fADD
0d40MLOAD
0d41SWAP7
0d42DUP3
0d43PUSH0
0d44MSTORE
0d45PUSH10x07
0d47PUSH10x20
0d49MSTORE
0d4aPUSH10x40
0d4cPUSH0
0d4dKECCAK256
0d4eDUP9
0d4fMLOAD
0d50PUSH10x01
0d52PUSH10x01
0d54PUSH10x40
0d56SHL
0d57SUB
0d58DUP2
0d59GT
0d5aPUSH20x0ee2
0d5dJUMPI
0d5ePUSH10x20
0d60SWAP10
0d61PUSH20x0d6a
0d64DUP4
0d65SLOAD
0d66PUSH20x42bc
0d69JUMP
0d6aJUMPDEST
0d6bPUSH10x1f
0d6dDUP2
0d6eGT
0d6fPUSH20x0ea1
0d72JUMPI
0d73JUMPDEST
0d74POP
0d75DUP11
0d76SWAP1
0d77PUSH10x1f
0d79DUP4
0d7aGT
0d7bPUSH10x01
0d7dEQ
0d7ePUSH20x0e24
0d81JUMPI
0d82SWAP4
0d83PUSH20x06a4
0d86SWAP10
0d87SWAP11
0d88SWAP4
0d89PUSH20x0dbd
0d8cDUP5
0d8dPUSH0
0d8eMLOAD
0d8fPUSH10x20
0d91PUSH20x64c9
0d94PUSH0
0d95CODECOPY
0d96PUSH0
0d97MLOAD
0d98SWAP1
0d99PUSH0
0d9aMSTORE
0d9bSWAP9
0d9cSWAP6
0d9dPUSH20x0deb
0da0SWAP6
0da1PUSH10x40
0da3SWAP10
0da4PUSH0
0da5SWAP3
0da6PUSH20x0e19
0da9JUMPI
0daaPOP
0dabPOP
0dacDUP2
0dadPUSH10x01
0dafSHL
0db0SWAP2
0db1PUSH0
0db2NOT
0db3SWAP1
0db4PUSH10x03
0db6SHL
0db7SHR
0db8NOT
0db9AND
0dbaOR
0dbbSWAP1
0dbcJUMP
0dbdJUMPDEST
0dbeSWAP1
0dbfSSTORE
0dc0JUMPDEST
0dc1PUSH20x0dd7
0dc4PUSH20x01a0
0dc7DUP5
0dc8ADD
0dc9MLOAD
0dcaPUSH20x01c0
0dcdDUP6
0dceADD
0dcfMLOAD
0dd0SWAP1
0dd1PUSH0
0dd2DUP13
0dd3PUSH20x5aba
0dd6JUMP
0dd7JUMPDEST
0dd8PUSH20x01c0
0ddbPUSH20x01a0
0ddeDUP3
0ddfADD
0de0MLOAD
0de1SWAP2
0de2ADD
0de3MLOAD
0de4SWAP1
0de5PUSH0
0de6DUP11
0de7PUSH20x5d7e
0deaJUMP
0debJUMPDEST
0decDUP1
0dedMLOAD
0deePUSH0
0defMSTORE
0df0PUSH10x0c
0df2DUP11
0df3MSTORE
0df4DUP3
0df5PUSH0
0df6KECCAK256
0df7DUP6
0df8PUSH10x01
0dfaPUSH10x01
0dfcPUSH10x60
0dfeSHL
0dffSUB
0e00PUSH10xa0
0e02SHL
0e03DUP3
0e04SLOAD
0e05AND
0e06OR
0e07SWAP1
0e08SSTORE
0e09MLOAD
0e0aSWAP5
0e0bDUP3
0e0cMLOAD
0e0dSWAP2
0e0eDUP3
0e0fMSTORE
0e10DUP10
0e11DUP3
0e12ADD
0e13MSTORE
0e14LOG3
0e15PUSH20x4eb2
0e18JUMP
0e19JUMPDEST
0e1aADD
0e1bMLOAD
0e1cSWAP1
0e1dPOP
0e1ePUSH0
0e1fDUP1
0e20PUSH20x0be8
0e23JUMP
0e24JUMPDEST
0e25SWAP1
0e26PUSH10x1f
0e28NOT
0e29DUP4
0e2aAND
0e2bSWAP2
0e2cDUP5
0e2dPUSH0
0e2eMSTORE
0e2fDUP2
0e30PUSH0
0e31KECCAK256
0e32SWAP3
0e33PUSH0
0e34JUMPDEST
0e35DUP2
0e36DUP2
0e37LT
0e38PUSH20x0e8a
0e3bJUMPI
0e3cPOP
0e3dDUP5
0e3ePUSH20x0deb
0e41SWAP5
0e42PUSH10x40
0e44SWAP9
0e45SWAP5
0e46PUSH20x06a4
0e49SWAP15
0e4aSWAP16
0e4bSWAP9
0e4cSWAP5
0e4dPUSH0
0e4eMLOAD
0e4fPUSH10x20
0e51PUSH20x64c9
0e54PUSH0
0e55CODECOPY
0e56PUSH0
0e57MLOAD
0e58SWAP1
0e59PUSH0
0e5aMSTORE
0e5bSWAP12
0e5cSWAP9
0e5dPUSH10x01
0e5fSWAP6
0e60LT
0e61PUSH20x0e72
0e64JUMPI
0e65JUMPDEST
0e66POP
0e67POP
0e68POP
0e69DUP2
0e6aSHL
0e6bADD
0e6cSWAP1
0e6dSSTORE
0e6ePUSH20x0dc0
0e71JUMP
0e72JUMPDEST
0e73ADD
0e74MLOAD
0e75PUSH0
0e76NOT
0e77PUSH10xf8
0e79DUP5
0e7aPUSH10x03
0e7cSHL
0e7dAND
0e7eSHR
0e7fNOT
0e80AND
0e81SWAP1
0e82SSTORE
0e83DUP16
0e84DUP1
0e85DUP1
0e86PUSH20x0e65
0e89JUMP
0e8aJUMPDEST
0e8bSWAP3
0e8cSWAP4
0e8dDUP15
0e8ePUSH10x01
0e90DUP2
0e91SWAP3
0e92DUP8
0e93DUP7
0e94ADD
0e95MLOAD
0e96DUP2
0e97SSTORE
0e98ADD
0e99SWAP6
0e9aADD
0e9bSWAP4
0e9cADD
0e9dPUSH20x0e34
0ea0JUMP
0ea1JUMPDEST
0ea2DUP3
0ea3DUP2
0ea4GT
0ea5ISZERO
0ea6PUSH20x0d73
0ea9JUMPI
0eaaPUSH20x0ed4
0eadSWAP1
0eaeDUP5
0eafPUSH0
0eb0MSTORE
0eb1DUP13
0eb2PUSH0
0eb3KECCAK256
0eb4SWAP1
0eb5PUSH10x1f
0eb7DUP6
0eb8ADD
0eb9PUSH10x05
0ebbSHR
0ebcSWAP1
0ebdDUP15
0ebeDUP7
0ebfLT
0ec0PUSH20x0eda
0ec3JUMPI
0ec4JUMPDEST
0ec5PUSH10x1f
0ec7DUP3
0ec8SWAP2
0ec9ADD
0ecaPUSH10x05
0eccSHR
0ecdSUB
0eceSWAP2
0ecfADD
0ed0PUSH20x587a
0ed3JUMP
0ed4JUMPDEST
0ed5DUP12
0ed6PUSH20x0d73
0ed9JUMP
0edaJUMPDEST
0edbPUSH0
0edcSWAP2
0eddPOP
0edePUSH20x0ec4
0ee1JUMP
0ee2JUMPDEST
0ee3PUSH40x4e487b71
0ee8PUSH10xe0
0eeaSHL
0eebPUSH0
0eecMSTORE
0eedPUSH10x41
0eefPUSH10x04
0ef1MSTORE
0ef2PUSH10x24
0ef4PUSH0
0ef5REVERT
0ef6JUMPDEST
0ef7ADD
0ef8MLOAD
0ef9SWAP1
0efaPOP
0efbDUP12
0efcDUP1
0efdPUSH20x0be8
0f00JUMP
0f01JUMPDEST
0f02SWAP1
0f03PUSH10x1f
0f05NOT
0f06DUP4
0f07AND
0f08SWAP2
0f09DUP5
0f0aPUSH0
0f0bMSTORE
0f0cDUP2
0f0dPUSH0
0f0eKECCAK256
0f0fSWAP3
0f10PUSH0
0f11JUMPDEST
0f12DUP2
0f13DUP2
0f14LT
0f15PUSH20x0f4c
0f18JUMPI
0f19POP
0f1aSWAP1
0f1bDUP5
0f1cPUSH10x01
0f1eSWAP6
0f1fSWAP5
0f20SWAP4
0f21SWAP3
0f22LT
0f23PUSH20x0f34
0f26JUMPI
0f27JUMPDEST
0f28POP
0f29POP
0f2aPOP
0f2bDUP2
0f2cSHL
0f2dADD
0f2eSWAP1
0f2fSSTORE
0f30PUSH20x0d3a
0f33JUMP
0f34JUMPDEST
0f35ADD
0f36MLOAD
0f37PUSH0
0f38NOT
0f39PUSH10xf8
0f3bDUP5
0f3cPUSH10x03
0f3eSHL
0f3fAND
0f40SHR
0f41NOT
0f42AND
0f43SWAP1
0f44SSTORE
0f45DUP11
0f46DUP1
0f47DUP1
0f48PUSH20x0f27
0f4bJUMP
0f4cJUMPDEST
0f4dSWAP3
0f4eSWAP4
0f4fPUSH10x20
0f51PUSH10x01
0f53DUP2
0f54SWAP3
0f55DUP8
0f56DUP7
0f57ADD
0f58MLOAD
0f59DUP2
0f5aSSTORE
0f5bADD
0f5cSWAP6
0f5dADD
0f5eSWAP4
0f5fADD
0f60PUSH20x0f11
0f63JUMP
0f64JUMPDEST
0f65DUP3
0f66DUP2
0f67GT
0f68ISZERO
0f69PUSH20x0d08
0f6cJUMPI
0f6dPUSH20x0f98
0f70SWAP1
0f71DUP5
0f72PUSH0
0f73MSTORE
0f74PUSH10x20
0f76PUSH0
0f77KECCAK256
0f78SWAP1
0f79PUSH10x1f
0f7bDUP6
0f7cADD
0f7dPUSH10x05
0f7fSHR
0f80SWAP1
0f81PUSH10x20
0f83DUP7
0f84LT
0f85PUSH20x0eda
0f88JUMPI
0f89PUSH10x1f
0f8bDUP3
0f8cSWAP2
0f8dADD
0f8ePUSH10x05
0f90SHR
0f91SUB
0f92SWAP2
0f93ADD
0f94PUSH20x587a
0f97JUMP
0f98JUMPDEST
0f99DUP11
0f9aPUSH20x0d08
0f9dJUMP
0f9eJUMPDEST
0f9fSWAP1
0fa0PUSH10x1f
0fa2NOT
0fa3DUP4
0fa4AND
0fa5SWAP2
0fa6DUP5
0fa7PUSH0
0fa8MSTORE
0fa9DUP2
0faaPUSH0
0fabKECCAK256
0facSWAP3
0fadPUSH0
0faeJUMPDEST
0fafDUP2
0fb0DUP2
0fb1LT
0fb2PUSH20x0fe9
0fb5JUMPI
0fb6POP
0fb7SWAP1
0fb8DUP5
0fb9PUSH10x01
0fbbSWAP6
0fbcSWAP5
0fbdSWAP4
0fbeSWAP3
0fbfLT
0fc0PUSH20x0fd1
0fc3JUMPI
0fc4JUMPDEST
0fc5POP
0fc6POP
0fc7POP
0fc8DUP2
0fc9SHL
0fcaADD
0fcbSWAP1
0fccSSTORE
0fcdPUSH20x0cd1
0fd0JUMP
0fd1JUMPDEST
0fd2ADD
0fd3MLOAD
0fd4PUSH0
0fd5NOT
0fd6PUSH10xf8
0fd8DUP5
0fd9PUSH10x03
0fdbSHL
0fdcAND
0fddSHR
0fdeNOT
0fdfAND
0fe0SWAP1
0fe1SSTORE
0fe2DUP11
0fe3DUP1
0fe4DUP1
0fe5PUSH20x0fc4
0fe8JUMP
0fe9JUMPDEST
0feaSWAP3
0febSWAP4
0fecPUSH10x20
0feePUSH10x01
0ff0DUP2
0ff1SWAP3
0ff2DUP8
0ff3DUP7
0ff4ADD
0ff5MLOAD
0ff6DUP2
0ff7SSTORE
0ff8ADD
0ff9SWAP6
0ffaADD
0ffbSWAP4
0ffcADD
0ffdPUSH20x0fae
1000JUMP
1001JUMPDEST
1002DUP3
1003DUP2
1004GT
1005ISZERO
1006PUSH20x0c9f
1009JUMPI
100aPUSH20x1035
100dSWAP1
100eDUP5
100fPUSH0
1010MSTORE
1011PUSH10x20
1013PUSH0
1014KECCAK256
1015SWAP1
1016PUSH10x1f
1018DUP6
1019ADD
101aPUSH10x05
101cSHR
101dSWAP1
101ePUSH10x20
1020DUP7
1021LT
1022PUSH20x0eda
1025JUMPI
1026PUSH10x1f
1028DUP3
1029SWAP2
102aADD
102bPUSH10x05
102dSHR
102eSUB
102fSWAP2
1030ADD
1031PUSH20x587a
1034JUMP
1035JUMPDEST
1036DUP11
1037PUSH20x0c9f
103aJUMP
103bJUMPDEST
103cSWAP1
103dPUSH10x1f
103fNOT
1040DUP4
1041AND
1042SWAP2
1043DUP5
1044PUSH0
1045MSTORE
1046DUP2
1047PUSH0
1048KECCAK256
1049SWAP3
104aPUSH0
104bJUMPDEST
104cDUP2
104dDUP2
104eLT
104fPUSH20x1086
1052JUMPI
1053POP
1054SWAP1
1055DUP5
1056PUSH10x01
1058SWAP6
1059SWAP5
105aSWAP4
105bSWAP3
105cLT
105dPUSH20x106e
1060JUMPI
1061JUMPDEST
1062POP
1063POP
1064POP
1065DUP2
1066SHL
1067ADD
1068SWAP1
1069SSTORE
106aPUSH20x0c68
106dJUMP
106eJUMPDEST
106fADD
1070MLOAD
1071PUSH0
1072NOT
1073PUSH10xf8
1075DUP5
1076PUSH10x03
1078SHL
1079AND
107aSHR
107bNOT
107cAND
107dSWAP1
107eSSTORE
107fDUP11
1080DUP1
1081DUP1
1082PUSH20x1061
1085JUMP
1086JUMPDEST
1087SWAP3
1088SWAP4
1089PUSH10x20
108bPUSH10x01
108dDUP2
108eSWAP3
108fDUP8
1090DUP7
1091ADD
1092MLOAD
1093DUP2
1094SSTORE
1095ADD
1096SWAP6
1097ADD
1098SWAP4
1099ADD
109aPUSH20x104b
109dJUMP
109eJUMPDEST
109fDUP3
10a0DUP2
10a1GT
10a2ISZERO
10a3PUSH20x0c36
10a6JUMPI
10a7PUSH20x10d2
10aaSWAP1
10abDUP5
10acPUSH0
10adMSTORE
10aePUSH10x20
10b0PUSH0
10b1KECCAK256
10b2SWAP1
10b3PUSH10x1f
10b5DUP6
10b6ADD
10b7PUSH10x05
10b9SHR
10baSWAP1
10bbPUSH10x20
10bdDUP7
10beLT
10bfPUSH20x0eda
10c2JUMPI
10c3PUSH10x1f
10c5DUP3
10c6SWAP2
10c7ADD
10c8PUSH10x05
10caSHR
10cbSUB
10ccSWAP2
10cdADD
10cePUSH20x587a
10d1JUMP
10d2JUMPDEST
10d3DUP11
10d4PUSH20x0c36
10d7JUMP
10d8JUMPDEST
10d9SWAP1
10daPUSH10x1f
10dcNOT
10ddDUP4
10deAND
10dfSWAP2
10e0DUP5
10e1PUSH0
10e2MSTORE
10e3DUP2
10e4PUSH0
10e5KECCAK256
10e6SWAP3
10e7PUSH0
10e8JUMPDEST
10e9DUP2
10eaDUP2
10ebLT
10ecPUSH20x1123
10efJUMPI
10f0POP
10f1SWAP1
10f2DUP5
10f3PUSH10x01
10f5SWAP6
10f6SWAP5
10f7SWAP4
10f8SWAP3
10f9LT
10faPUSH20x110b
10fdJUMPI
10feJUMPDEST
10ffPOP
1100POP
1101POP
1102DUP2
1103SHL
1104ADD
1105SWAP1
1106SSTORE
1107PUSH20x0bff
110aJUMP
110bJUMPDEST
110cADD
110dMLOAD
110ePUSH0
110fNOT
1110PUSH10xf8
1112DUP5
1113PUSH10x03
1115SHL
1116AND
1117SHR
1118NOT
1119AND
111aSWAP1
111bSSTORE
111cDUP11
111dDUP1
111eDUP1
111fPUSH20x10fe
1122JUMP
1123JUMPDEST
1124SWAP3
1125SWAP4
1126PUSH10x20
1128PUSH10x01
112aDUP2
112bSWAP3
112cDUP8
112dDUP7
112eADD
112fMLOAD
1130DUP2
1131SSTORE
1132ADD
1133SWAP6
1134ADD
1135SWAP4
1136ADD
1137PUSH20x10e8
113aJUMP
113bJUMPDEST
113cDUP3
113dDUP2
113eGT
113fISZERO
1140PUSH20x0bcc
1143JUMPI
1144PUSH20x116f
1147SWAP1
1148DUP5
1149PUSH0
114aMSTORE
114bPUSH10x20
114dPUSH0
114eKECCAK256
114fSWAP1
1150PUSH10x1f
1152DUP6
1153ADD
1154PUSH10x05
1156SHR
1157SWAP1
1158PUSH10x20
115aDUP7
115bLT
115cPUSH20x0eda
115fJUMPI
1160PUSH10x1f
1162DUP3
1163SWAP2
1164ADD
1165PUSH10x05
1167SHR
1168SUB
1169SWAP2
116aADD
116bPUSH20x587a
116eJUMP
116fJUMPDEST
1170DUP11
1171PUSH20x0bcc
1174JUMP
1175JUMPDEST
1176DUP4
1177PUSH0
1178MSTORE
1179PUSH10x03
117bPUSH10x20
117dMSTORE
117ePUSH10x01
1180DUP1
1181PUSH10xa0
1183SHL
1184SUB
1185PUSH20x1193
1188PUSH20x0466
118bPUSH10x40
118dPUSH0
118eKECCAK256
118fPUSH20x42f4
1192JUMP
1193JUMPDEST
1194AND
1195DUP2
1196DUP2
1197SUB
1198PUSH20x11a2
119bJUMPI
119cJUMPDEST
119dPOP
119ePUSH20x0b7d
11a1JUMP
11a2JUMPDEST
11a3PUSH0
11a4MSTORE
11a5PUSH10x15
11a7PUSH10x20
11a9MSTORE
11aaPUSH10x40
11acPUSH0
11adKECCAK256
11aePUSH10x01
11b0PUSH10x01
11b2PUSH10x60
11b4SHL
11b5SUB
11b6PUSH10xa0
11b8SHL
11b9DUP2
11baSLOAD
11bbAND
11bcSWAP1
11bdSSTORE
11beDUP10
11bfPUSH20x119c
11c2JUMP
11c3JUMPDEST
11c4SWAP1
11c5PUSH40x13362de3
11caPUSH10xe0
11ccSHL
11cdPUSH0
11ceMSTORE
11cfPUSH10x04
11d1MSTORE
11d2PUSH10x24
11d4MSTORE
11d5PUSH10x44
11d7PUSH0
11d8REVERT
11d9JUMPDEST
11daPOP
11dbDUP5
11dcDUP2
11ddEQ
11deISZERO
11dfPUSH20x0b5e
11e2JUMP
11e3JUMPDEST
11e4PUSH10x01
11e6PUSH10x01
11e8PUSH10x40
11eaSHL
11ebSUB
11ecDUP2
11edPUSH10x90
11efSHR
11f0AND
11f1DUP1
11f2DUP11
11f3GT
11f4ISZERO
11f5PUSH20x124e
11f8JUMPI
11f9POP
11faPUSH10xd0
11fcSHR
11fdPUSH10xff
11ffAND
1200PUSH20x123a
1203JUMPI
1204DUP2
1205PUSH10x04
1207SWAP2
1208DUP13
1209SWAP4
120aSLOAD
120bDUP9
120cMLOAD
120dDUP2
120eSUB
120fPUSH20x1219
1212JUMPI
1213JUMPDEST
1214POP
1215PUSH20x0a92
1218JUMP
1219JUMPDEST
121aPUSH0
121bMSTORE
121cPUSH10x0c
121ePUSH10x20
1220MSTORE
1221PUSH10x40
1223PUSH0
1224KECCAK256
1225PUSH10x01
1227PUSH10x01
1229PUSH10x60
122bSHL
122cSUB
122dPUSH10xa0
122fSHL
1230DUP2
1231SLOAD
1232AND
1233SWAP1
1234SSTORE
1235DUP15
1236PUSH20x1213
1239JUMP
123aJUMPDEST
123bPOP
123cSLOAD
123dPUSH40xe41b98f7
1242PUSH10xe0
1244SHL
1245PUSH0
1246MSTORE
1247PUSH10x04
1249MSTORE
124aPUSH10x24
124cPUSH0
124dREVERT
124eJUMPDEST
124fDUP10
1250SWAP1
1251PUSH40xdf5abcab
1256PUSH10xe0
1258SHL
1259PUSH0
125aMSTORE
125bPUSH10x04
125dMSTORE
125ePUSH10x24
1260MSTORE
1261PUSH10x44
1263PUSH0
1264REVERT
1265JUMPDEST
1266DUP5
1267MLOAD
1268PUSH30x3bdaab
126cPUSH10xe5
126eSHL
126fPUSH0
1270MSTORE
1271PUSH10x04
1273MSTORE
1274PUSH10x24
1276MSTORE
1277PUSH10x44
1279PUSH0
127aREVERT
127bJUMPDEST
127cPOP
127dDUP6
127eDUP2
127fEQ
1280ISZERO
1281PUSH20x0a0c
1284JUMP
1285JUMPDEST
1286DUP4
1287MLOAD
1288PUSH40xe41b98f7
128dPUSH10xe0
128fSHL
1290PUSH0
1291MSTORE
1292PUSH10x04
1294MSTORE
1295PUSH10x24
1297PUSH0
1298REVERT
1299JUMPDEST
129aDUP5
129bPUSH40x3131bf79
12a0PUSH10xe2
12a2SHL
12a3PUSH0
12a4MSTORE
12a5PUSH10x04
12a7MSTORE
12a8PUSH10x24
12aaPUSH0
12abREVERT
12acJUMPDEST
12adPUSH20x12b5
12b0SWAP1
12b1PUSH20x426c
12b4JUMP
12b5JUMPDEST
12b6ADDRESS
12b7PUSH0
12b8MSTORE
12b9PUSH10x14
12bbPUSH10x20
12bdMSTORE
12bePUSH10x01
12c0PUSH10x01
12c2PUSH10x40
12c4SHL
12c5SUB
12c6PUSH10x40
12c8PUSH0
12c9KECCAK256
12caSWAP2
12cbAND
12ccPUSH10x01
12cePUSH10x01
12d0PUSH10x40
12d2SHL
12d3SUB
12d4NOT
12d5DUP3
12d6SLOAD
12d7AND
12d8OR
12d9SWAP1
12daSSTORE
12dbDUP10
12dcPUSH20x09cb
12dfJUMP
12e0JUMPDEST
12e1DUP5
12e2PUSH40x9ae9f73b
12e7PUSH10xe0
12e9SHL
12eaPUSH0
12ebMSTORE
12ecPUSH10x04
12eeMSTORE
12efPUSH10x24
12f1PUSH0
12f2REVERT
12f3JUMPDEST
12f4PUSH20x1315
12f7SWAP4
12f8POP
12f9PUSH20x0991
12fcPUSH20x130f
12ffSWAP2
1300PUSH10x24
1302PUSH20x0180
1305DUP12
1306ADD
1307MLOAD
1308SWAP6
1309ADD
130aSWAP1
130bPUSH20x428a
130eJUMP
130fJUMPDEST
1310SWAP2
1311PUSH20x50b0
1314JUMP
1315JUMPDEST
1316ISZERO
1317DUP12
1318DUP1
1319DUP1
131aPUSH20x09a7
131dJUMP
131eJUMPDEST
131fPUSH40x88e08729
1324PUSH10xe0
1326SHL
1327PUSH0
1328MSTORE
1329PUSH10x04
132bMSTORE
132cPUSH10x24
132eMSTORE
132fPUSH10x44
1331PUSH0
1332REVERT
1333JUMPDEST
1334PUSH20x1350
1337SWAP2
1338SWAP11
1339POP
133aRETURNDATASIZE
133bDUP1
133cPUSH0
133dDUP4
133eRETURNDATACOPY
133fPUSH20x1348
1342DUP2
1343DUP4
1344PUSH20x40d5
1347JUMP
1348JUMPDEST
1349DUP2
134aADD
134bSWAP1
134cPUSH20x45db
134fJUMP
1350JUMPDEST
1351SWAP9
1352DUP11
1353PUSH20x08d7
1356JUMP
1357JUMPDEST
1358PUSH10x40
135aMLOAD
135bRETURNDATASIZE
135cPUSH0
135dDUP3
135eRETURNDATACOPY
135fRETURNDATASIZE
1360SWAP1
1361REVERT
1362JUMPDEST
1363PUSH0
1364SWAP4
1365SWAP3
1366SWAP2
1367SWAP8
1368POP
1369PUSH20x137b
136cSWAP1
136dRETURNDATASIZE
136eDUP1
136fDUP7
1370DUP4
1371RETURNDATACOPY
1372PUSH20x1348
1375DUP2
1376DUP4
1377PUSH20x40d5
137aJUMP
137bJUMPDEST
137cSWAP7
137dSWAP1
137eSWAP2
137fSWAP3
1380PUSH20x08a4
1383JUMP
1384JUMPDEST
1385PUSH0
1386DUP1
1387REVERT
1388JUMPDEST
1389CALLVALUE
138aPUSH20x1384
138dJUMPI
138ePUSH10x20
1390CALLDATASIZE
1391PUSH10x03
1393NOT
1394ADD
1395SLT
1396PUSH20x1384
1399JUMPI
139aPUSH20x13a1
139dPUSH20x4021
13a0JUMP
13a1JUMPDEST
13a2PUSH10x40
13a4MLOAD
13a5PUSH20x13ad
13a8DUP2
13a9PUSH20x40b9
13acJUMP
13adJUMPDEST
13aePUSH0
13afDUP2
13b0MSTORE
13b1PUSH10x20
13b3DUP2
13b4ADD
13b5PUSH0
13b6SWAP1
13b7MSTORE
13b8PUSH10x40
13baDUP2
13bbADD
13bcPUSH0
13bdSWAP1
13beMSTORE
13bfPUSH10x60
13c1DUP2
13c2ADD
13c3PUSH0
13c4SWAP1
13c5MSTORE
13c6PUSH10x80
13c8DUP2
13c9ADD
13caPUSH0
13cbSWAP1
13ccMSTORE
13cdPUSH10xa0
13cfDUP2
13d0ADD
13d1PUSH0
13d2SWAP1
13d3MSTORE
13d4PUSH10xc0
13d6DUP2
13d7ADD
13d8PUSH0
13d9SWAP1
13daMSTORE
13dbPUSH10xe0
13ddDUP2
13deADD
13dfPUSH0
13e0SWAP1
13e1MSTORE
13e2PUSH20x0100
13e5DUP2
13e6ADD
13e7PUSH0
13e8SWAP1
13e9MSTORE
13eaPUSH20x0120
13edDUP2
13eeADD
13efPUSH0
13f0SWAP1
13f1MSTORE
13f2PUSH20x0140
13f5DUP2
13f6ADD
13f7PUSH0
13f8SWAP1
13f9MSTORE
13faPUSH20x0160
13fdADD
13fePUSH0
13ffSWAP1
1400MSTORE
1401PUSH10x01
1403PUSH10xa0
1405SHL
1406PUSH10x01
1408SWAP1
1409SUB
140aAND
140bPUSH0
140cMSTORE
140dPUSH10x02
140fPUSH10x20
1411MSTORE
1412PUSH10x40
1414PUSH0
1415KECCAK256
1416PUSH10x40
1418MLOAD
1419SWAP1
141aPUSH20x1422
141dDUP3
141ePUSH20x40b9
1421JUMP
1422JUMPDEST
1423DUP1
1424SLOAD
1425SWAP2
1426DUP3
1427DUP2
1428MSTORE
1429PUSH10x01
142bDUP3
142cADD
142dSLOAD
142ePUSH10x20
1430DUP3
1431ADD
1432SWAP1
1433DUP2
1434MSTORE
1435PUSH10x02
1437DUP4
1438ADD
1439SLOAD
143aPUSH10x40
143cDUP4
143dADD
143eSWAP1
143fDUP2
1440MSTORE
1441PUSH10x03
1443DUP5
1444ADD
1445SLOAD
1446PUSH10x60
1448DUP5
1449ADD
144aSWAP1
144bDUP2
144cMSTORE
144dPUSH10x04
144fDUP6
1450ADD
1451SLOAD
1452SWAP5
1453PUSH10x80
1455DUP6
1456ADD
1457SWAP6
1458DUP7
1459MSTORE
145aPUSH10x05
145cADD
145dSLOAD
145eSWAP5
145fPUSH10xa0
1461DUP6
1462ADD
1463PUSH10xff
1465DUP8
1466AND
1467DUP2
1468MSTORE
1469PUSH10xc0
146bDUP7
146cADD
146dSWAP2
146eDUP8
146fPUSH10x08
1471SHR
1472PUSH10xff
1474AND
1475DUP4
1476MSTORE
1477PUSH10xe0
1479DUP8
147aADD
147bSWAP4
147cDUP9
147dPUSH10x10
147fSHR
1480PUSH10x01
1482PUSH10x01
1484PUSH10x40
1486SHL
1487SUB
1488AND
1489DUP6
148aMSTORE
148bPUSH20x0100
148eDUP9
148fADD
1490SWAP6
1491DUP10
1492PUSH10x50
1494SHR
1495PUSH10x01
1497PUSH10x01
1499PUSH10x40
149bSHL
149cSUB
149dAND
149eDUP8
149fMSTORE
14a0PUSH20x0120
14a3DUP10
14a4ADD
14a5SWAP8
14a6DUP11
14a7PUSH10x90
14a9SHR
14aaPUSH10x01
14acPUSH10x01
14aePUSH10x40
14b0SHL
14b1SUB
14b2AND
14b3DUP10
14b4MSTORE
14b5PUSH20x0140
14b8DUP11
14b9ADD
14baSWAP10
14bbDUP12
14bcPUSH10xd0
14beSHR
14bfPUSH10xff
14c1AND
14c2ISZERO
14c3ISZERO
14c4DUP12
14c5MSTORE
14c6PUSH20x0160
14c9ADD
14caSWAP11
14cbPUSH10xd8
14cdSHR
14cePUSH10xff
14d0AND
14d1ISZERO
14d2ISZERO
14d3DUP12
14d4MSTORE
14d5PUSH10x40
14d7MLOAD
14d8SWAP12
14d9DUP13
14daMSTORE
14dbMLOAD
14dcPUSH10x20
14deDUP13
14dfADD
14e0MSTORE
14e1MLOAD
14e2PUSH10x40
14e4DUP12
14e5ADD
14e6MSTORE
14e7MLOAD
14e8PUSH10x60
14eaDUP11
14ebADD
14ecMSTORE
14edMLOAD
14eePUSH10x80
14f0DUP10
14f1ADD
14f2MSTORE
14f3MLOAD
14f4PUSH10xff
14f6AND
14f7PUSH10xa0
14f9DUP9
14faADD
14fbMSTORE
14fcMLOAD
14fdPUSH10xff
14ffAND
1500PUSH10xc0
1502DUP8
1503ADD
1504MSTORE
1505MLOAD
1506PUSH10x01
1508PUSH10x01
150aPUSH10x40
150cSHL
150dSUB
150eAND
150fPUSH10xe0
1511DUP7
1512ADD
1513MSTORE
1514MLOAD
1515PUSH10x01
1517PUSH10x01
1519PUSH10x40
151bSHL
151cSUB
151dAND
151ePUSH20x0100
1521DUP6
1522ADD
1523MSTORE
1524MLOAD
1525PUSH10x01
1527PUSH10x01
1529PUSH10x40
152bSHL
152cSUB
152dAND
152ePUSH20x0120
1531DUP5
1532ADD
1533MSTORE
1534MLOAD
1535ISZERO
1536ISZERO
1537PUSH20x0140
153aDUP4
153bADD
153cMSTORE
153dMLOAD
153eISZERO
153fISZERO
1540PUSH20x0160
1543DUP3
1544ADD
1545MSTORE
1546PUSH20x0180
1549SWAP1
154aRETURN
154bJUMPDEST
154cCALLVALUE
154dPUSH20x1384
1550JUMPI
1551PUSH0
1552CALLDATASIZE
1553PUSH10x03
1555NOT
1556ADD
1557SLT
1558PUSH20x1384
155bJUMPI
155cPUSH10x12
155eSLOAD
155fPUSH10x40
1561MLOAD
1562PUSH10x01
1564PUSH10x01
1566PUSH10xa0
1568SHL
1569SUB
156aSWAP1
156bSWAP2
156cAND
156dDUP2
156eMSTORE
156fPUSH10x20
1571SWAP1
1572RETURN
1573JUMPDEST
1574CALLVALUE
1575PUSH20x1384
1578JUMPI
1579PUSH10x20
157bCALLDATASIZE
157cPUSH10x03
157eNOT
157fADD
1580SLT
1581PUSH20x1384
1584JUMPI
1585PUSH10x20
1587PUSH20x046b
158aPUSH20x1591
158dPUSH20x4021
1590JUMP
1591JUMPDEST
1592PUSH20x48b9
1595JUMP
1596JUMPDEST
1597CALLVALUE
1598PUSH20x1384
159bJUMPI
159cPUSH0
159dCALLDATASIZE
159ePUSH10x03
15a0NOT
15a1ADD
15a2SLT
15a3PUSH20x1384
15a6JUMPI
15a7PUSH10x20
15a9PUSH10x40
15abMLOAD
15acPUSH20x0400
15afDUP2
15b0MSTORE
15b1RETURN
15b2JUMPDEST
15b3CALLVALUE
15b4PUSH20x1384
15b7JUMPI
15b8PUSH10xa0
15baCALLDATASIZE
15bbPUSH10x03
15bdNOT
15beADD
15bfSLT
15c0PUSH20x1384
15c3JUMPI
15c4PUSH20x15cb
15c7PUSH20x4021
15caJUMP
15cbJUMPDEST
15ccPOP
15cdPUSH20x15d4
15d0PUSH20x4037
15d3JUMP
15d4JUMPDEST
15d5POP
15d6PUSH10x44
15d8CALLDATALOAD
15d9PUSH10x01
15dbPUSH10x01
15ddPUSH10x40
15dfSHL
15e0SUB
15e1DUP2
15e2GT
15e3PUSH20x1384
15e6JUMPI
15e7PUSH20x15f4
15eaSWAP1
15ebCALLDATASIZE
15ecSWAP1
15edPUSH10x04
15efADD
15f0PUSH20x3faf
15f3JUMP
15f4JUMPDEST
15f5POP
15f6POP
15f7PUSH10x64
15f9CALLDATALOAD
15faPUSH10x01
15fcPUSH10x01
15fePUSH10x40
1600SHL
1601SUB
1602DUP2
1603GT
1604PUSH20x1384
1607JUMPI
1608PUSH20x1615
160bSWAP1
160cCALLDATASIZE
160dSWAP1
160ePUSH10x04
1610ADD
1611PUSH20x3faf
1614JUMP
1615JUMPDEST
1616POP
1617POP
1618PUSH10x84
161aCALLDATALOAD
161bPUSH10x01
161dPUSH10x01
161fPUSH10x40
1621SHL
1622SUB
1623DUP2
1624GT
1625PUSH20x1384
1628JUMPI
1629PUSH20x1636
162cSWAP1
162dCALLDATASIZE
162eSWAP1
162fPUSH10x04
1631ADD
1632PUSH20x404d
1635JUMP
1636JUMPDEST
1637POP
1638POP
1639PUSH10x40
163bMLOAD
163cPUSH40xbc197c81
1641PUSH10xe0
1643SHL
1644DUP2
1645MSTORE
1646PUSH10x20
1648SWAP1
1649RETURN
164aJUMPDEST
164bCALLVALUE
164cPUSH20x1384
164fJUMPI
1650PUSH0
1651CALLDATASIZE
1652PUSH10x03
1654NOT
1655ADD
1656SLT
1657PUSH20x1384
165aJUMPI
165bPUSH10x20
165dPUSH10x40
165fMLOAD
1660PUSH320x6e3591285df1e4e62815fc41f5a94072fc4e3ab79993ede42c87f8995a9f8168
1681DUP2
1682MSTORE
1683RETURN
1684JUMPDEST
1685CALLVALUE
1686PUSH20x1384
1689JUMPI
168aPUSH10x20
168cCALLDATASIZE
168dPUSH10x03
168fNOT
1690ADD
1691SLT
1692PUSH20x1384
1695JUMPI
1696PUSH10x01
1698PUSH10x01
169aPUSH10xa0
169cSHL
169dSUB
169ePUSH20x16a5
16a1PUSH20x4021
16a4JUMP
16a5JUMPDEST
16a6AND
16a7PUSH0
16a8MSTORE
16a9PUSH10x03
16abPUSH10x20
16adMSTORE
16aePUSH20x16d0
16b1PUSH20x16bc
16b4PUSH10x40
16b6PUSH0
16b7KECCAK256
16b8PUSH20x42f4
16bbJUMP
16bcJUMPDEST
16bdPUSH10x40
16bfMLOAD
16c0SWAP2
16c1DUP3
16c2SWAP2
16c3PUSH10x20
16c5DUP4
16c6MSTORE
16c7PUSH10x20
16c9DUP4
16caADD
16cbSWAP1
16ccPUSH20x407a
16cfJUMP
16d0JUMPDEST
16d1SUB
16d2SWAP1
16d3RETURN
16d4JUMPDEST
16d5CALLVALUE
16d6PUSH20x1384
16d9JUMPI
16daPUSH10x40
16dcCALLDATASIZE
16ddPUSH10x03
16dfNOT
16e0ADD
16e1SLT
16e2PUSH20x1384
16e5JUMPI
16e6PUSH10x01
16e8PUSH10x01
16eaPUSH10xa0
16ecSHL
16edSUB
16eePUSH20x16f5
16f1PUSH20x4021
16f4JUMP
16f5JUMPDEST
16f6AND
16f7PUSH0
16f8MSTORE
16f9PUSH10x15
16fbPUSH10x20
16fdMSTORE
16fePUSH10x20
1700PUSH10x01
1702DUP1
1703PUSH10xa0
1705SHL
1706SUB
1707PUSH10x40
1709PUSH0
170aKECCAK256
170bSLOAD
170cAND
170dDUP1
170eISZERO
170fISZERO
1710SWAP1
1711DUP2
1712PUSH20x1721
1715JUMPI
1716JUMPDEST
1717POP
1718PUSH10x40
171aMLOAD
171bSWAP1
171cISZERO
171dISZERO
171eDUP2
171fMSTORE
1720RETURN
1721JUMPDEST
1722PUSH20x172f
1725SWAP2
1726POP
1727PUSH10x24
1729CALLDATALOAD
172aSWAP1
172bPUSH20x44ca
172eJUMP
172fJUMPDEST
1730DUP3
1731PUSH20x1716
1734JUMP