Final Testnetexplorer K_J · Final Testnet · 48359
en

Contract

0xb456559b6870a4212cab022b8a106d0879c08fc2

Address
0xb456559b6870a4212cab022b8a106d0879c08fc2
Kind
verified contract FinalPhiSupply
Balance
0 vETH
Nonce
1
Code
9,831 bytes codehash 0x8048e44c03d2f7f4b5f7c4ee98abf4fe789a4451d33658f488158d96dddc6671

account tree

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

source verified

Contract
FinalPhiSupply exact match · immutables masked
Compiler
v0.8.33+commit.64118f21
Optimizer
enabled · 200 runs
EVM version
prague
Verified
2026-09-10T07:10:08.429Z
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/FinalPhiSupply.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 supply record as part
//    of a Final DeFi Protocol chain, and may record issuance and movement in
//    it under the authority the chain recognises.
// 2. Integrators, auditors, and indexers may read the supply accounting it
//    keeps, 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 supply record or a competing token-issuance
//    plane derived from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.24;

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

/**
 * @title FinalPhiSupply
 * @notice The 100,000,000 PHI, and the only place that can see all of them.
 *
 * @dev ## What this exists to fix
 *
 * `PHIToken` on every execution chain deploys with no supply and can only gain
 * it through `spawn`, so a chain can never hold more than Final Chain issued it.
 * That property is local and structural. The GLOBAL one was not:
 *
 * > The global cap still cannot be enforced on-chain — no chain sees the others
 * > — so it rests on this ledger's integrity.
 *
 * Final Chain does see the others, because it is what issues to them. This
 * contract is that view made into an invariant: every chain's outstanding
 * allocation is a number here, and no path exists that changes one without
 * changing `held` by the same amount in the opposite direction.
 *
 *     held + Σ over chains of allocatedTo[chainRef]  ==  TOTAL_SUPPLY
 *
 * Checked after every mutation and reverted on violation, so "100M is fixed"
 * stops being a property the backend must not break and becomes one it cannot.
 *
 * ## What this is NOT
 *
 * **Not a token, and deliberately.** There are no per-wallet balances here. A
 * wallet's PHI is the **tree 2** record — `available`, the lock and its
 * exposures per `(wallet, chainId)`, published by the PHI publishers from the
 * execution chains and the admitted intents. Holding balances here as well
 * would make two records of one asset that mutate independently. This contract
 * knows totals per chain and nothing about who holds them — and its invariant
 * IS the conservation of PHI: nothing off-chain re-derives it, and a chain's
 * supply lagging its allocation during a transfer is the order of operations,
 * not drift.
 *
 * **Not the gas token.** This chain charges gas in vETH. PHI being native is
 * for Final Chain proper; here it would put a wallet's PHI in two places — its
 * native balance and its tree-2 leaf — and the base fee would burn supply on
 * every transaction, since EIP-1559 destroys it rather than paying it out.
 *
 * ## Cadence: this is not on the hot path
 *
 * `spawn` and `despawn` run on the **scheduled per-chain reconciliation**, sized
 * to bring that chain's paymaster float back to its 2% target — not per
 * operation, and not per liquidation.
 *
 * **The 2% is of THAT CHAIN's PHI in use, not of the 100M in circulation.**
 * The two read almost identically and compute very differently: on a chain
 * holding 1M PHI the target is 20,000, not the 2,000,000 that 2% of the global
 * supply would give — a hundred times the float, drawn off every other chain to
 * sit idle. Per chain, the float scales with that chain's own allocation and
 * needs no re-tuning as allocation shifts between chains, which is the reason
 * it is a fraction rather than a fixed amount. `allocatedTo[chainRef]` is the
 * base; `totalAllocated` is never the base.
 *
 * That is what makes liquidations and manual reconciliations instant on the
 * chain they happen on: between sweeps the paymaster serves them out of float,
 * and Final Chain is not in the loop at all. Wiring either entrypoint per
 * operation would put a quorum round trip in front of a liquidation, which is
 * the one path that cannot afford one — and it would do so for no gain, because
 * the float exists precisely so the allocation is already there.
 *
 * Refill is to TARGET, not to zero. A flat float makes the next user pay the
 * same latency, and on a busy chain that turns every operation into a refill.
 *
 * ## Ordering
 *
 * Final Chain decides and the chains follow. A spawn debits `held` HERE first
 * and the remote mint proves against the record this emits, so the destination
 * can never mint ahead of the source debit — which is the one rule
 * "executing-first" does not imply on its own, and the reason
 * `projection.js` exists. Issuing out of Final Chain is safe by construction
 * because the debit is already durable; legacy-to-legacy is the dangerous
 * direction and it routes through here rather than between chains.
 */
contract FinalPhiSupply is FinalPlaneSweep {
    // ------------------------------------------------------------------ types

    /// @notice One chain's outstanding allocation.
    struct Allocation {
        /// @dev Spawned to this chain and not yet returned. Never exceeds
        ///      TOTAL_SUPPLY, because `held` cannot go below zero.
        uint256 outstanding;
        /// @dev Monotonic per chain. What a consumer compares to tell a stale
        ///      record from a current one without needing a round.
        uint64 epoch;
    }

    // -------------------------------------------------------------- constants

    /// @notice 100,000,000 PHI, 18 decimals. Fixed for the life of the protocol.
    ///
    /// @dev A constant rather than a constructor argument for the same reason
    ///      `PHIToken` takes no supply argument: a value someone supplies is a
    ///      value someone can supply twice.
    uint256 public constant TOTAL_SUPPLY = 100_000_000 ether;

    /// @dev Action tag for issuing supply onto a chain.
    bytes32 internal constant ACTION_SPAWN = keccak256("FinalPhiSupply.spawn.v01");
    /// @dev Action tag for retiring supply from a chain. Distinct from the spawn tag, so an approval collected to
    ///       issue can never retire.
    bytes32 internal constant ACTION_DESPAWN = keccak256("FinalPhiSupply.despawn.v01");
    /// @dev Action tag for moving supply between chains.
    bytes32 internal constant ACTION_MOVE = keccak256("FinalPhiSupply.move.v01");

    // ------------------------------------------------------------------ state

    /// @notice The identity registry quorum members are resolved through.
    /// @dev Immutable: it decides who may change supply, so a movable pointer would make the quorum only as
    ///       strong as whoever could re-point it.
    FinalIdentityRegistry public immutable registry;

    /// @dev Can `configure` and `seal`, and nothing else. Zero once sealed.
    /// @dev Registrar-quorum action, verified by the registry with this
    /// contract as the verifying contract.
    bytes32 public constant ACTION_CONFIGURE = keccak256("FINAL_PHI_SUPPLY_CONFIGURE_v01");
    /// @dev Registrar-quorum action: a fresh supply taking over the previous
    /// supply's ledger (the NO-WIPE redeploy).
    bytes32 public constant ACTION_SEED = keccak256("FINAL_PHI_SUPPLY_SEED_v01");

    /// @dev Bootstrap admin, cleared permanently by sealing.
    address public admin;

    /// @dev `ROLE_PHI_PUBLISHER`. The role bit itself is the registry's, so
    ///      membership changes there and not here.
    uint256 public publisherRole;

    /// @dev How many of the quorum must approve. Follows the fleet rule of
    ///      floor(2N/3) — three of five — rather than being tuned per contract.
    ///
    ///      Settable rather than immutable for one reason: the roster grows. A
    ///      threshold fixed at deploy against three members stays 2-of-N when
    ///      the fleet reaches five, and a quorum that does not track its roster
    ///      weakens silently as the roster it guards gets larger.
    uint256 public threshold;

    /// @notice PHI on Final Chain, allocated to no execution chain.
    uint256 public held;

    /// @notice Per-chain outstanding allocation, by CAIP-style chain reference.
    mapping(bytes32 => Allocation) public allocatedTo;

    /// @notice The sum of every `allocatedTo[*].outstanding`.
    ///
    /// @dev Maintained incrementally rather than summed on read: the mapping
    ///      cannot be iterated, and an invariant that can only be checked by an
    ///      off-chain sweep is not an invariant.
    uint256 public totalAllocated;

    /// @dev Replay protection for the quorum digest.
    uint64 public nonce;

    /// @dev Consumed-once per `(chainRef, originSeqId)`. One despawn on a chain
    ///      can never become two credits here — the mirror of the same rule
    ///      `PHIToken.spawn` enforces in the other direction.
    mapping(bytes32 => mapping(uint256 => bool)) public consumedDespawn;

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

    /// @notice Thrown when a bootstrap-only entrypoint is reached by anyone but the admin.
    /// @param caller The rejected caller.
    error NotAdmin(address caller);
    /// @notice Thrown when the requested threshold exceeds the live role membership.
    /// @param live Members currently holding the role.
    /// @param wanted The threshold requested.
    error ThresholdUnreachable(uint256 live, uint256 wanted);
    /// @notice Thrown when supply is changed before the role and threshold are configured.
    error NotConfigured();
    /// @notice Thrown when a supply operation carries no amount.
    error ZeroAmount();
    /// @notice Thrown when an operation would issue more than the supply holds unallocated.
    /// @param want The amount requested.
    /// @param have The amount available.
    error InsufficientHeld(uint256 want, uint256 have);
    /// @notice Thrown when an operation would retire more from a chain than that chain holds.
    /// @param chainRef The chain.
    /// @param want The amount requested.
    /// @param have The amount outstanding there.
    error InsufficientAllocation(bytes32 chainRef, uint256 want, uint256 have);
    /// @notice Thrown when an origin sequence id that has already been settled is settled again.
    /// @dev Consumption is recorded per origin id, which is what stops one cross-chain operation being counted
    ///       twice and minting supply the origin never retired.
    /// @param chainRef The origin chain.
    /// @param originSeqId The sequence id already consumed.
    error AlreadyConsumed(bytes32 chainRef, uint256 originSeqId);
    /// @notice Thrown when the supply's own accounting no longer balances.
    /// @dev Checked rather than assumed: everything downstream treats this contract's numbers as the definition
    ///       of how much exists, so an imbalance must halt rather than propagate.
    /// @param held Total the supply accounts for.
    /// @param allocated Total allocated across chains.
    error SupplyInvariantBroken(uint256 held, uint256 allocated);
    /// @notice A move names the same chain twice, or no chain.
    error InvalidMove(bytes32 source, bytes32 target);
    /// @notice The ledger can be seeded only into a fresh supply.
    error NotFresh();
    /// @notice `seed`'s parallel arrays disagree in length, or a ref is zero
    ///         or carries epoch 0 (a ref the old supply never touched).
    error SeedShapeMismatch();
    /// @notice `seed` names one chain ref twice.
    error SeedRefRepeated(bytes32 chainRef);

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

    /// @notice Supply was issued onto a chain.
    /// @param chainRef The chain.
    /// @param amount The amount issued.
    /// @param outstanding The chain's total after this issuance.
    /// @param epoch The supply epoch this issuance carries.
    /// @param seq The sequence id downstream consumers settle against.
    event Spawned(bytes32 indexed chainRef, uint256 amount, uint256 outstanding, uint64 epoch, uint64 seq);
    /// @notice Supply was retired from a chain.
    /// @param chainRef The chain.
    /// @param amount The amount retired.
    /// @param outstanding The chain's total after this retirement.
    /// @param epoch The supply epoch this retirement carries.
    /// @param originSeqId The origin sequence id this retirement settles.
    event Despawned(bytes32 indexed chainRef, uint256 amount, uint256 outstanding, uint64 epoch, uint256 originSeqId);
    /// @notice Allocation moved between two execution chains in one record.
    /// `seq` names the move for the target's spawn and the source's closing
    /// despawn; `held` is untouched.
    event Moved(
        bytes32 indexed source,
        bytes32 indexed target,
        uint256 amount,
        uint256 sourceOutstanding,
        uint256 targetOutstanding,
        uint64 seq
    );
    /// @notice The publisher role and threshold were configured.
    /// @param role Role whose members may change supply.
    /// @param threshold Approvals a change requires.
    event SupplyConfigured(uint256 role, uint256 threshold);
    /// @notice The bootstrap admin was cleared, permanently. Every later change requires the quorum.
    event Sealed();
    /// @notice A fresh supply took over the previous supply's ledger.
    event Seeded(uint256 allocations, uint256 despawns, uint64 nonce);

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

    /// @notice Binds the supply to its identity registry and bootstrap admin.
    /// @param registry_ The registry quorum members are resolved through. Immutable.
    /// @param admin_ The bootstrap admin, cleared by sealing.
    constructor(FinalIdentityRegistry registry_, address admin_) {
        registry = registry_;
        admin = admin_;

        // The whole supply starts here, unallocated. This is the "created once"
        // in "created once, on Final Chain" — and it happens exactly once,
        // because a constant cannot be passed twice.
        held = TOTAL_SUPPLY;
    }

    // ------------------------------------------------------------- bootstrap

    /**
     * @notice Point at the publisher role and set how many of it must approve.
     *
     * @dev Refuses a threshold the roster cannot reach. Without that check the
     *      failure is not a revert here but a `spawn` that no set of signatures
     *      can ever satisfy — supply frozen by a typo, and discovered at the
     *      first reconciliation rather than at configure time.
     */
    function configure(
        uint256 role,
        uint256 k,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        // The admin alone while this contract's window is open; the sealed
        // `ROLE_REGISTRAR` quorum afterwards, exactly as on the registry and
        // the trees. Before the quorum path existed, `seal()` froze this
        // configuration forever — a publisher set that could never re-threshold.
        if (msg.sender != admin) {
            registry.requireRegistrarQuorum(
                ACTION_CONFIGURE, keccak256(abi.encode(role, k)), anchorBlock, approvals
            );
        }
        if (k != 0) {
            uint256 live = registry.liveMemberCount(role);
            if (live < k) revert ThresholdUnreachable(live, k);
        }
        publisherRole = role;
        threshold = k;
        emit SupplyConfigured(role, k);
    }

    /// @notice Close the bootstrap window. One way.
    function seal() external {
        if (msg.sender != admin) revert NotAdmin(msg.sender);
        admin = address(0);
        emit Sealed();
    }

    /**
     * @notice Take over the previous supply's ledger: every chain's outstanding
     *         allocation and epoch, every consumed despawn, and the nonce — so
     *         a redeployed supply says exactly what the old one said, and every
     *         `seq` it will ever issue stays above every seq a `PHIToken` has
     *         already consumed. A redeploy does NOT wipe: this lane is how a
     *         fresh supply is brought up already ahead of every consumer,
     *         rather than behind them.
     * @dev Only into a fresh supply (nothing allocated, nonce 0). The
     *      registry's bootstrap admin inside its window, the sealed
     *      `ROLE_REGISTRAR` quorum afterwards — the door every other
     *      state-plane seed uses. `held` is DERIVED (`TOTAL_SUPPLY - Σ
     *      outstanding`) and the invariant asserted, so a list that does not
     *      add up reverts rather than seeds.
     */
    function seed(
        bytes32[] calldata refs,
        uint256[] calldata outstanding,
        uint64[] calldata epochs,
        bytes32[] calldata despawnRefs,
        uint256[] calldata despawnSeqs,
        uint64 nonce_,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (registry.bootstrapSealed() || msg.sender != registry.bootstrapAdmin()) {
            registry.requireRegistrarQuorum(
                ACTION_SEED,
                keccak256(abi.encode(refs, outstanding, epochs, despawnRefs, despawnSeqs, nonce_)),
                anchorBlock,
                approvals
            );
        }
        if (nonce != 0 || totalAllocated != 0) revert NotFresh();
        if (refs.length != outstanding.length || refs.length != epochs.length) revert SeedShapeMismatch();
        if (despawnRefs.length != despawnSeqs.length) revert SeedShapeMismatch();
        uint256 allocated;
        for (uint256 i = 0; i < refs.length; i++) {
            if (refs[i] == bytes32(0) || epochs[i] == 0) revert SeedShapeMismatch();
            Allocation storage a = allocatedTo[refs[i]];
            if (a.epoch != 0) revert SeedRefRepeated(refs[i]);
            a.outstanding = outstanding[i];
            a.epoch = epochs[i];
            allocated += outstanding[i];
        }
        if (allocated > TOTAL_SUPPLY) revert InsufficientHeld(allocated, TOTAL_SUPPLY);
        totalAllocated = allocated;
        held = TOTAL_SUPPLY - allocated;
        for (uint256 i = 0; i < despawnRefs.length; i++) {
            consumedDespawn[despawnRefs[i]][despawnSeqs[i]] = true;
        }
        nonce = nonce_;
        _assertInvariant();
        emit Seeded(refs.length, despawnRefs.length, nonce_);
    }

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

    /// @notice The invariant, as a number a caller can check without trusting us.
    function accountedSupply() external view returns (uint256) {
        return held + totalAllocated;
    }

    /// @notice How much supply is outstanding on one chain.
    /// @param chainRef The chain.
    /// @return The amount outstanding there.
    function outstandingOn(bytes32 chainRef) external view returns (uint256) {
        return allocatedTo[chainRef].outstanding;
    }

    // ------------------------------------------------------------- mutations

    /**
     * @notice Allocate PHI to an execution chain. The remote mint proves against
     *         the `Spawned` record this emits.
     *
     * @dev Debits `held` BEFORE anything can mint remotely, which is what makes
     *      "never mint on the destination before the burn on the source is
     *      confirmed" hold for this direction by construction.
     */
    function spawn(
        bytes32 chainRef,
        uint256 amount,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external returns (uint64 seq) {
        uint256 k = threshold;
        if (k == 0) revert NotConfigured();
        if (amount == 0) revert ZeroAmount();
        if (amount > held) revert InsufficientHeld(amount, held);

        uint64 n = nonce;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this), ACTION_SPAWN, anchorBlock, keccak256(abi.encode(n, chainRef, amount))
            ),
            publisherRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce = n + 1;

        Allocation storage a = allocatedTo[chainRef];
        unchecked {
            // `amount <= held` was checked, and `totalAllocated + amount` cannot
            // exceed TOTAL_SUPPLY for the same reason.
            held -= amount;
            a.outstanding += amount;
            totalAllocated += amount;
        }
        a.epoch += 1;

        _assertInvariant();
        emit Spawned(chainRef, amount, a.outstanding, a.epoch, n);
        return n;
    }

    /**
     * @notice Return PHI from an execution chain, against a burn already
     *         performed there.
     *
     * @dev `originSeqId` is the despawn's sequence on the source chain and is
     *      consumed once. Gaps and out-of-order arrival are both normal — the
     *      source sequences, this does not re-order.
     */
    function despawn(
        bytes32 chainRef,
        uint256 amount,
        uint256 originSeqId,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        uint256 k = threshold;
        if (k == 0) revert NotConfigured();
        if (amount == 0) revert ZeroAmount();
        if (consumedDespawn[chainRef][originSeqId]) revert AlreadyConsumed(chainRef, originSeqId);

        Allocation storage a = allocatedTo[chainRef];
        if (amount > a.outstanding) revert InsufficientAllocation(chainRef, amount, a.outstanding);

        uint64 n = nonce;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this),
                ACTION_DESPAWN,
                anchorBlock,
                keccak256(abi.encode(n, chainRef, amount, originSeqId))
            ),
            publisherRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce = n + 1;
        consumedDespawn[chainRef][originSeqId] = true;

        unchecked {
            a.outstanding -= amount;
            totalAllocated -= amount;
            held += amount;
        }
        a.epoch += 1;

        _assertInvariant();
        emit Despawned(chainRef, amount, a.outstanding, a.epoch, originSeqId);
    }

    /**
     * @notice Move allocation from one execution chain to another, in one record.
     *
     * @dev **Final Chain leads.** A cross-chain PHI transfer is: pre-approved
     *      intent → transfer lock on the source → finality → THIS → the target
     *      spawns → the source burns as the CLOSING step. Both allocations
     *      change here, together, before either chain acts, and `held` is
     *      untouched — the PHI is not leaving circulation, it is changing where
     *      it sits. Recorded as one mutation so there is no instant at which the
     *      per-chain amounts sum to anything but the same total.
     *
     *      `spawn` / `despawn` stay for reconciliation, where `held` does move.
     *      Routing a transfer through them instead would pass through `held`
     *      and make a legacy-to-legacy move look, for one transaction, like a
     *      return to Final Chain that it is not.
     */
    function move(
        bytes32 source,
        bytes32 target,
        uint256 amount,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external returns (uint64 seq) {
        uint256 k = threshold;
        if (k == 0) revert NotConfigured();
        if (amount == 0) revert ZeroAmount();
        if (source == bytes32(0) || target == bytes32(0) || source == target) revert InvalidMove(source, target);

        Allocation storage from = allocatedTo[source];
        if (amount > from.outstanding) revert InsufficientAllocation(source, amount, from.outstanding);

        uint64 n = nonce;
        FinalPqQuorum.require_(
            registry,
            approvals,
            FinalPqQuorum.digest(
                address(this), ACTION_MOVE, anchorBlock, keccak256(abi.encode(n, source, target, amount))
            ),
            publisherRole,
            k,
            FinalPqQuorum.ALG_ML_DSA_87,
            anchorBlock,
            false
        );
        nonce = n + 1;

        Allocation storage to = allocatedTo[target];
        unchecked {
            // `amount <= from.outstanding` was checked; `totalAllocated` is
            // unchanged by construction, so the invariant cannot move.
            from.outstanding -= amount;
            to.outstanding += amount;
        }
        from.epoch += 1;
        to.epoch += 1;

        _assertInvariant();
        emit Moved(source, target, amount, from.outstanding, to.outstanding, n);
        return n;
    }

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

    /**
     * @dev The reason this contract exists, checked on every path that moves a
     *      number. Both mutations are written to preserve it by construction, so
     *      a revert here means a bug in this contract rather than bad input —
     *      which is exactly when an assertion earns its gas.
     */
    function _assertInvariant() internal view {
        if (held + totalAllocated != TOTAL_SUPPLY) revert SupplyInvariantBroken(held, totalAllocated);
    }

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

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

    /// @dev The local `admin` first — the same address this contract's own
    /// configuration gate accepts ahead of the registrar quorum — then the
    /// plane rule. Zero once sealed, and `msg.sender` can never be zero, so the
    /// leg closes with the window it belongs to.
    function _requireSweepAuthority() internal view override {
        if (admin != address(0) && msg.sender == admin) return;
        super._requireSweepAuthority();
    }

    /// @dev The local admin, and the proven authority that called. The registry's
    /// bootstrap admin is not named here because this contract answers to its
    /// own admin during the window and to the registrar roster after it.
    function _sweepDestinations() internal view override returns (address, address) {
        return (admin, msg.sender);
    }

    /**
     * @dev Nothing is reserved, because this contract holds nothing to reserve.
     *
     * `held`, `allocatedTo` and `totalAllocated` are LEDGER NUMBERS over the
     * fixed 100M global supply — where PHI sits, not PHI sitting here. PHI is
     * this chain's native asset and the allocation this contract accounts for
     * lives in holders' balances and in the execution chains' `PHIToken`
     * supplies; a spawn debits `held` and credits a chain, and neither leg
     * moves value through this address. There is no payable entrypoint and no
     * custody line, so any balance this contract carries arrived by accident
     * and is stray in full.
     */
    function _sweepReserved(SweepKind, address, uint256) internal pure override returns (uint256) {
        return 0;
    }
}

contracts/finalchain/FinalPlaneSweep.sol

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

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

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

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

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

contracts/finalchain/FinalPqQuorum.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            valid++;
        }

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

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

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

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

contracts/utils/FinalSweep.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

abi

[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "registry_",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      },
      {
        "name": "admin_",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "ACTION_CONFIGURE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SEED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TOTAL_SUPPLY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "accountedSupply",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "admin",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "allocatedTo",
    "inputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "outstanding",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "configure",
    "inputs": [
      {
        "name": "role",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "k",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "consumedDespawn",
    "inputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "despawn",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "originSeqId",
        "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": "held",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "move",
    "inputs": [
      {
        "name": "source",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "target",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "amount",
        "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": [
      {
        "name": "seq",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "nonce",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "onERC1155BatchReceived",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC1155Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "onERC721Received",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "outstandingOn",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "publisherRole",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registry",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "seal",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "seed",
    "inputs": [
      {
        "name": "refs",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "outstanding",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "epochs",
        "type": "uint64[]",
        "internalType": "uint64[]"
      },
      {
        "name": "despawnRefs",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "despawnSeqs",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "nonce_",
        "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": "spawn",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "amount",
        "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": [
      {
        "name": "seq",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "sweepAsset",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "moved",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "sweepableSurplus",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "surplus",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "threshold",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "totalAllocated",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "event",
    "name": "AssetSwept",
    "inputs": [
      {
        "name": "kind",
        "type": "uint8",
        "indexed": true,
        "internalType": "enum SweepKind"
      },
      {
        "name": "asset",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "id",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "Despawned",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "outstanding",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "originSeqId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "Moved",
    "inputs": [
      {
        "name": "source",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "target",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "sourceOutstanding",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "targetOutstanding",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "seq",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "Sealed",
    "inputs": [],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "Seeded",
    "inputs": [
      {
        "name": "allocations",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "despawns",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "nonce",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "Spawned",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "outstanding",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "epoch",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "seq",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "SupplyConfigured",
    "inputs": [
      {
        "name": "role",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "threshold",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "error",
    "name": "AlreadyConsumed",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "originSeqId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "AnchorAhead",
    "inputs": [
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "blockNumber",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "AnchorStale",
    "inputs": [
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "blockNumber",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSeal",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "BadSignature",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "algorithm",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "InsufficientAllocation",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "want",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "have",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "InsufficientHeld",
    "inputs": [
      {
        "name": "want",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "have",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "InvalidMove",
    "inputs": [
      {
        "name": "source",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "target",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAdmin",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotConfigured",
    "inputs": []
  },
  {
    "type": "error",
    "name": "NotFresh",
    "inputs": []
  },
  {
    "type": "error",
    "name": "SeedRefRepeated",
    "inputs": [
      {
        "name": "chainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "SeedShapeMismatch",
    "inputs": []
  },
  {
    "type": "error",
    "name": "SignerLacksRole",
    "inputs": [
      {
        "name": "signer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "roleMask",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SignersNotAscending",
    "inputs": [
      {
        "name": "previous",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "next",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SupplyInvariantBroken",
    "inputs": [
      {
        "name": "held",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "allocated",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepAboveSurplus",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "requested",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "surplus",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepDestinationNotAllowed",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepTransferFailed",
    "inputs": [
      {
        "name": "asset",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepUnauthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "SweepZeroAmount",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ThresholdIsZero",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ThresholdNotMet",
    "inputs": [
      {
        "name": "valid",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "ThresholdUnreachable",
    "inputs": [
      {
        "name": "live",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "wanted",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "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": "ZeroAmount",
    "inputs": []
  }
]

read contract

bytecode · 9,831 bytes

0x6101a0806040526004361015610013575f80fd5b5f610160525f3560e01c9081620b82b5146119c95750806307a96b5c14611778578063150b7a02146117225780633fb27b85146116b457806342cde4e81461169757806345f7f2491461167a57806360a180081461164657806372f56b2c1461160c5780637b103999146115c85780638d0de2411461158c5780638eae91b214611563578063902d55a51461153e57806394bc4e961461125e57806396f51f3a14610fbb578063a1aab18c146107f1578063a285aed7146107d1578063a2c7376c146105dd578063affed0e0146105b4578063b19f480514610577578063bc197c81146104df578063e1c783cb146104af578063e96b9491146101de578063f23a6e6114610188578063f4e885db146101685763f851a44014610134575f80fd5b3461016157610160513660031901126101615761016051546040516001600160a01b039091168152602090f35b6101605180fd5b346101615761016051366003190112610161576020600154604051908152f35b346101615760a0366003190112610161576101a1611a5b565b506101aa611a71565b506084356001600160401b038111610161576101ca903690600401611a9b565b505060405163f23a6e6160e01b8152602090f35b346101615760a0366003190112610161576044356004356024356102006119ff565b916084356001600160401b03811161016157610220903690600401611a2b565b939060025491821561049a578615610485578315801561047d575b8015610474575b6104565783610160515260046020526040610160512092835480891161043657509260809261037c602099937f918413a673b69ea329c13b81535c07cd4e55e8544a26871a6f0d841f203af09996600654936001600160401b0385169b8c6040518f81019182528c60408201528d6060820152898b8201528a81526102c860a082611ac8565b51902061016051506040518f8101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527f5315ade0aa736cdc5532fa19a1ed12e3ce89b6ad07040b14bac780938e50423b8c8301526001600160401b03871660a083015260c082015260c0815261034e60e082611ac8565b51902090600154927f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf611ca5565b506001600160401b0361038e89611afd565b16906001600160401b03191617600655856101605152600488526040610160512090828154038155828254018255600181016001600160401b036103d481835416611afd565b166001600160401b0319825416179055600182016001600160401b036103fc81835416611afd565b166001600160401b0319825416179055610414611f17565b54905490604051928352888301526040820152856060820152a3604051908152f35b8886637c06acb760e11b6101605152600452602452604452606461016051fd5b50505063108b161760e01b6101605152600452602452604461016051fd5b50848414610242565b50841561023b565b631f2a200560e01b6101605152600461016051fd5b63d311bc3960e01b6101605152600461016051fd5b34610161576020366003190112610161576004356101605152600460205260206040610160512054604051908152f35b346101615760a0366003190112610161576104f8611a5b565b50610501611a71565b506044356001600160401b03811161016157610521903690600401611a2b565b50506064356001600160401b03811161016157610542903690600401611a2b565b50506084356001600160401b03811161016157610563903690600401611a9b565b505060405163bc197c8160e01b8152602090f35b3461016157610160513660031901126101615760206040517fbc5858e168b959a61a8fb2d7957ef31dbed683a362770ca030e5d772cc44e0688152f35b3461016157610160513660031901126101615760206001600160401b0360065416604051908152f35b34610161576080366003190112610161576004356024356105fc611a15565b916064356001600160401b0381116101615761061c903690600401611a2b565b929060025494851561049a57821561048557600354958684116107b4579560809261071f7f4565741fc5c16ad3a3a3fbbf4311621df9850edf4097f549079a6855d21331359593602099600654936001600160401b0385169a6040518d8101908d82528c6040820152896060820152606081526106998b82611ac8565b51902061016051506040518e8101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527fef06d5e846d5c9e7b4cf0eb3e3b66d716af68a3cea05cd7bc7743bbc177405228c8301526001600160401b03871660a083015260c082015260c0815261034e60e082611ac8565b506001600160401b0361073188611afd565b16906001600160401b03191617600655846101605152600487528160406101605120910360035581815401815581600554016005556001600160401b0360018201918161078081855416611afd565b168219845416178355610791611f17565b5491541690604051928352878301526040820152846060820152a2604051908152f35b8684631125005160e21b6101605152600452602452604461016051fd5b346101615761016051366003190112610161576020600354604051908152f35b3461016157610100366003190112610161576004356001600160401b03811161016157610822903690600401611a2b565b610180526024356001600160401b03811161016157610845903690600401611a2b565b6044929192356001600160401b03811161016157610867903690600401611a2b565b60a0526080526064356001600160401b0381116101615761088c903690600401611a2b565b61012052610100526084356001600160401b038111610161576108b3903690600401611a2b565b60c05260e05260a4356101408190526001600160401b03811690036101615760c4356001600160401b03811681036101615760e4356001600160401b03811161016157610904903690600401611a2b565b6040516328305db160e21b81529091906020816004817f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03165afa908115610e18576101605191610f8c575b508015610efa575b610c47575b5050506001600160401b036006541615801590610c3c575b610c2757806101805114801590610c18575b610be15760c0516101205103610be1576101605192839291905b610180518410610b0557846a52b7d2dcc80cd2e40000008111610ade57806005556a52b7d2dcc80cd2e4000000036a52b7d2dcc80cd2e40000008111610ac457600355610160515b610120518110610a6f576001600160401b0361014051166001600160401b03196006541617600655610a20611f17565b7fb04da588109787f2e1bce4cc15cc0e414cae26034cea4902695311f75568bf8760606040516101805181526101205160208201526001600160401b0361014051166040820152a16101605180f35b80610a836001926101205161010051611c4b565b356101605152600760205260406101605120610aa48260c05160e051611c4b565b356101605152602052604061016051208260ff19825416179055016109f0565b634e487b7160e01b61016051526011600452602461016051fd5b631125005160e21b61016051526004526a52b7d2dcc80cd2e4000000602452604461016051fd5b90919293610b17856101805186611c4b565b35158015610bf6575b610be157610b32856101805186611c4b565b35610160515260046020526040610160512060018101906001600160401b03825416610bb95791610baf91600193610b6b898888611c4b565b3590556001600160401b03610b8d610b888a60a051608051611c4b565b611c6f565b166001600160401b0319825416179055610ba8878686611c4b565b3590611b48565b94019291906109a8565b610bc887876101805190611c4b565b3563724ac17360e01b6101605152600452602461016051fd5b63200ff7d760e11b6101605152600461016051fd5b506001600160401b03610c11610b888760a051608051611c4b565b1615610b20565b5060a05161018051141561098e565b63dc63d81f60e01b6101605152600461016051fd5b50600554151561097c565b60405160c060208201526020610c7a610c6760e08401610180518a611c27565b838103601f19016040850152878a611c27565b601f1983820301606084015260a05181520181608051610160515b60a0518110610ec8575050610cda610cc3610cfa93601f198482030160808501526101205161010051611c27565b828103601f190160a084015260c05160e051611c27565b6001600160401b03610140511660c083015203601f198101835282611ac8565b80516020909101206001600160a01b037f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf163b156101615782906001600160401b03604051956322f3f44760e11b8752610160515060848701927f08296c4851c7aca93c422c73902d61d179ed1bf52cf0ed257e6d096b9a8bb85160048901526024880152166044860152608060648601525260a4830160a060048460051b86010101928261016051905b828210610e2657505061016051938593508390039150829050837f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03165af18015610e1857610dfd575b8080610964565b61016051610e0a91611ac8565b610160516101615783610df6565b6040513d61016051823e3d90fd5b9091929394609f19600319888303010185528535607e19833603018112156101615782016001600160a01b03610e5b82611a87565b16825260208101359160ff831680930361016157610ebb602092826001958580950152610ead610ea2610e916040850185611b55565b608060408601526080850191611b86565b926060810190611b55565b916060818503910152611b86565b9701950193920190610da5565b91509161016051508235906001600160401b038216809203610161576020816001938293520193019101908391610c95565b5060405163f5778b0360e01b81526020816004817f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03165afa908115610e18576101605191610f5d575b506001600160a01b031633141561095f565b610f7f915060203d602011610f85575b610f778183611ac8565b810190611c08565b87610f4b565b503d610f6d565b610fae915060203d602011610fb4575b610fa68183611ac8565b810190611bf0565b87610957565b503d610f9c565b346101615760a036600319011261016157600435600481101561016157610fe0611a71565b90606435906084356001600160a01b0381169160443591838103610161576110066120a9565b61016051548415906001600160a01b03168115611243575b5061122a5761102e838784611b2f565b945f1981036112255750845b80958115611210578082116111e557506101605191836110f05750506101605180808088885af1611069611bc1565b50156110d4575b6110ba57604080519283526020838101869052956001600160a01b0316927f7643c83e539cea2f6bf506545392e52cfd5f917e327efbcd0ba28f29c28d042e9190a4604051908152f35b634e487b7160e01b61016051526021600452602461016051fd5b6365f4a9ef60e11b610160515261016051600452602461016051fd5b610160519250906001840361114d575060405163a9059cbb60e01b60208201526001600160a01b039091166024820152604481018690526111489061114281606481015b03601f198101835282611ac8565b876120d7565b611070565b610160519692509050600283036111975750506001936111486040516323b872dd60e01b602082015230602482015285604482015284606482015260648152611142608482611ac8565b6111489060409692965190637921219560e11b6020830152306024830152866044830152856064830152608482015260a060a48201526101605160c482015260c4815261114260e482611ac8565b6101608051632190968160e01b90526001600160a01b03891660045260249290925260445251606490fd5b637c2e506f60e11b6101605152600461016051fd5b61103a565b836315150d4d60e31b6101605152600452602461016051fd5b905084141580611254575b8761101e565b503384141561124e565b346113a25760803660031901126113a25760243560043561127d611a15565b6064356001600160401b0381116113a25761129c903690600401611a2b565b5f546001600160a01b031633036113ae575b505050816112f6575b807f43c4ef2494de90aa2f24830e48f3dc8579dec67c48d59b2ffba50c125576d4c4926040926001558060025582519182526020820152a16101605180f35b60405163342f616360e01b8152600481018290526020816024817f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03165afa908115610e18576101605191611378575b5082811061135b57506112b7565b9050633770da3360e11b6101605152600452602452604461016051fd5b90506020813d6020116113a6575b8161139360209383611ac8565b810103126113a257518361134d565b5f80fd5b3d9150611386565b60408051602081018681528183018890529181527f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03169391906113fa606082611ac8565b519020833b156113a25790826001600160401b039593926040519687956322f3f44760e11b875260848701927fbc5858e168b959a61a8fb2d7957ef31dbed683a362770ca030e5d772cc44e06860048901526024880152166044860152608060648601525260a4830160a060048460051b8601010192825f90607e19813603015b8383106114c65750505050505091815f818582965003925af180156114bb576114a6575b80806112ae565b5f6114b091611ac8565b5f610160528261149f565b6040513d5f823e3d90fd5b60a3198a8803018552949650929491939092918635828112156113a25783016001600160a01b036114f682611a87565b16825260208101359160ff83168093036113a25761152c602092826001958580950152610ead610ea2610e916040850185611b55565b9801960193019091889695949261147b565b346113a2575f3660031901126113a25760206040516a52b7d2dcc80cd2e40000008152f35b346113a2575f3660031901126113a257602061158460035460055490611b48565b604051908152f35b346113a25760203660031901126113a2576004355f5260046020526040805f206001600160401b03600182549201541682519182526020820152f35b346113a2575f3660031901126113a2576040517f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03168152602090f35b346113a2575f3660031901126113a25760206040517f08296c4851c7aca93c422c73902d61d179ed1bf52cf0ed257e6d096b9a8bb8518152f35b346113a25760603660031901126113a25760043560048110156113a257611584602091611671611a71565b60443591611b2f565b346113a2575f3660031901126113a2576020600554604051908152f35b346113a2575f3660031901126113a2576020600254604051908152f35b346113a2575f3660031901126113a2575f546001600160a01b038116330361170f576bffffffffffffffffffffffff60a01b165f557f1b2d71eb44f882534bf4e86f940c56ccc869ffb927e2bab86561de93950c22165f80a1005b630bd4212160e11b5f523360045260245ffd5b346113a25760803660031901126113a25761173b611a5b565b50611744611a71565b506064356001600160401b0381116113a257611764903690600401611a9b565b5050604051630a85bd0160e11b8152602090f35b346113a25760a03660031901126113a25760043560243560443561179a6119ff565b6084356001600160401b0381116113a2576117b9903690600401611a2b565b909160025480156119ba5785156119ab57865f52600760205260405f20855f5260205260ff60405f20541661199457865f52600460205260405f2093845480881161197a57506118eb886118e57f3ef380598f06b01333350e61cefa066c10a2088a64ad65aadd22eda04b972b8999979560809997956001600160401b03956006549787891695604051602081019188835260408201528d60608201528c8f8201528e815261186960a082611ac8565b51902060405160208101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301528f7f07e27cdd90594a3caa105e64724b3ff44d247017420d1a3df2cc49fa10cdf0f5908301528a871660a083015260c082015260c0815261034e60e082611ac8565b50611afd565b16906001600160401b03191617600655855f52600760205260405f20825f5260205260405f20600160ff19825416179055828154038155826005540360055582600354016003556001600160401b0360018201918161194c81855416611afd565b16821984541617835561195d611f17565b5491541690604051938452602084015260408301526060820152a2005b8789637c06acb760e11b5f5260045260245260445260645ffd5b8487630dd4fdfd60e21b5f5260045260245260445ffd5b631f2a200560e01b5f5260045ffd5b63d311bc3960e01b5f5260045ffd5b346113a25760403660031901126113a2576020906004355f526007825260405f206024355f52825260ff60405f20541615158152f35b606435906001600160401b03821682036113a257565b604435906001600160401b03821682036113a257565b9181601f840112156113a2578235916001600160401b0383116113a2576020808501948460051b0101116113a257565b600435906001600160a01b03821682036113a257565b602435906001600160a01b03821682036113a257565b35906001600160a01b03821682036113a257565b9181601f840112156113a2578235916001600160401b0383116113a257602083818601950101116113a257565b90601f801991011681019081106001600160401b03821117611ae957604052565b634e487b7160e01b5f52604160045260245ffd5b6001600160401b036001911601906001600160401b038211611b1b57565b634e487b7160e01b5f52601160045260245ffd5b90611b3a9291611f52565b8015611b435790565b505f90565b91908201809211611b1b57565b9035601e19823603018112156113a25701602081359101916001600160401b0382116113a25781360383136113a257565b908060209392818452848401375f828201840152601f01601f1916010190565b6001600160401b038111611ae957601f01601f191660200190565b3d15611beb573d90611bd282611ba6565b91611be06040519384611ac8565b82523d5f602084013e565b606090565b908160209103126113a2575180151581036113a25790565b908160209103126113a257516001600160a01b03811681036113a25790565b81835290916001600160fb1b0383116113a25760209260051b809284830137010190565b9190811015611c5b5760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160401b03811681036113a25790565b356001600160a01b03811681036113a25790565b3560ff811681036113a25790565b94939195965f978515611f08576001600160401b0316438111611ef257804303438111611b1b5761025810611edc575060405193602085015260208452611ced604085611ac8565b5f945f985b888a1015611eb2578960051b840135607e19853603018112156113a257840196611d1b88611c83565b6001600160a01b039182169116811015611e865750611d3987611c83565b96611d7a602087611d4984611c83565b604051632e4bfa5160e11b81526001600160a01b039091166004820152602481019190915291829081906044820190565b03816001600160a01b038e165afa9081156114bb575f91611e68575b5015611e41576020810190600460ff611dae84611c97565b1603611e0e57611dbf88828c612241565b15611dda5750505f198114611b1b576001998a019901611cf2565b90611def611de960ff93611c83565b91611c97565b9063bbf82ba360e01b5f5260018060a01b03166004521660245260445ffd5b90611e1d611de960ff93611c83565b9063587548c360e11b5f5260018060a01b031660045216602452600460445260645ffd5b611e4b8691611c83565b63ae8bb03960e01b5f5260018060a01b031660045260245260445ffd5b611e80915060203d8111610fb457610fa68183611ac8565b5f611d96565b611e8f88611c83565b6311641feb60e21b5f9081526004929092526001600160a01b0316602452604490fd5b98509550955050505050808310611ec65750565b826305bc216760e51b5f5260045260245260445ffd5b630ed38fd160e41b5f526004524360245260445ffd5b637b51505560e01b5f526004524360245260445ffd5b631fc460bf60e11b5f5260045ffd5b600354600554906a52b7d2dcc80cd2e4000000611f348383611b48565b03611f3d575050565b63e0724a2b60e01b5f5260045260245260445ffd5b90600482101561209557811561208e575f9283926001811461206557600214611fdd57604051627eeac760e11b602082019081523060248301526044820192909252611fa18160648101611134565b51915afa611fad611bc1565b9080611fd1575b15611b4357602081519181808201938492010103126113a2575190565b50602081511015611fb4565b60405160208101916331a9108f60e11b8352602482015260248152612003604482611ac8565b51915afa61200f611bc1565b81612057575b8161202a575b501561202657600190565b5f90565b90506020818051810103126113a257602001516001600160a01b038116908190036113a25730145f61201b565b905060208151101590612015565b505060405160208101906370a0823160e01b825230602482015260248152611fa1604482611ac8565b5050504790565b634e487b7160e01b5f52602160045260245ffd5b5f546001600160a01b031680151590816120cd575b506120cb576120cb612386565b565b905033145f6120be565b90813b15612156575f816020829351910182855af16120f4611bc1565b9015908115612126575b506121065750565b6365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b805180151592508261213b575b50505f6120fe565b61214e9250602080918301019101611bf0565b155f80612133565b506365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b6020818303126113a2578051906001600160401b0382116113a2570181601f820112156113a2578051906121aa82611ba6565b926121b86040519485611ac8565b828452602083830101116113a257815f9260208093018386015e8301015290565b903590601e19813603018212156113a257018035906001600160401b0382116113a2576020019181360383136113a257565b92919261221782611ba6565b916122256040519384611ac8565b8294818452818301116113a2578281602093845f960137010152565b9160208201600460ff61225383611c97565b16146123055760ff612266600592611c97565b1614612273575050505f90565b5f61227d83611c83565b604051639e5adaeb60e01b81526001600160a01b0391821660048201529485916024918391165afa9182156114bb576122d6935f936122d9575b506122c98160406122d09301906121d9565b369161220b565b916125eb565b90565b6122d09193506122fd6122c9913d805f833e6122f58183611ac8565b810190612177565b9391506122b7565b505f61231083611c83565b60405163b7af85d760e01b81526001600160a01b0391821660048201529485916024918391165afa9182156114bb576122d6935f93612362575b506122c981604061235c9301906121d9565b91612522565b61235c91935061237e6122c9913d805f833e6122f58183611ac8565b93915061234a565b6040516328305db160e21b81527f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b031690602081600481855afa9081156114bb575f91612503575b5015806124ae575b6124ab5760405163e14c465b60e01b8152602081600481855afa9081156114bb575f91612477575b50604051632e4bfa5160e11b815233600482015260248101919091529060209082908180604481015b03915afa9081156114bb575f91612458575b506120cb5763321cbc0960e21b5f523360045260245ffd5b612471915060203d602011610fb457610fa68183611ac8565b5f612440565b90506020813d6020116124a3575b8161249260209383611ac8565b810103126113a2575161242e612405565b3d9150612485565b50565b5060405163f5778b0360e01b8152602081600481855afa9081156114bb575f916124e4575b506001600160a01b031633146123dd565b6124fd915060203d602011610f8557610f778183611ac8565b5f6124d3565b61251c915060203d602011610fb457610fa68183611ac8565b5f6123d5565b610a208151148015906125de575b6125d75760206125835f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282611ac8565b51906102045afa612592611bc1565b816125cb575b816125a1575090565b90506020815191015190602081106125ba575b50151590565b5f199060200360031b1b165f6125b4565b80516020149150612598565b5050505f90565b5061121383511415612530565b604081511480159061265a575b6125d757602061264b5f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282611ac8565b51906102055afa612592611bc1565b50617460835114156125f856
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
0000PUSH20x01a0
0003DUP1
0004PUSH10x40
0006MSTORE
0007PUSH10x04
0009CALLDATASIZE
000aLT
000bISZERO
000cPUSH20x0013
000fJUMPI
0010PUSH0
0011DUP1
0012REVERT
0013JUMPDEST
0014PUSH0
0015PUSH20x0160
0018MSTORE
0019PUSH0
001aCALLDATALOAD
001bPUSH10xe0
001dSHR
001eSWAP1
001fDUP2
0020PUSH30x0b82b5
0024EQ
0025PUSH20x19c9
0028JUMPI
0029POP
002aDUP1
002bPUSH40x07a96b5c
0030EQ
0031PUSH20x1778
0034JUMPI
0035DUP1
0036PUSH40x150b7a02
003bEQ
003cPUSH20x1722
003fJUMPI
0040DUP1
0041PUSH40x3fb27b85
0046EQ
0047PUSH20x16b4
004aJUMPI
004bDUP1
004cPUSH40x42cde4e8
0051EQ
0052PUSH20x1697
0055JUMPI
0056DUP1
0057PUSH40x45f7f249
005cEQ
005dPUSH20x167a
0060JUMPI
0061DUP1
0062PUSH40x60a18008
0067EQ
0068PUSH20x1646
006bJUMPI
006cDUP1
006dPUSH40x72f56b2c
0072EQ
0073PUSH20x160c
0076JUMPI
0077DUP1
0078PUSH40x7b103999
007dEQ
007ePUSH20x15c8
0081JUMPI
0082DUP1
0083PUSH40x8d0de241
0088EQ
0089PUSH20x158c
008cJUMPI
008dDUP1
008ePUSH40x8eae91b2
0093EQ
0094PUSH20x1563
0097JUMPI
0098DUP1
0099PUSH40x902d55a5
009eEQ
009fPUSH20x153e
00a2JUMPI
00a3DUP1
00a4PUSH40x94bc4e96
00a9EQ
00aaPUSH20x125e
00adJUMPI
00aeDUP1
00afPUSH40x96f51f3a
00b4EQ
00b5PUSH20x0fbb
00b8JUMPI
00b9DUP1
00baPUSH40xa1aab18c
00bfEQ
00c0PUSH20x07f1
00c3JUMPI
00c4DUP1
00c5PUSH40xa285aed7
00caEQ
00cbPUSH20x07d1
00ceJUMPI
00cfDUP1
00d0PUSH40xa2c7376c
00d5EQ
00d6PUSH20x05dd
00d9JUMPI
00daDUP1
00dbPUSH40xaffed0e0
00e0EQ
00e1PUSH20x05b4
00e4JUMPI
00e5DUP1
00e6PUSH40xb19f4805
00ebEQ
00ecPUSH20x0577
00efJUMPI
00f0DUP1
00f1PUSH40xbc197c81
00f6EQ
00f7PUSH20x04df
00faJUMPI
00fbDUP1
00fcPUSH40xe1c783cb
0101EQ
0102PUSH20x04af
0105JUMPI
0106DUP1
0107PUSH40xe96b9491
010cEQ
010dPUSH20x01de
0110JUMPI
0111DUP1
0112PUSH40xf23a6e61
0117EQ
0118PUSH20x0188
011bJUMPI
011cDUP1
011dPUSH40xf4e885db
0122EQ
0123PUSH20x0168
0126JUMPI
0127PUSH40xf851a440
012cEQ
012dPUSH20x0134
0130JUMPI
0131PUSH0
0132DUP1
0133REVERT
0134JUMPDEST
0135CALLVALUE
0136PUSH20x0161
0139JUMPI
013aPUSH20x0160
013dMLOAD
013eCALLDATASIZE
013fPUSH10x03
0141NOT
0142ADD
0143SLT
0144PUSH20x0161
0147JUMPI
0148PUSH20x0160
014bMLOAD
014cSLOAD
014dPUSH10x40
014fMLOAD
0150PUSH10x01
0152PUSH10x01
0154PUSH10xa0
0156SHL
0157SUB
0158SWAP1
0159SWAP2
015aAND
015bDUP2
015cMSTORE
015dPUSH10x20
015fSWAP1
0160RETURN
0161JUMPDEST
0162PUSH20x0160
0165MLOAD
0166DUP1
0167REVERT
0168JUMPDEST
0169CALLVALUE
016aPUSH20x0161
016dJUMPI
016ePUSH20x0160
0171MLOAD
0172CALLDATASIZE
0173PUSH10x03
0175NOT
0176ADD
0177SLT
0178PUSH20x0161
017bJUMPI
017cPUSH10x20
017ePUSH10x01
0180SLOAD
0181PUSH10x40
0183MLOAD
0184SWAP1
0185DUP2
0186MSTORE
0187RETURN
0188JUMPDEST
0189CALLVALUE
018aPUSH20x0161
018dJUMPI
018ePUSH10xa0
0190CALLDATASIZE
0191PUSH10x03
0193NOT
0194ADD
0195SLT
0196PUSH20x0161
0199JUMPI
019aPUSH20x01a1
019dPUSH20x1a5b
01a0JUMP
01a1JUMPDEST
01a2POP
01a3PUSH20x01aa
01a6PUSH20x1a71
01a9JUMP
01aaJUMPDEST
01abPOP
01acPUSH10x84
01aeCALLDATALOAD
01afPUSH10x01
01b1PUSH10x01
01b3PUSH10x40
01b5SHL
01b6SUB
01b7DUP2
01b8GT
01b9PUSH20x0161
01bcJUMPI
01bdPUSH20x01ca
01c0SWAP1
01c1CALLDATASIZE
01c2SWAP1
01c3PUSH10x04
01c5ADD
01c6PUSH20x1a9b
01c9JUMP
01caJUMPDEST
01cbPOP
01ccPOP
01cdPUSH10x40
01cfMLOAD
01d0PUSH40xf23a6e61
01d5PUSH10xe0
01d7SHL
01d8DUP2
01d9MSTORE
01daPUSH10x20
01dcSWAP1
01ddRETURN
01deJUMPDEST
01dfCALLVALUE
01e0PUSH20x0161
01e3JUMPI
01e4PUSH10xa0
01e6CALLDATASIZE
01e7PUSH10x03
01e9NOT
01eaADD
01ebSLT
01ecPUSH20x0161
01efJUMPI
01f0PUSH10x44
01f2CALLDATALOAD
01f3PUSH10x04
01f5CALLDATALOAD
01f6PUSH10x24
01f8CALLDATALOAD
01f9PUSH20x0200
01fcPUSH20x19ff
01ffJUMP
0200JUMPDEST
0201SWAP2
0202PUSH10x84
0204CALLDATALOAD
0205PUSH10x01
0207PUSH10x01
0209PUSH10x40
020bSHL
020cSUB
020dDUP2
020eGT
020fPUSH20x0161
0212JUMPI
0213PUSH20x0220
0216SWAP1
0217CALLDATASIZE
0218SWAP1
0219PUSH10x04
021bADD
021cPUSH20x1a2b
021fJUMP
0220JUMPDEST
0221SWAP4
0222SWAP1
0223PUSH10x02
0225SLOAD
0226SWAP2
0227DUP3
0228ISZERO
0229PUSH20x049a
022cJUMPI
022dDUP7
022eISZERO
022fPUSH20x0485
0232JUMPI
0233DUP4
0234ISZERO
0235DUP1
0236ISZERO
0237PUSH20x047d
023aJUMPI
023bJUMPDEST
023cDUP1
023dISZERO
023ePUSH20x0474
0241JUMPI
0242JUMPDEST
0243PUSH20x0456
0246JUMPI
0247DUP4
0248PUSH20x0160
024bMLOAD
024cMSTORE
024dPUSH10x04
024fPUSH10x20
0251MSTORE
0252PUSH10x40
0254PUSH20x0160
0257MLOAD
0258KECCAK256
0259SWAP3
025aDUP4
025bSLOAD
025cDUP1
025dDUP10
025eGT
025fPUSH20x0436
0262JUMPI
0263POP
0264SWAP3
0265PUSH10x80
0267SWAP3
0268PUSH20x037c
026bPUSH10x20
026dSWAP10
026eSWAP4
026fPUSH320x918413a673b69ea329c13b81535c07cd4e55e8544a26871a6f0d841f203af099
0290SWAP7
0291PUSH10x06
0293SLOAD
0294SWAP4
0295PUSH10x01
0297PUSH10x01
0299PUSH10x40
029bSHL
029cSUB
029dDUP6
029eAND
029fSWAP12
02a0DUP13
02a1PUSH10x40
02a3MLOAD
02a4DUP16
02a5DUP2
02a6ADD
02a7SWAP2
02a8DUP3
02a9MSTORE
02aaDUP13
02abPUSH10x40
02adDUP3
02aeADD
02afMSTORE
02b0DUP14
02b1PUSH10x60
02b3DUP3
02b4ADD
02b5MSTORE
02b6DUP10
02b7DUP12
02b8DUP3
02b9ADD
02baMSTORE
02bbDUP11
02bcDUP2
02bdMSTORE
02bePUSH20x02c8
02c1PUSH10xa0
02c3DUP3
02c4PUSH20x1ac8
02c7JUMP
02c8JUMPDEST
02c9MLOAD
02caSWAP1
02cbKECCAK256
02ccPUSH20x0160
02cfMLOAD
02d0POP
02d1PUSH10x40
02d3MLOAD
02d4DUP16
02d5DUP2
02d6ADD
02d7SWAP2
02d8PUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
02f9DUP4
02faMSTORE
02fbCHAINID
02fcPUSH10x40
02feDUP4
02ffADD
0300MSTORE
0301ADDRESS
0302PUSH10x60
0304DUP4
0305ADD
0306MSTORE
0307PUSH320x5315ade0aa736cdc5532fa19a1ed12e3ce89b6ad07040b14bac780938e50423b
0328DUP13
0329DUP4
032aADD
032bMSTORE
032cPUSH10x01
032ePUSH10x01
0330PUSH10x40
0332SHL
0333SUB
0334DUP8
0335AND
0336PUSH10xa0
0338DUP4
0339ADD
033aMSTORE
033bPUSH10xc0
033dDUP3
033eADD
033fMSTORE
0340PUSH10xc0
0342DUP2
0343MSTORE
0344PUSH20x034e
0347PUSH10xe0
0349DUP3
034aPUSH20x1ac8
034dJUMP
034eJUMPDEST
034fMLOAD
0350SWAP1
0351KECCAK256
0352SWAP1
0353PUSH10x01
0355SLOAD
0356SWAP3
0357PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0378PUSH20x1ca5
037bJUMP
037cJUMPDEST
037dPOP
037ePUSH10x01
0380PUSH10x01
0382PUSH10x40
0384SHL
0385SUB
0386PUSH20x038e
0389DUP10
038aPUSH20x1afd
038dJUMP
038eJUMPDEST
038fAND
0390SWAP1
0391PUSH10x01
0393PUSH10x01
0395PUSH10x40
0397SHL
0398SUB
0399NOT
039aAND
039bOR
039cPUSH10x06
039eSSTORE
039fDUP6
03a0PUSH20x0160
03a3MLOAD
03a4MSTORE
03a5PUSH10x04
03a7DUP9
03a8MSTORE
03a9PUSH10x40
03abPUSH20x0160
03aeMLOAD
03afKECCAK256
03b0SWAP1
03b1DUP3
03b2DUP2
03b3SLOAD
03b4SUB
03b5DUP2
03b6SSTORE
03b7DUP3
03b8DUP3
03b9SLOAD
03baADD
03bbDUP3
03bcSSTORE
03bdPUSH10x01
03bfDUP2
03c0ADD
03c1PUSH10x01
03c3PUSH10x01
03c5PUSH10x40
03c7SHL
03c8SUB
03c9PUSH20x03d4
03ccDUP2
03cdDUP4
03ceSLOAD
03cfAND
03d0PUSH20x1afd
03d3JUMP
03d4JUMPDEST
03d5AND
03d6PUSH10x01
03d8PUSH10x01
03daPUSH10x40
03dcSHL
03ddSUB
03deNOT
03dfDUP3
03e0SLOAD
03e1AND
03e2OR
03e3SWAP1
03e4SSTORE
03e5PUSH10x01
03e7DUP3
03e8ADD
03e9PUSH10x01
03ebPUSH10x01
03edPUSH10x40
03efSHL
03f0SUB
03f1PUSH20x03fc
03f4DUP2
03f5DUP4
03f6SLOAD
03f7AND
03f8PUSH20x1afd
03fbJUMP
03fcJUMPDEST
03fdAND
03fePUSH10x01
0400PUSH10x01
0402PUSH10x40
0404SHL
0405SUB
0406NOT
0407DUP3
0408SLOAD
0409AND
040aOR
040bSWAP1
040cSSTORE
040dPUSH20x0414
0410PUSH20x1f17
0413JUMP
0414JUMPDEST
0415SLOAD
0416SWAP1
0417SLOAD
0418SWAP1
0419PUSH10x40
041bMLOAD
041cSWAP3
041dDUP4
041eMSTORE
041fDUP9
0420DUP4
0421ADD
0422MSTORE
0423PUSH10x40
0425DUP3
0426ADD
0427MSTORE
0428DUP6
0429PUSH10x60
042bDUP3
042cADD
042dMSTORE
042eLOG3
042fPUSH10x40
0431MLOAD
0432SWAP1
0433DUP2
0434MSTORE
0435RETURN
0436JUMPDEST
0437DUP9
0438DUP7
0439PUSH40x7c06acb7
043ePUSH10xe1
0440SHL
0441PUSH20x0160
0444MLOAD
0445MSTORE
0446PUSH10x04
0448MSTORE
0449PUSH10x24
044bMSTORE
044cPUSH10x44
044eMSTORE
044fPUSH10x64
0451PUSH20x0160
0454MLOAD
0455REVERT
0456JUMPDEST
0457POP
0458POP
0459POP
045aPUSH40x108b1617
045fPUSH10xe0
0461SHL
0462PUSH20x0160
0465MLOAD
0466MSTORE
0467PUSH10x04
0469MSTORE
046aPUSH10x24
046cMSTORE
046dPUSH10x44
046fPUSH20x0160
0472MLOAD
0473REVERT
0474JUMPDEST
0475POP
0476DUP5
0477DUP5
0478EQ
0479PUSH20x0242
047cJUMP
047dJUMPDEST
047ePOP
047fDUP5
0480ISZERO
0481PUSH20x023b
0484JUMP
0485JUMPDEST
0486PUSH40x1f2a2005
048bPUSH10xe0
048dSHL
048ePUSH20x0160
0491MLOAD
0492MSTORE
0493PUSH10x04
0495PUSH20x0160
0498MLOAD
0499REVERT
049aJUMPDEST
049bPUSH40xd311bc39
04a0PUSH10xe0
04a2SHL
04a3PUSH20x0160
04a6MLOAD
04a7MSTORE
04a8PUSH10x04
04aaPUSH20x0160
04adMLOAD
04aeREVERT
04afJUMPDEST
04b0CALLVALUE
04b1PUSH20x0161
04b4JUMPI
04b5PUSH10x20
04b7CALLDATASIZE
04b8PUSH10x03
04baNOT
04bbADD
04bcSLT
04bdPUSH20x0161
04c0JUMPI
04c1PUSH10x04
04c3CALLDATALOAD
04c4PUSH20x0160
04c7MLOAD
04c8MSTORE
04c9PUSH10x04
04cbPUSH10x20
04cdMSTORE
04cePUSH10x20
04d0PUSH10x40
04d2PUSH20x0160
04d5MLOAD
04d6KECCAK256
04d7SLOAD
04d8PUSH10x40
04daMLOAD
04dbSWAP1
04dcDUP2
04ddMSTORE
04deRETURN
04dfJUMPDEST
04e0CALLVALUE
04e1PUSH20x0161
04e4JUMPI
04e5PUSH10xa0
04e7CALLDATASIZE
04e8PUSH10x03
04eaNOT
04ebADD
04ecSLT
04edPUSH20x0161
04f0JUMPI
04f1PUSH20x04f8
04f4PUSH20x1a5b
04f7JUMP
04f8JUMPDEST
04f9POP
04faPUSH20x0501
04fdPUSH20x1a71
0500JUMP
0501JUMPDEST
0502POP
0503PUSH10x44
0505CALLDATALOAD
0506PUSH10x01
0508PUSH10x01
050aPUSH10x40
050cSHL
050dSUB
050eDUP2
050fGT
0510PUSH20x0161
0513JUMPI
0514PUSH20x0521
0517SWAP1
0518CALLDATASIZE
0519SWAP1
051aPUSH10x04
051cADD
051dPUSH20x1a2b
0520JUMP
0521JUMPDEST
0522POP
0523POP
0524PUSH10x64
0526CALLDATALOAD
0527PUSH10x01
0529PUSH10x01
052bPUSH10x40
052dSHL
052eSUB
052fDUP2
0530GT
0531PUSH20x0161
0534JUMPI
0535PUSH20x0542
0538SWAP1
0539CALLDATASIZE
053aSWAP1
053bPUSH10x04
053dADD
053ePUSH20x1a2b
0541JUMP
0542JUMPDEST
0543POP
0544POP
0545PUSH10x84
0547CALLDATALOAD
0548PUSH10x01
054aPUSH10x01
054cPUSH10x40
054eSHL
054fSUB
0550DUP2
0551GT
0552PUSH20x0161
0555JUMPI
0556PUSH20x0563
0559SWAP1
055aCALLDATASIZE
055bSWAP1
055cPUSH10x04
055eADD
055fPUSH20x1a9b
0562JUMP
0563JUMPDEST
0564POP
0565POP
0566PUSH10x40
0568MLOAD
0569PUSH40xbc197c81
056ePUSH10xe0
0570SHL
0571DUP2
0572MSTORE
0573PUSH10x20
0575SWAP1
0576RETURN
0577JUMPDEST
0578CALLVALUE
0579PUSH20x0161
057cJUMPI
057dPUSH20x0160
0580MLOAD
0581CALLDATASIZE
0582PUSH10x03
0584NOT
0585ADD
0586SLT
0587PUSH20x0161
058aJUMPI
058bPUSH10x20
058dPUSH10x40
058fMLOAD
0590PUSH320xbc5858e168b959a61a8fb2d7957ef31dbed683a362770ca030e5d772cc44e068
05b1DUP2
05b2MSTORE
05b3RETURN
05b4JUMPDEST
05b5CALLVALUE
05b6PUSH20x0161
05b9JUMPI
05baPUSH20x0160
05bdMLOAD
05beCALLDATASIZE
05bfPUSH10x03
05c1NOT
05c2ADD
05c3SLT
05c4PUSH20x0161
05c7JUMPI
05c8PUSH10x20
05caPUSH10x01
05ccPUSH10x01
05cePUSH10x40
05d0SHL
05d1SUB
05d2PUSH10x06
05d4SLOAD
05d5AND
05d6PUSH10x40
05d8MLOAD
05d9SWAP1
05daDUP2
05dbMSTORE
05dcRETURN
05ddJUMPDEST
05deCALLVALUE
05dfPUSH20x0161
05e2JUMPI
05e3PUSH10x80
05e5CALLDATASIZE
05e6PUSH10x03
05e8NOT
05e9ADD
05eaSLT
05ebPUSH20x0161
05eeJUMPI
05efPUSH10x04
05f1CALLDATALOAD
05f2PUSH10x24
05f4CALLDATALOAD
05f5PUSH20x05fc
05f8PUSH20x1a15
05fbJUMP
05fcJUMPDEST
05fdSWAP2
05fePUSH10x64
0600CALLDATALOAD
0601PUSH10x01
0603PUSH10x01
0605PUSH10x40
0607SHL
0608SUB
0609DUP2
060aGT
060bPUSH20x0161
060eJUMPI
060fPUSH20x061c
0612SWAP1
0613CALLDATASIZE
0614SWAP1
0615PUSH10x04
0617ADD
0618PUSH20x1a2b
061bJUMP
061cJUMPDEST
061dSWAP3
061eSWAP1
061fPUSH10x02
0621SLOAD
0622SWAP5
0623DUP6
0624ISZERO
0625PUSH20x049a
0628JUMPI
0629DUP3
062aISZERO
062bPUSH20x0485
062eJUMPI
062fPUSH10x03
0631SLOAD
0632SWAP6
0633DUP7
0634DUP5
0635GT
0636PUSH20x07b4
0639JUMPI
063aSWAP6
063bPUSH10x80
063dSWAP3
063ePUSH20x071f
0641PUSH320x4565741fc5c16ad3a3a3fbbf4311621df9850edf4097f549079a6855d2133135
0662SWAP6
0663SWAP4
0664PUSH10x20
0666SWAP10
0667PUSH10x06
0669SLOAD
066aSWAP4
066bPUSH10x01
066dPUSH10x01
066fPUSH10x40
0671SHL
0672SUB
0673DUP6
0674AND
0675SWAP11
0676PUSH10x40
0678MLOAD
0679DUP14
067aDUP2
067bADD
067cSWAP1
067dDUP14
067eDUP3
067fMSTORE
0680DUP13
0681PUSH10x40
0683DUP3
0684ADD
0685MSTORE
0686DUP10
0687PUSH10x60
0689DUP3
068aADD
068bMSTORE
068cPUSH10x60
068eDUP2
068fMSTORE
0690PUSH20x0699
0693DUP12
0694DUP3
0695PUSH20x1ac8
0698JUMP
0699JUMPDEST
069aMLOAD
069bSWAP1
069cKECCAK256
069dPUSH20x0160
06a0MLOAD
06a1POP
06a2PUSH10x40
06a4MLOAD
06a5DUP15
06a6DUP2
06a7ADD
06a8SWAP2
06a9PUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
06caDUP4
06cbMSTORE
06ccCHAINID
06cdPUSH10x40
06cfDUP4
06d0ADD
06d1MSTORE
06d2ADDRESS
06d3PUSH10x60
06d5DUP4
06d6ADD
06d7MSTORE
06d8PUSH320xef06d5e846d5c9e7b4cf0eb3e3b66d716af68a3cea05cd7bc7743bbc17740522
06f9DUP13
06faDUP4
06fbADD
06fcMSTORE
06fdPUSH10x01
06ffPUSH10x01
0701PUSH10x40
0703SHL
0704SUB
0705DUP8
0706AND
0707PUSH10xa0
0709DUP4
070aADD
070bMSTORE
070cPUSH10xc0
070eDUP3
070fADD
0710MSTORE
0711PUSH10xc0
0713DUP2
0714MSTORE
0715PUSH20x034e
0718PUSH10xe0
071aDUP3
071bPUSH20x1ac8
071eJUMP
071fJUMPDEST
0720POP
0721PUSH10x01
0723PUSH10x01
0725PUSH10x40
0727SHL
0728SUB
0729PUSH20x0731
072cDUP9
072dPUSH20x1afd
0730JUMP
0731JUMPDEST
0732AND
0733SWAP1
0734PUSH10x01
0736PUSH10x01
0738PUSH10x40
073aSHL
073bSUB
073cNOT
073dAND
073eOR
073fPUSH10x06
0741SSTORE
0742DUP5
0743PUSH20x0160
0746MLOAD
0747MSTORE
0748PUSH10x04
074aDUP8
074bMSTORE
074cDUP2
074dPUSH10x40
074fPUSH20x0160
0752MLOAD
0753KECCAK256
0754SWAP2
0755SUB
0756PUSH10x03
0758SSTORE
0759DUP2
075aDUP2
075bSLOAD
075cADD
075dDUP2
075eSSTORE
075fDUP2
0760PUSH10x05
0762SLOAD
0763ADD
0764PUSH10x05
0766SSTORE
0767PUSH10x01
0769PUSH10x01
076bPUSH10x40
076dSHL
076eSUB
076fPUSH10x01
0771DUP3
0772ADD
0773SWAP2
0774DUP2
0775PUSH20x0780
0778DUP2
0779DUP6
077aSLOAD
077bAND
077cPUSH20x1afd
077fJUMP
0780JUMPDEST
0781AND
0782DUP3
0783NOT
0784DUP5
0785SLOAD
0786AND
0787OR
0788DUP4
0789SSTORE
078aPUSH20x0791
078dPUSH20x1f17
0790JUMP
0791JUMPDEST
0792SLOAD
0793SWAP2
0794SLOAD
0795AND
0796SWAP1
0797PUSH10x40
0799MLOAD
079aSWAP3
079bDUP4
079cMSTORE
079dDUP8
079eDUP4
079fADD
07a0MSTORE
07a1PUSH10x40
07a3DUP3
07a4ADD
07a5MSTORE
07a6DUP5
07a7PUSH10x60
07a9DUP3
07aaADD
07abMSTORE
07acLOG2
07adPUSH10x40
07afMLOAD
07b0SWAP1
07b1DUP2
07b2MSTORE
07b3RETURN
07b4JUMPDEST
07b5DUP7
07b6DUP5
07b7PUSH40x11250051
07bcPUSH10xe2
07beSHL
07bfPUSH20x0160
07c2MLOAD
07c3MSTORE
07c4PUSH10x04
07c6MSTORE
07c7PUSH10x24
07c9MSTORE
07caPUSH10x44
07ccPUSH20x0160
07cfMLOAD
07d0REVERT
07d1JUMPDEST
07d2CALLVALUE
07d3PUSH20x0161
07d6JUMPI
07d7PUSH20x0160
07daMLOAD
07dbCALLDATASIZE
07dcPUSH10x03
07deNOT
07dfADD
07e0SLT
07e1PUSH20x0161
07e4JUMPI
07e5PUSH10x20
07e7PUSH10x03
07e9SLOAD
07eaPUSH10x40
07ecMLOAD
07edSWAP1
07eeDUP2
07efMSTORE
07f0RETURN
07f1JUMPDEST
07f2CALLVALUE
07f3PUSH20x0161
07f6JUMPI
07f7PUSH20x0100
07faCALLDATASIZE
07fbPUSH10x03
07fdNOT
07feADD
07ffSLT
0800PUSH20x0161
0803JUMPI
0804PUSH10x04
0806CALLDATALOAD
0807PUSH10x01
0809PUSH10x01
080bPUSH10x40
080dSHL
080eSUB
080fDUP2
0810GT
0811PUSH20x0161
0814JUMPI
0815PUSH20x0822
0818SWAP1
0819CALLDATASIZE
081aSWAP1
081bPUSH10x04
081dADD
081ePUSH20x1a2b
0821JUMP
0822JUMPDEST
0823PUSH20x0180
0826MSTORE
0827PUSH10x24
0829CALLDATALOAD
082aPUSH10x01
082cPUSH10x01
082ePUSH10x40
0830SHL
0831SUB
0832DUP2
0833GT
0834PUSH20x0161
0837JUMPI
0838PUSH20x0845
083bSWAP1
083cCALLDATASIZE
083dSWAP1
083ePUSH10x04
0840ADD
0841PUSH20x1a2b
0844JUMP
0845JUMPDEST
0846PUSH10x44
0848SWAP3
0849SWAP2
084aSWAP3
084bCALLDATALOAD
084cPUSH10x01
084ePUSH10x01
0850PUSH10x40
0852SHL
0853SUB
0854DUP2
0855GT
0856PUSH20x0161
0859JUMPI
085aPUSH20x0867
085dSWAP1
085eCALLDATASIZE
085fSWAP1
0860PUSH10x04
0862ADD
0863PUSH20x1a2b
0866JUMP
0867JUMPDEST
0868PUSH10xa0
086aMSTORE
086bPUSH10x80
086dMSTORE
086ePUSH10x64
0870CALLDATALOAD
0871PUSH10x01
0873PUSH10x01
0875PUSH10x40
0877SHL
0878SUB
0879DUP2
087aGT
087bPUSH20x0161
087eJUMPI
087fPUSH20x088c
0882SWAP1
0883CALLDATASIZE
0884SWAP1
0885PUSH10x04
0887ADD
0888PUSH20x1a2b
088bJUMP
088cJUMPDEST
088dPUSH20x0120
0890MSTORE
0891PUSH20x0100
0894MSTORE
0895PUSH10x84
0897CALLDATALOAD
0898PUSH10x01
089aPUSH10x01
089cPUSH10x40
089eSHL
089fSUB
08a0DUP2
08a1GT
08a2PUSH20x0161
08a5JUMPI
08a6PUSH20x08b3
08a9SWAP1
08aaCALLDATASIZE
08abSWAP1
08acPUSH10x04
08aeADD
08afPUSH20x1a2b
08b2JUMP
08b3JUMPDEST
08b4PUSH10xc0
08b6MSTORE
08b7PUSH10xe0
08b9MSTORE
08baPUSH10xa4
08bcCALLDATALOAD
08bdPUSH20x0140
08c0DUP2
08c1SWAP1
08c2MSTORE
08c3PUSH10x01
08c5PUSH10x01
08c7PUSH10x40
08c9SHL
08caSUB
08cbDUP2
08ccAND
08cdSWAP1
08ceSUB
08cfPUSH20x0161
08d2JUMPI
08d3PUSH10xc4
08d5CALLDATALOAD
08d6PUSH10x01
08d8PUSH10x01
08daPUSH10x40
08dcSHL
08ddSUB
08deDUP2
08dfAND
08e0DUP2
08e1SUB
08e2PUSH20x0161
08e5JUMPI
08e6PUSH10xe4
08e8CALLDATALOAD
08e9PUSH10x01
08ebPUSH10x01
08edPUSH10x40
08efSHL
08f0SUB
08f1DUP2
08f2GT
08f3PUSH20x0161
08f6JUMPI
08f7PUSH20x0904
08faSWAP1
08fbCALLDATASIZE
08fcSWAP1
08fdPUSH10x04
08ffADD
0900PUSH20x1a2b
0903JUMP
0904JUMPDEST
0905PUSH10x40
0907MLOAD
0908PUSH40x28305db1
090dPUSH10xe2
090fSHL
0910DUP2
0911MSTORE
0912SWAP1
0913SWAP2
0914SWAP1
0915PUSH10x20
0917DUP2
0918PUSH10x04
091aDUP2
091bPUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
093cPUSH10x01
093ePUSH10x01
0940PUSH10xa0
0942SHL
0943SUB
0944AND
0945GAS
0946STATICCALL
0947SWAP1
0948DUP2
0949ISZERO
094aPUSH20x0e18
094dJUMPI
094ePUSH20x0160
0951MLOAD
0952SWAP2
0953PUSH20x0f8c
0956JUMPI
0957JUMPDEST
0958POP
0959DUP1
095aISZERO
095bPUSH20x0efa
095eJUMPI
095fJUMPDEST
0960PUSH20x0c47
0963JUMPI
0964JUMPDEST
0965POP
0966POP
0967POP
0968PUSH10x01
096aPUSH10x01
096cPUSH10x40
096eSHL
096fSUB
0970PUSH10x06
0972SLOAD
0973AND
0974ISZERO
0975DUP1
0976ISZERO
0977SWAP1
0978PUSH20x0c3c
097bJUMPI
097cJUMPDEST
097dPUSH20x0c27
0980JUMPI
0981DUP1
0982PUSH20x0180
0985MLOAD
0986EQ
0987DUP1
0988ISZERO
0989SWAP1
098aPUSH20x0c18
098dJUMPI
098eJUMPDEST
098fPUSH20x0be1
0992JUMPI
0993PUSH10xc0
0995MLOAD
0996PUSH20x0120
0999MLOAD
099aSUB
099bPUSH20x0be1
099eJUMPI
099fPUSH20x0160
09a2MLOAD
09a3SWAP3
09a4DUP4
09a5SWAP3
09a6SWAP2
09a7SWAP1
09a8JUMPDEST
09a9PUSH20x0180
09acMLOAD
09adDUP5
09aeLT
09afPUSH20x0b05
09b2JUMPI
09b3DUP5
09b4PUSH110x52b7d2dcc80cd2e4000000
09c0DUP2
09c1GT
09c2PUSH20x0ade
09c5JUMPI
09c6DUP1
09c7PUSH10x05
09c9SSTORE
09caPUSH110x52b7d2dcc80cd2e4000000
09d6SUB
09d7PUSH110x52b7d2dcc80cd2e4000000
09e3DUP2
09e4GT
09e5PUSH20x0ac4
09e8JUMPI
09e9PUSH10x03
09ebSSTORE
09ecPUSH20x0160
09efMLOAD
09f0JUMPDEST
09f1PUSH20x0120
09f4MLOAD
09f5DUP2
09f6LT
09f7PUSH20x0a6f
09faJUMPI
09fbPUSH10x01
09fdPUSH10x01
09ffPUSH10x40
0a01SHL
0a02SUB
0a03PUSH20x0140
0a06MLOAD
0a07AND
0a08PUSH10x01
0a0aPUSH10x01
0a0cPUSH10x40
0a0eSHL
0a0fSUB
0a10NOT
0a11PUSH10x06
0a13SLOAD
0a14AND
0a15OR
0a16PUSH10x06
0a18SSTORE
0a19PUSH20x0a20
0a1cPUSH20x1f17
0a1fJUMP
0a20JUMPDEST
0a21PUSH320xb04da588109787f2e1bce4cc15cc0e414cae26034cea4902695311f75568bf87
0a42PUSH10x60
0a44PUSH10x40
0a46MLOAD
0a47PUSH20x0180
0a4aMLOAD
0a4bDUP2
0a4cMSTORE
0a4dPUSH20x0120
0a50MLOAD
0a51PUSH10x20
0a53DUP3
0a54ADD
0a55MSTORE
0a56PUSH10x01
0a58PUSH10x01
0a5aPUSH10x40
0a5cSHL
0a5dSUB
0a5ePUSH20x0140
0a61MLOAD
0a62AND
0a63PUSH10x40
0a65DUP3
0a66ADD
0a67MSTORE
0a68LOG1
0a69PUSH20x0160
0a6cMLOAD
0a6dDUP1
0a6eRETURN
0a6fJUMPDEST
0a70DUP1
0a71PUSH20x0a83
0a74PUSH10x01
0a76SWAP3
0a77PUSH20x0120
0a7aMLOAD
0a7bPUSH20x0100
0a7eMLOAD
0a7fPUSH20x1c4b
0a82JUMP
0a83JUMPDEST
0a84CALLDATALOAD
0a85PUSH20x0160
0a88MLOAD
0a89MSTORE
0a8aPUSH10x07
0a8cPUSH10x20
0a8eMSTORE
0a8fPUSH10x40
0a91PUSH20x0160
0a94MLOAD
0a95KECCAK256
0a96PUSH20x0aa4
0a99DUP3
0a9aPUSH10xc0
0a9cMLOAD
0a9dPUSH10xe0
0a9fMLOAD
0aa0PUSH20x1c4b
0aa3JUMP
0aa4JUMPDEST
0aa5CALLDATALOAD
0aa6PUSH20x0160
0aa9MLOAD
0aaaMSTORE
0aabPUSH10x20
0aadMSTORE
0aaePUSH10x40
0ab0PUSH20x0160
0ab3MLOAD
0ab4KECCAK256
0ab5DUP3
0ab6PUSH10xff
0ab8NOT
0ab9DUP3
0abaSLOAD
0abbAND
0abcOR
0abdSWAP1
0abeSSTORE
0abfADD
0ac0PUSH20x09f0
0ac3JUMP
0ac4JUMPDEST
0ac5PUSH40x4e487b71
0acaPUSH10xe0
0accSHL
0acdPUSH20x0160
0ad0MLOAD
0ad1MSTORE
0ad2PUSH10x11
0ad4PUSH10x04
0ad6MSTORE
0ad7PUSH10x24
0ad9PUSH20x0160
0adcMLOAD
0addREVERT
0adeJUMPDEST
0adfPUSH40x11250051
0ae4PUSH10xe2
0ae6SHL
0ae7PUSH20x0160
0aeaMLOAD
0aebMSTORE
0aecPUSH10x04
0aeeMSTORE
0aefPUSH110x52b7d2dcc80cd2e4000000
0afbPUSH10x24
0afdMSTORE
0afePUSH10x44
0b00PUSH20x0160
0b03MLOAD
0b04REVERT
0b05JUMPDEST
0b06SWAP1
0b07SWAP2
0b08SWAP3
0b09SWAP4
0b0aPUSH20x0b17
0b0dDUP6
0b0ePUSH20x0180
0b11MLOAD
0b12DUP7
0b13PUSH20x1c4b
0b16JUMP
0b17JUMPDEST
0b18CALLDATALOAD
0b19ISZERO
0b1aDUP1
0b1bISZERO
0b1cPUSH20x0bf6
0b1fJUMPI
0b20JUMPDEST
0b21PUSH20x0be1
0b24JUMPI
0b25PUSH20x0b32
0b28DUP6
0b29PUSH20x0180
0b2cMLOAD
0b2dDUP7
0b2ePUSH20x1c4b
0b31JUMP
0b32JUMPDEST
0b33CALLDATALOAD
0b34PUSH20x0160
0b37MLOAD
0b38MSTORE
0b39PUSH10x04
0b3bPUSH10x20
0b3dMSTORE
0b3ePUSH10x40
0b40PUSH20x0160
0b43MLOAD
0b44KECCAK256
0b45PUSH10x01
0b47DUP2
0b48ADD
0b49SWAP1
0b4aPUSH10x01
0b4cPUSH10x01
0b4ePUSH10x40
0b50SHL
0b51SUB
0b52DUP3
0b53SLOAD
0b54AND
0b55PUSH20x0bb9
0b58JUMPI
0b59SWAP2
0b5aPUSH20x0baf
0b5dSWAP2
0b5ePUSH10x01
0b60SWAP4
0b61PUSH20x0b6b
0b64DUP10
0b65DUP9
0b66DUP9
0b67PUSH20x1c4b
0b6aJUMP
0b6bJUMPDEST
0b6cCALLDATALOAD
0b6dSWAP1
0b6eSSTORE
0b6fPUSH10x01
0b71PUSH10x01
0b73PUSH10x40
0b75SHL
0b76SUB
0b77PUSH20x0b8d
0b7aPUSH20x0b88
0b7dDUP11
0b7ePUSH10xa0
0b80MLOAD
0b81PUSH10x80
0b83MLOAD
0b84PUSH20x1c4b
0b87JUMP
0b88JUMPDEST
0b89PUSH20x1c6f
0b8cJUMP
0b8dJUMPDEST
0b8eAND
0b8fPUSH10x01
0b91PUSH10x01
0b93PUSH10x40
0b95SHL
0b96SUB
0b97NOT
0b98DUP3
0b99SLOAD
0b9aAND
0b9bOR
0b9cSWAP1
0b9dSSTORE
0b9ePUSH20x0ba8
0ba1DUP8
0ba2DUP7
0ba3DUP7
0ba4PUSH20x1c4b
0ba7JUMP
0ba8JUMPDEST
0ba9CALLDATALOAD
0baaSWAP1
0babPUSH20x1b48
0baeJUMP
0bafJUMPDEST
0bb0SWAP5
0bb1ADD
0bb2SWAP3
0bb3SWAP2
0bb4SWAP1
0bb5PUSH20x09a8
0bb8JUMP
0bb9JUMPDEST
0bbaPUSH20x0bc8
0bbdDUP8
0bbeDUP8
0bbfPUSH20x0180
0bc2MLOAD
0bc3SWAP1
0bc4PUSH20x1c4b
0bc7JUMP
0bc8JUMPDEST
0bc9CALLDATALOAD
0bcaPUSH40x724ac173
0bcfPUSH10xe0
0bd1SHL
0bd2PUSH20x0160
0bd5MLOAD
0bd6MSTORE
0bd7PUSH10x04
0bd9MSTORE
0bdaPUSH10x24
0bdcPUSH20x0160
0bdfMLOAD
0be0REVERT
0be1JUMPDEST
0be2PUSH40x200ff7d7
0be7PUSH10xe1
0be9SHL
0beaPUSH20x0160
0bedMLOAD
0beeMSTORE
0befPUSH10x04
0bf1PUSH20x0160
0bf4MLOAD
0bf5REVERT
0bf6JUMPDEST
0bf7POP
0bf8PUSH10x01
0bfaPUSH10x01
0bfcPUSH10x40
0bfeSHL
0bffSUB
0c00PUSH20x0c11
0c03PUSH20x0b88
0c06DUP8
0c07PUSH10xa0
0c09MLOAD
0c0aPUSH10x80
0c0cMLOAD
0c0dPUSH20x1c4b
0c10JUMP
0c11JUMPDEST
0c12AND
0c13ISZERO
0c14PUSH20x0b20
0c17JUMP
0c18JUMPDEST
0c19POP
0c1aPUSH10xa0
0c1cMLOAD
0c1dPUSH20x0180
0c20MLOAD
0c21EQ
0c22ISZERO
0c23PUSH20x098e
0c26JUMP
0c27JUMPDEST
0c28PUSH40xdc63d81f
0c2dPUSH10xe0
0c2fSHL
0c30PUSH20x0160
0c33MLOAD
0c34MSTORE
0c35PUSH10x04
0c37PUSH20x0160
0c3aMLOAD
0c3bREVERT
0c3cJUMPDEST
0c3dPOP
0c3ePUSH10x05
0c40SLOAD
0c41ISZERO
0c42ISZERO
0c43PUSH20x097c
0c46JUMP
0c47JUMPDEST
0c48PUSH10x40
0c4aMLOAD
0c4bPUSH10xc0
0c4dPUSH10x20
0c4fDUP3
0c50ADD
0c51MSTORE
0c52PUSH10x20
0c54PUSH20x0c7a
0c57PUSH20x0c67
0c5aPUSH10xe0
0c5cDUP5
0c5dADD
0c5ePUSH20x0180
0c61MLOAD
0c62DUP11
0c63PUSH20x1c27
0c66JUMP
0c67JUMPDEST
0c68DUP4
0c69DUP2
0c6aSUB
0c6bPUSH10x1f
0c6dNOT
0c6eADD
0c6fPUSH10x40
0c71DUP6
0c72ADD
0c73MSTORE
0c74DUP8
0c75DUP11
0c76PUSH20x1c27
0c79JUMP
0c7aJUMPDEST
0c7bPUSH10x1f
0c7dNOT
0c7eDUP4
0c7fDUP3
0c80SUB
0c81ADD
0c82PUSH10x60
0c84DUP5
0c85ADD
0c86MSTORE
0c87PUSH10xa0
0c89MLOAD
0c8aDUP2
0c8bMSTORE
0c8cADD
0c8dDUP2
0c8ePUSH10x80
0c90MLOAD
0c91PUSH20x0160
0c94MLOAD
0c95JUMPDEST
0c96PUSH10xa0
0c98MLOAD
0c99DUP2
0c9aLT
0c9bPUSH20x0ec8
0c9eJUMPI
0c9fPOP
0ca0POP
0ca1PUSH20x0cda
0ca4PUSH20x0cc3
0ca7PUSH20x0cfa
0caaSWAP4
0cabPUSH10x1f
0cadNOT
0caeDUP5
0cafDUP3
0cb0SUB
0cb1ADD
0cb2PUSH10x80
0cb4DUP6
0cb5ADD
0cb6MSTORE
0cb7PUSH20x0120
0cbaMLOAD
0cbbPUSH20x0100
0cbeMLOAD
0cbfPUSH20x1c27
0cc2JUMP
0cc3JUMPDEST
0cc4DUP3
0cc5DUP2
0cc6SUB
0cc7PUSH10x1f
0cc9NOT
0ccaADD
0ccbPUSH10xa0
0ccdDUP5
0cceADD
0ccfMSTORE
0cd0PUSH10xc0
0cd2MLOAD
0cd3PUSH10xe0
0cd5MLOAD
0cd6PUSH20x1c27
0cd9JUMP
0cdaJUMPDEST
0cdbPUSH10x01
0cddPUSH10x01
0cdfPUSH10x40
0ce1SHL
0ce2SUB
0ce3PUSH20x0140
0ce6MLOAD
0ce7AND
0ce8PUSH10xc0
0ceaDUP4
0cebADD
0cecMSTORE
0cedSUB
0ceePUSH10x1f
0cf0NOT
0cf1DUP2
0cf2ADD
0cf3DUP4
0cf4MSTORE
0cf5DUP3
0cf6PUSH20x1ac8
0cf9JUMP
0cfaJUMPDEST
0cfbDUP1
0cfcMLOAD
0cfdPUSH10x20
0cffSWAP1
0d00SWAP2
0d01ADD
0d02KECCAK256
0d03PUSH10x01
0d05PUSH10x01
0d07PUSH10xa0
0d09SHL
0d0aSUB
0d0bPUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0d2cAND
0d2dEXTCODESIZE
0d2eISZERO
0d2fPUSH20x0161
0d32JUMPI
0d33DUP3
0d34SWAP1
0d35PUSH10x01
0d37PUSH10x01
0d39PUSH10x40
0d3bSHL
0d3cSUB
0d3dPUSH10x40
0d3fMLOAD
0d40SWAP6
0d41PUSH40x22f3f447
0d46PUSH10xe1
0d48SHL
0d49DUP8
0d4aMSTORE
0d4bPUSH20x0160
0d4eMLOAD
0d4fPOP
0d50PUSH10x84
0d52DUP8
0d53ADD
0d54SWAP3
0d55PUSH320x08296c4851c7aca93c422c73902d61d179ed1bf52cf0ed257e6d096b9a8bb851
0d76PUSH10x04
0d78DUP10
0d79ADD
0d7aMSTORE
0d7bPUSH10x24
0d7dDUP9
0d7eADD
0d7fMSTORE
0d80AND
0d81PUSH10x44
0d83DUP7
0d84ADD
0d85MSTORE
0d86PUSH10x80
0d88PUSH10x64
0d8aDUP7
0d8bADD
0d8cMSTORE
0d8dMSTORE
0d8ePUSH10xa4
0d90DUP4
0d91ADD
0d92PUSH10xa0
0d94PUSH10x04
0d96DUP5
0d97PUSH10x05
0d99SHL
0d9aDUP7
0d9bADD
0d9cADD
0d9dADD
0d9eSWAP3
0d9fDUP3
0da0PUSH20x0160
0da3MLOAD
0da4SWAP1
0da5JUMPDEST
0da6DUP3
0da7DUP3
0da8LT
0da9PUSH20x0e26
0dacJUMPI
0dadPOP
0daePOP
0dafPUSH20x0160
0db2MLOAD
0db3SWAP4
0db4DUP6
0db5SWAP4
0db6POP
0db7DUP4
0db8SWAP1
0db9SUB
0dbaSWAP2
0dbbPOP
0dbcDUP3
0dbdSWAP1
0dbePOP
0dbfDUP4
0dc0PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0de1PUSH10x01
0de3PUSH10x01
0de5PUSH10xa0
0de7SHL
0de8SUB
0de9AND
0deaGAS
0debCALL
0decDUP1
0dedISZERO
0deePUSH20x0e18
0df1JUMPI
0df2PUSH20x0dfd
0df5JUMPI
0df6JUMPDEST
0df7DUP1
0df8DUP1
0df9PUSH20x0964
0dfcJUMP
0dfdJUMPDEST
0dfePUSH20x0160
0e01MLOAD
0e02PUSH20x0e0a
0e05SWAP2
0e06PUSH20x1ac8
0e09JUMP
0e0aJUMPDEST
0e0bPUSH20x0160
0e0eMLOAD
0e0fPUSH20x0161
0e12JUMPI
0e13DUP4
0e14PUSH20x0df6
0e17JUMP
0e18JUMPDEST
0e19PUSH10x40
0e1bMLOAD
0e1cRETURNDATASIZE
0e1dPUSH20x0160
0e20MLOAD
0e21DUP3
0e22RETURNDATACOPY
0e23RETURNDATASIZE
0e24SWAP1
0e25REVERT
0e26JUMPDEST
0e27SWAP1
0e28SWAP2
0e29SWAP3
0e2aSWAP4
0e2bSWAP5
0e2cPUSH10x9f
0e2eNOT
0e2fPUSH10x03
0e31NOT
0e32DUP9
0e33DUP4
0e34SUB
0e35ADD
0e36ADD
0e37DUP6
0e38MSTORE
0e39DUP6
0e3aCALLDATALOAD
0e3bPUSH10x7e
0e3dNOT
0e3eDUP4
0e3fCALLDATASIZE
0e40SUB
0e41ADD
0e42DUP2
0e43SLT
0e44ISZERO
0e45PUSH20x0161
0e48JUMPI
0e49DUP3
0e4aADD
0e4bPUSH10x01
0e4dPUSH10x01
0e4fPUSH10xa0
0e51SHL
0e52SUB
0e53PUSH20x0e5b
0e56DUP3
0e57PUSH20x1a87
0e5aJUMP
0e5bJUMPDEST
0e5cAND
0e5dDUP3
0e5eMSTORE
0e5fPUSH10x20
0e61DUP2
0e62ADD
0e63CALLDATALOAD
0e64SWAP2
0e65PUSH10xff
0e67DUP4
0e68AND
0e69DUP1
0e6aSWAP4
0e6bSUB
0e6cPUSH20x0161
0e6fJUMPI
0e70PUSH20x0ebb
0e73PUSH10x20
0e75SWAP3
0e76DUP3
0e77PUSH10x01
0e79SWAP6
0e7aDUP6
0e7bDUP1
0e7cSWAP6
0e7dADD
0e7eMSTORE
0e7fPUSH20x0ead
0e82PUSH20x0ea2
0e85PUSH20x0e91
0e88PUSH10x40
0e8aDUP6
0e8bADD
0e8cDUP6
0e8dPUSH20x1b55
0e90JUMP
0e91JUMPDEST
0e92PUSH10x80
0e94PUSH10x40
0e96DUP7
0e97ADD
0e98MSTORE
0e99PUSH10x80
0e9bDUP6
0e9cADD
0e9dSWAP2
0e9ePUSH20x1b86
0ea1JUMP
0ea2JUMPDEST
0ea3SWAP3
0ea4PUSH10x60
0ea6DUP2
0ea7ADD
0ea8SWAP1
0ea9PUSH20x1b55
0eacJUMP
0eadJUMPDEST
0eaeSWAP2
0eafPUSH10x60
0eb1DUP2
0eb2DUP6
0eb3SUB
0eb4SWAP2
0eb5ADD
0eb6MSTORE
0eb7PUSH20x1b86
0ebaJUMP
0ebbJUMPDEST
0ebcSWAP8
0ebdADD
0ebeSWAP6
0ebfADD
0ec0SWAP4
0ec1SWAP3
0ec2ADD
0ec3SWAP1
0ec4PUSH20x0da5
0ec7JUMP
0ec8JUMPDEST
0ec9SWAP2
0ecaPOP
0ecbSWAP2
0eccPUSH20x0160
0ecfMLOAD
0ed0POP
0ed1DUP3
0ed2CALLDATALOAD
0ed3SWAP1
0ed4PUSH10x01
0ed6PUSH10x01
0ed8PUSH10x40
0edaSHL
0edbSUB
0edcDUP3
0eddAND
0edeDUP1
0edfSWAP3
0ee0SUB
0ee1PUSH20x0161
0ee4JUMPI
0ee5PUSH10x20
0ee7DUP2
0ee8PUSH10x01
0eeaSWAP4
0eebDUP3
0eecSWAP4
0eedMSTORE
0eeeADD
0eefSWAP4
0ef0ADD
0ef1SWAP2
0ef2ADD
0ef3SWAP1
0ef4DUP4
0ef5SWAP2
0ef6PUSH20x0c95
0ef9JUMP
0efaJUMPDEST
0efbPOP
0efcPUSH10x40
0efeMLOAD
0effPUSH40xf5778b03
0f04PUSH10xe0
0f06SHL
0f07DUP2
0f08MSTORE
0f09PUSH10x20
0f0bDUP2
0f0cPUSH10x04
0f0eDUP2
0f0fPUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
0f30PUSH10x01
0f32PUSH10x01
0f34PUSH10xa0
0f36SHL
0f37SUB
0f38AND
0f39GAS
0f3aSTATICCALL
0f3bSWAP1
0f3cDUP2
0f3dISZERO
0f3ePUSH20x0e18
0f41JUMPI
0f42PUSH20x0160
0f45MLOAD
0f46SWAP2
0f47PUSH20x0f5d
0f4aJUMPI
0f4bJUMPDEST
0f4cPOP
0f4dPUSH10x01
0f4fPUSH10x01
0f51PUSH10xa0
0f53SHL
0f54SUB
0f55AND
0f56CALLER
0f57EQ
0f58ISZERO
0f59PUSH20x095f
0f5cJUMP
0f5dJUMPDEST
0f5ePUSH20x0f7f
0f61SWAP2
0f62POP
0f63PUSH10x20
0f65RETURNDATASIZE
0f66PUSH10x20
0f68GT
0f69PUSH20x0f85
0f6cJUMPI
0f6dJUMPDEST
0f6ePUSH20x0f77
0f71DUP2
0f72DUP4
0f73PUSH20x1ac8
0f76JUMP
0f77JUMPDEST
0f78DUP2
0f79ADD
0f7aSWAP1
0f7bPUSH20x1c08
0f7eJUMP
0f7fJUMPDEST
0f80DUP8
0f81PUSH20x0f4b
0f84JUMP
0f85JUMPDEST
0f86POP
0f87RETURNDATASIZE
0f88PUSH20x0f6d
0f8bJUMP
0f8cJUMPDEST
0f8dPUSH20x0fae
0f90SWAP2
0f91POP
0f92PUSH10x20
0f94RETURNDATASIZE
0f95PUSH10x20
0f97GT
0f98PUSH20x0fb4
0f9bJUMPI
0f9cJUMPDEST
0f9dPUSH20x0fa6
0fa0DUP2
0fa1DUP4
0fa2PUSH20x1ac8
0fa5JUMP
0fa6JUMPDEST
0fa7DUP2
0fa8ADD
0fa9SWAP1
0faaPUSH20x1bf0
0fadJUMP
0faeJUMPDEST
0fafDUP8
0fb0PUSH20x0957
0fb3JUMP
0fb4JUMPDEST
0fb5POP
0fb6RETURNDATASIZE
0fb7PUSH20x0f9c
0fbaJUMP
0fbbJUMPDEST
0fbcCALLVALUE
0fbdPUSH20x0161
0fc0JUMPI
0fc1PUSH10xa0
0fc3CALLDATASIZE
0fc4PUSH10x03
0fc6NOT
0fc7ADD
0fc8SLT
0fc9PUSH20x0161
0fccJUMPI
0fcdPUSH10x04
0fcfCALLDATALOAD
0fd0PUSH10x04
0fd2DUP2
0fd3LT
0fd4ISZERO
0fd5PUSH20x0161
0fd8JUMPI
0fd9PUSH20x0fe0
0fdcPUSH20x1a71
0fdfJUMP
0fe0JUMPDEST
0fe1SWAP1
0fe2PUSH10x64
0fe4CALLDATALOAD
0fe5SWAP1
0fe6PUSH10x84
0fe8CALLDATALOAD
0fe9PUSH10x01
0febPUSH10x01
0fedPUSH10xa0
0fefSHL
0ff0SUB
0ff1DUP2
0ff2AND
0ff3SWAP2
0ff4PUSH10x44
0ff6CALLDATALOAD
0ff7SWAP2
0ff8DUP4
0ff9DUP2
0ffaSUB
0ffbPUSH20x0161
0ffeJUMPI
0fffPUSH20x1006
1002PUSH20x20a9
1005JUMP
1006JUMPDEST
1007PUSH20x0160
100aMLOAD
100bSLOAD
100cDUP5
100dISZERO
100eSWAP1
100fPUSH10x01
1011PUSH10x01
1013PUSH10xa0
1015SHL
1016SUB
1017AND
1018DUP2
1019ISZERO
101aPUSH20x1243
101dJUMPI
101eJUMPDEST
101fPOP
1020PUSH20x122a
1023JUMPI
1024PUSH20x102e
1027DUP4
1028DUP8
1029DUP5
102aPUSH20x1b2f
102dJUMP
102eJUMPDEST
102fSWAP5
1030PUSH0
1031NOT
1032DUP2
1033SUB
1034PUSH20x1225
1037JUMPI
1038POP
1039DUP5
103aJUMPDEST
103bDUP1
103cSWAP6
103dDUP2
103eISZERO
103fPUSH20x1210
1042JUMPI
1043DUP1
1044DUP3
1045GT
1046PUSH20x11e5
1049JUMPI
104aPOP
104bPUSH20x0160
104eMLOAD
104fSWAP2
1050DUP4
1051PUSH20x10f0
1054JUMPI
1055POP
1056POP
1057PUSH20x0160
105aMLOAD
105bDUP1
105cDUP1
105dDUP1
105eDUP9
105fDUP9
1060GAS
1061CALL
1062PUSH20x1069
1065PUSH20x1bc1
1068JUMP
1069JUMPDEST
106aPOP
106bISZERO
106cPUSH20x10d4
106fJUMPI
1070JUMPDEST
1071PUSH20x10ba
1074JUMPI
1075PUSH10x40
1077DUP1
1078MLOAD
1079SWAP3
107aDUP4
107bMSTORE
107cPUSH10x20
107eDUP4
107fDUP2
1080ADD
1081DUP7
1082SWAP1
1083MSTORE
1084SWAP6
1085PUSH10x01
1087PUSH10x01
1089PUSH10xa0
108bSHL
108cSUB
108dAND
108eSWAP3
108fPUSH320x7643c83e539cea2f6bf506545392e52cfd5f917e327efbcd0ba28f29c28d042e
10b0SWAP2
10b1SWAP1
10b2LOG4
10b3PUSH10x40
10b5MLOAD
10b6SWAP1
10b7DUP2
10b8MSTORE
10b9RETURN
10baJUMPDEST
10bbPUSH40x4e487b71
10c0PUSH10xe0
10c2SHL
10c3PUSH20x0160
10c6MLOAD
10c7MSTORE
10c8PUSH10x21
10caPUSH10x04
10ccMSTORE
10cdPUSH10x24
10cfPUSH20x0160
10d2MLOAD
10d3REVERT
10d4JUMPDEST
10d5PUSH40x65f4a9ef
10daPUSH10xe1
10dcSHL
10ddPUSH20x0160
10e0MLOAD
10e1MSTORE
10e2PUSH20x0160
10e5MLOAD
10e6PUSH10x04
10e8MSTORE
10e9PUSH10x24
10ebPUSH20x0160
10eeMLOAD
10efREVERT
10f0JUMPDEST
10f1PUSH20x0160
10f4MLOAD
10f5SWAP3
10f6POP
10f7SWAP1
10f8PUSH10x01
10faDUP5
10fbSUB
10fcPUSH20x114d
10ffJUMPI
1100POP
1101PUSH10x40
1103MLOAD
1104PUSH40xa9059cbb
1109PUSH10xe0
110bSHL
110cPUSH10x20
110eDUP3
110fADD
1110MSTORE
1111PUSH10x01
1113PUSH10x01
1115PUSH10xa0
1117SHL
1118SUB
1119SWAP1
111aSWAP2
111bAND
111cPUSH10x24
111eDUP3
111fADD
1120MSTORE
1121PUSH10x44
1123DUP2
1124ADD
1125DUP7
1126SWAP1
1127MSTORE
1128PUSH20x1148
112bSWAP1
112cPUSH20x1142
112fDUP2
1130PUSH10x64
1132DUP2
1133ADD
1134JUMPDEST
1135SUB
1136PUSH10x1f
1138NOT
1139DUP2
113aADD
113bDUP4
113cMSTORE
113dDUP3
113ePUSH20x1ac8
1141JUMP
1142JUMPDEST
1143DUP8
1144PUSH20x20d7
1147JUMP
1148JUMPDEST
1149PUSH20x1070
114cJUMP
114dJUMPDEST
114ePUSH20x0160
1151MLOAD
1152SWAP7
1153SWAP3
1154POP
1155SWAP1
1156POP
1157PUSH10x02
1159DUP4
115aSUB
115bPUSH20x1197
115eJUMPI
115fPOP
1160POP
1161PUSH10x01
1163SWAP4
1164PUSH20x1148
1167PUSH10x40
1169MLOAD
116aPUSH40x23b872dd
116fPUSH10xe0
1171SHL
1172PUSH10x20
1174DUP3
1175ADD
1176MSTORE
1177ADDRESS
1178PUSH10x24
117aDUP3
117bADD
117cMSTORE
117dDUP6
117ePUSH10x44
1180DUP3
1181ADD
1182MSTORE
1183DUP5
1184PUSH10x64
1186DUP3
1187ADD
1188MSTORE
1189PUSH10x64
118bDUP2
118cMSTORE
118dPUSH20x1142
1190PUSH10x84
1192DUP3
1193PUSH20x1ac8
1196JUMP
1197JUMPDEST
1198PUSH20x1148
119bSWAP1
119cPUSH10x40
119eSWAP7
119fSWAP3
11a0SWAP7
11a1MLOAD
11a2SWAP1
11a3PUSH40x79212195
11a8PUSH10xe1
11aaSHL
11abPUSH10x20
11adDUP4
11aeADD
11afMSTORE
11b0ADDRESS
11b1PUSH10x24
11b3DUP4
11b4ADD
11b5MSTORE
11b6DUP7
11b7PUSH10x44
11b9DUP4
11baADD
11bbMSTORE
11bcDUP6
11bdPUSH10x64
11bfDUP4
11c0ADD
11c1MSTORE
11c2PUSH10x84
11c4DUP3
11c5ADD
11c6MSTORE
11c7PUSH10xa0
11c9PUSH10xa4
11cbDUP3
11ccADD
11cdMSTORE
11cePUSH20x0160
11d1MLOAD
11d2PUSH10xc4
11d4DUP3
11d5ADD
11d6MSTORE
11d7PUSH10xc4
11d9DUP2
11daMSTORE
11dbPUSH20x1142
11dePUSH10xe4
11e0DUP3
11e1PUSH20x1ac8
11e4JUMP
11e5JUMPDEST
11e6PUSH20x0160
11e9DUP1
11eaMLOAD
11ebPUSH40x21909681
11f0PUSH10xe0
11f2SHL
11f3SWAP1
11f4MSTORE
11f5PUSH10x01
11f7PUSH10x01
11f9PUSH10xa0
11fbSHL
11fcSUB
11fdDUP10
11feAND
11ffPUSH10x04
1201MSTORE
1202PUSH10x24
1204SWAP3
1205SWAP1
1206SWAP3
1207MSTORE
1208PUSH10x44
120aMSTORE
120bMLOAD
120cPUSH10x64
120eSWAP1
120fREVERT
1210JUMPDEST
1211PUSH40x7c2e506f
1216PUSH10xe1
1218SHL
1219PUSH20x0160
121cMLOAD
121dMSTORE
121ePUSH10x04
1220PUSH20x0160
1223MLOAD
1224REVERT
1225JUMPDEST
1226PUSH20x103a
1229JUMP
122aJUMPDEST
122bDUP4
122cPUSH40x15150d4d
1231PUSH10xe3
1233SHL
1234PUSH20x0160
1237MLOAD
1238MSTORE
1239PUSH10x04
123bMSTORE
123cPUSH10x24
123ePUSH20x0160
1241MLOAD
1242REVERT
1243JUMPDEST
1244SWAP1
1245POP
1246DUP5
1247EQ
1248ISZERO
1249DUP1
124aPUSH20x1254
124dJUMPI
124eJUMPDEST
124fDUP8
1250PUSH20x101e
1253JUMP
1254JUMPDEST
1255POP
1256CALLER
1257DUP5
1258EQ
1259ISZERO
125aPUSH20x124e
125dJUMP
125eJUMPDEST
125fCALLVALUE
1260PUSH20x13a2
1263JUMPI
1264PUSH10x80
1266CALLDATASIZE
1267PUSH10x03
1269NOT
126aADD
126bSLT
126cPUSH20x13a2
126fJUMPI
1270PUSH10x24
1272CALLDATALOAD
1273PUSH10x04
1275CALLDATALOAD
1276PUSH20x127d
1279PUSH20x1a15
127cJUMP
127dJUMPDEST
127ePUSH10x64
1280CALLDATALOAD
1281PUSH10x01
1283PUSH10x01
1285PUSH10x40
1287SHL
1288SUB
1289DUP2
128aGT
128bPUSH20x13a2
128eJUMPI
128fPUSH20x129c
1292SWAP1
1293CALLDATASIZE
1294SWAP1
1295PUSH10x04
1297ADD
1298PUSH20x1a2b
129bJUMP
129cJUMPDEST
129dPUSH0
129eSLOAD
129fPUSH10x01
12a1PUSH10x01
12a3PUSH10xa0
12a5SHL
12a6SUB
12a7AND
12a8CALLER
12a9SUB
12aaPUSH20x13ae
12adJUMPI
12aeJUMPDEST
12afPOP
12b0POP
12b1POP
12b2DUP2
12b3PUSH20x12f6
12b6JUMPI
12b7JUMPDEST
12b8DUP1
12b9PUSH320x43c4ef2494de90aa2f24830e48f3dc8579dec67c48d59b2ffba50c125576d4c4
12daSWAP3
12dbPUSH10x40
12ddSWAP3
12dePUSH10x01
12e0SSTORE
12e1DUP1
12e2PUSH10x02
12e4SSTORE
12e5DUP3
12e6MLOAD
12e7SWAP2
12e8DUP3
12e9MSTORE
12eaPUSH10x20
12ecDUP3
12edADD
12eeMSTORE
12efLOG1
12f0PUSH20x0160
12f3MLOAD
12f4DUP1
12f5RETURN
12f6JUMPDEST
12f7PUSH10x40
12f9MLOAD
12faPUSH40x342f6163
12ffPUSH10xe0
1301SHL
1302DUP2
1303MSTORE
1304PUSH10x04
1306DUP2
1307ADD
1308DUP3
1309SWAP1
130aMSTORE
130bPUSH10x20
130dDUP2
130ePUSH10x24
1310DUP2
1311PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
1332PUSH10x01
1334PUSH10x01
1336PUSH10xa0
1338SHL
1339SUB
133aAND
133bGAS
133cSTATICCALL
133dSWAP1
133eDUP2
133fISZERO
1340PUSH20x0e18
1343JUMPI
1344PUSH20x0160
1347MLOAD
1348SWAP2
1349PUSH20x1378
134cJUMPI
134dJUMPDEST
134ePOP
134fDUP3
1350DUP2
1351LT
1352PUSH20x135b
1355JUMPI
1356POP
1357PUSH20x12b7
135aJUMP
135bJUMPDEST
135cSWAP1
135dPOP
135ePUSH40x3770da33
1363PUSH10xe1
1365SHL
1366PUSH20x0160
1369MLOAD
136aMSTORE
136bPUSH10x04
136dMSTORE
136ePUSH10x24
1370MSTORE
1371PUSH10x44
1373PUSH20x0160
1376MLOAD
1377REVERT
1378JUMPDEST
1379SWAP1
137aPOP
137bPUSH10x20
137dDUP2
137eRETURNDATASIZE
137fPUSH10x20
1381GT
1382PUSH20x13a6
1385JUMPI
1386JUMPDEST
1387DUP2
1388PUSH20x1393
138bPUSH10x20
138dSWAP4
138eDUP4
138fPUSH20x1ac8
1392JUMP
1393JUMPDEST
1394DUP2
1395ADD
1396SUB
1397SLT
1398PUSH20x13a2
139bJUMPI
139cMLOAD
139dDUP4
139ePUSH20x134d
13a1JUMP
13a2JUMPDEST
13a3PUSH0
13a4DUP1
13a5REVERT
13a6JUMPDEST
13a7RETURNDATASIZE
13a8SWAP2
13a9POP
13aaPUSH20x1386
13adJUMP
13aeJUMPDEST
13afPUSH10x40
13b1DUP1
13b2MLOAD
13b3PUSH10x20
13b5DUP2
13b6ADD
13b7DUP7
13b8DUP2
13b9MSTORE
13baDUP2
13bbDUP4
13bcADD
13bdDUP9
13beSWAP1
13bfMSTORE
13c0SWAP2
13c1DUP2
13c2MSTORE
13c3PUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
13e4PUSH10x01
13e6PUSH10x01
13e8PUSH10xa0
13eaSHL
13ebSUB
13ecAND
13edSWAP4
13eeSWAP2
13efSWAP1
13f0PUSH20x13fa
13f3PUSH10x60
13f5DUP3
13f6PUSH20x1ac8
13f9JUMP
13faJUMPDEST
13fbMLOAD
13fcSWAP1
13fdKECCAK256
13feDUP4
13ffEXTCODESIZE
1400ISZERO
1401PUSH20x13a2
1404JUMPI
1405SWAP1
1406DUP3
1407PUSH10x01
1409PUSH10x01
140bPUSH10x40
140dSHL
140eSUB
140fSWAP6
1410SWAP4
1411SWAP3
1412PUSH10x40
1414MLOAD
1415SWAP7
1416DUP8
1417SWAP6
1418PUSH40x22f3f447
141dPUSH10xe1
141fSHL
1420DUP8
1421MSTORE
1422PUSH10x84
1424DUP8
1425ADD
1426SWAP3
1427PUSH320xbc5858e168b959a61a8fb2d7957ef31dbed683a362770ca030e5d772cc44e068
1448PUSH10x04
144aDUP10
144bADD
144cMSTORE
144dPUSH10x24
144fDUP9
1450ADD
1451MSTORE
1452AND
1453PUSH10x44
1455DUP7
1456ADD
1457MSTORE
1458PUSH10x80
145aPUSH10x64
145cDUP7
145dADD
145eMSTORE
145fMSTORE
1460PUSH10xa4
1462DUP4
1463ADD
1464PUSH10xa0
1466PUSH10x04
1468DUP5
1469PUSH10x05
146bSHL
146cDUP7
146dADD
146eADD
146fADD
1470SWAP3
1471DUP3
1472PUSH0
1473SWAP1
1474PUSH10x7e
1476NOT
1477DUP2
1478CALLDATASIZE
1479SUB
147aADD
147bJUMPDEST
147cDUP4
147dDUP4
147eLT
147fPUSH20x14c6
1482JUMPI
1483POP
1484POP
1485POP
1486POP
1487POP
1488POP
1489SWAP2
148aDUP2
148bPUSH0
148cDUP2
148dDUP6
148eDUP3
148fSWAP7
1490POP
1491SUB
1492SWAP3
1493GAS
1494CALL
1495DUP1
1496ISZERO
1497PUSH20x14bb
149aJUMPI
149bPUSH20x14a6
149eJUMPI
149fJUMPDEST
14a0DUP1
14a1DUP1
14a2PUSH20x12ae
14a5JUMP
14a6JUMPDEST
14a7PUSH0
14a8PUSH20x14b0
14abSWAP2
14acPUSH20x1ac8
14afJUMP
14b0JUMPDEST
14b1PUSH0
14b2PUSH20x0160
14b5MSTORE
14b6DUP3
14b7PUSH20x149f
14baJUMP
14bbJUMPDEST
14bcPUSH10x40
14beMLOAD
14bfRETURNDATASIZE
14c0PUSH0
14c1DUP3
14c2RETURNDATACOPY
14c3RETURNDATASIZE
14c4SWAP1
14c5REVERT
14c6JUMPDEST
14c7PUSH10xa3
14c9NOT
14caDUP11
14cbDUP9
14ccSUB
14cdADD
14ceDUP6
14cfMSTORE
14d0SWAP5
14d1SWAP7
14d2POP
14d3SWAP3
14d4SWAP5
14d5SWAP2
14d6SWAP4
14d7SWAP1
14d8SWAP3
14d9SWAP2
14daDUP7
14dbCALLDATALOAD
14dcDUP3
14ddDUP2
14deSLT
14dfISZERO
14e0PUSH20x13a2
14e3JUMPI
14e4DUP4
14e5ADD
14e6PUSH10x01
14e8PUSH10x01
14eaPUSH10xa0
14ecSHL
14edSUB
14eePUSH20x14f6
14f1DUP3
14f2PUSH20x1a87
14f5JUMP
14f6JUMPDEST
14f7AND
14f8DUP3
14f9MSTORE
14faPUSH10x20
14fcDUP2
14fdADD
14feCALLDATALOAD
14ffSWAP2
1500PUSH10xff
1502DUP4
1503AND
1504DUP1
1505SWAP4
1506SUB
1507PUSH20x13a2
150aJUMPI
150bPUSH20x152c
150ePUSH10x20
1510SWAP3
1511DUP3
1512PUSH10x01
1514SWAP6
1515DUP6
1516DUP1
1517SWAP6
1518ADD
1519MSTORE
151aPUSH20x0ead
151dPUSH20x0ea2
1520PUSH20x0e91
1523PUSH10x40
1525DUP6
1526ADD
1527DUP6
1528PUSH20x1b55
152bJUMP
152cJUMPDEST
152dSWAP9
152eADD
152fSWAP7
1530ADD
1531SWAP4
1532ADD
1533SWAP1
1534SWAP2
1535DUP9
1536SWAP7
1537SWAP6
1538SWAP5
1539SWAP3
153aPUSH20x147b
153dJUMP
153eJUMPDEST
153fCALLVALUE
1540PUSH20x13a2
1543JUMPI
1544PUSH0
1545CALLDATASIZE
1546PUSH10x03
1548NOT
1549ADD
154aSLT
154bPUSH20x13a2
154eJUMPI
154fPUSH10x20
1551PUSH10x40
1553MLOAD
1554PUSH110x52b7d2dcc80cd2e4000000
1560DUP2
1561MSTORE
1562RETURN
1563JUMPDEST
1564CALLVALUE
1565PUSH20x13a2
1568JUMPI
1569PUSH0
156aCALLDATASIZE
156bPUSH10x03
156dNOT
156eADD
156fSLT
1570PUSH20x13a2
1573JUMPI
1574PUSH10x20
1576PUSH20x1584
1579PUSH10x03
157bSLOAD
157cPUSH10x05
157eSLOAD
157fSWAP1
1580PUSH20x1b48
1583JUMP
1584JUMPDEST
1585PUSH10x40
1587MLOAD
1588SWAP1
1589DUP2
158aMSTORE
158bRETURN
158cJUMPDEST
158dCALLVALUE
158ePUSH20x13a2
1591JUMPI
1592PUSH10x20
1594CALLDATASIZE
1595PUSH10x03
1597NOT
1598ADD
1599SLT
159aPUSH20x13a2
159dJUMPI
159ePUSH10x04
15a0CALLDATALOAD
15a1PUSH0
15a2MSTORE
15a3PUSH10x04
15a5PUSH10x20
15a7MSTORE
15a8PUSH10x40
15aaDUP1
15abPUSH0
15acKECCAK256
15adPUSH10x01
15afPUSH10x01
15b1PUSH10x40
15b3SHL
15b4SUB
15b5PUSH10x01
15b7DUP3
15b8SLOAD
15b9SWAP3
15baADD
15bbSLOAD
15bcAND
15bdDUP3
15beMLOAD
15bfSWAP2
15c0DUP3
15c1MSTORE
15c2PUSH10x20
15c4DUP3
15c5ADD
15c6MSTORE
15c7RETURN
15c8JUMPDEST
15c9CALLVALUE
15caPUSH20x13a2
15cdJUMPI
15cePUSH0
15cfCALLDATASIZE
15d0PUSH10x03
15d2NOT
15d3ADD
15d4SLT
15d5PUSH20x13a2
15d8JUMPI
15d9PUSH10x40
15dbMLOAD
15dcPUSH320x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf
15fdPUSH10x01
15ffPUSH10x01
1601PUSH10xa0
1603SHL
1604SUB
1605AND
1606DUP2
1607MSTORE
1608PUSH10x20
160aSWAP1
160bRETURN
160cJUMPDEST
160dCALLVALUE
160ePUSH20x13a2
1611JUMPI
1612PUSH0
1613CALLDATASIZE
1614PUSH10x03
1616NOT
1617ADD
1618SLT
1619PUSH20x13a2
161cJUMPI
161dPUSH10x20
161fPUSH10x40
1621MLOAD
1622PUSH320x08296c4851c7aca93c422c73902d61d179ed1bf52cf0ed257e6d096b9a8bb851
1643DUP2
1644MSTORE
1645RETURN
1646JUMPDEST
1647CALLVALUE
1648PUSH20x13a2
164bJUMPI
164cPUSH10x60
164eCALLDATASIZE
164fPUSH10x03
1651NOT
1652ADD
1653SLT
1654PUSH20x13a2
1657JUMPI
1658PUSH10x04
165aCALLDATALOAD
165bPUSH10x04
165dDUP2
165eLT
165fISZERO
1660PUSH20x13a2
1663JUMPI
1664PUSH20x1584
1667PUSH10x20
1669SWAP2
166aPUSH20x1671
166dPUSH20x1a71
1670JUMP
1671JUMPDEST
1672PUSH10x44
1674CALLDATALOAD
1675SWAP2
1676PUSH20x1b2f
1679JUMP
167aJUMPDEST
167bCALLVALUE
167cPUSH20x13a2
167fJUMPI
1680PUSH0
1681CALLDATASIZE
1682PUSH10x03
1684NOT
1685ADD
1686SLT
1687PUSH20x13a2
168aJUMPI
168bPUSH10x20
168dPUSH10x05
168fSLOAD
1690PUSH10x40
1692MLOAD
1693SWAP1
1694DUP2
1695MSTORE
1696RETURN
1697JUMPDEST
1698CALLVALUE
1699PUSH20x13a2
169cJUMPI
169dPUSH0
169eCALLDATASIZE
169fPUSH10x03
16a1NOT
16a2ADD
16a3SLT
16a4PUSH20x13a2
16a7JUMPI
16a8PUSH10x20
16aaPUSH10x02
16acSLOAD
16adPUSH10x40
16afMLOAD
16b0SWAP1
16b1DUP2
16b2MSTORE
16b3RETURN
16b4JUMPDEST
16b5CALLVALUE
16b6PUSH20x13a2
16b9JUMPI
16baPUSH0
16bbCALLDATASIZE
16bcPUSH10x03
16beNOT
16bfADD
16c0SLT
16c1PUSH20x13a2
16c4JUMPI
16c5PUSH0
16c6SLOAD
16c7PUSH10x01
16c9PUSH10x01
16cbPUSH10xa0
16cdSHL
16ceSUB
16cfDUP2
16d0AND
16d1CALLER
16d2SUB
16d3PUSH20x170f
16d6JUMPI
16d7PUSH120xffffffffffffffffffffffff
16e4PUSH10xa0
16e6SHL
16e7AND
16e8PUSH0
16e9SSTORE
16eaPUSH320x1b2d71eb44f882534bf4e86f940c56ccc869ffb927e2bab86561de93950c2216
170bPUSH0
170cDUP1
170dLOG1
170eSTOP
170fJUMPDEST
1710PUSH40x0bd42121
1715PUSH10xe1
1717SHL
1718PUSH0
1719MSTORE
171aCALLER
171bPUSH10x04
171dMSTORE
171ePUSH10x24
1720PUSH0
1721REVERT
1722JUMPDEST
1723CALLVALUE
1724PUSH20x13a2
1727JUMPI
1728PUSH10x80
172aCALLDATASIZE
172bPUSH10x03
172dNOT
172eADD
172fSLT
1730PUSH20x13a2
1733JUMPI
1734PUSH20x173b
1737PUSH20x1a5b
173aJUMP
173bJUMPDEST
173cPOP
173dPUSH20x1744
1740PUSH20x1a71
1743JUMP
1744JUMPDEST
1745POP
1746PUSH10x64
1748CALLDATALOAD
1749PUSH10x01
174bPUSH10x01
174dPUSH10x40
174fSHL
1750SUB
1751DUP2
1752GT
1753PUSH20x13a2
1756JUMPI
1757PUSH20x1764
175aSWAP1
175bCALLDATASIZE
175cSWAP1
175dPUSH10x04
175fADD
1760PUSH20x1a9b
1763JUMP
1764JUMPDEST
1765POP
1766POP
1767PUSH10x40
1769MLOAD
176aPUSH40x0a85bd01
176fPUSH10xe1
1771SHL
1772DUP2
1773MSTORE
1774PUSH10x20
1776SWAP1
1777RETURN
1778JUMPDEST
1779CALLVALUE
177aPUSH20x13a2
177dJUMPI
177ePUSH10xa0
1780CALLDATASIZE
1781PUSH10x03
1783NOT
1784ADD
1785SLT
1786PUSH20x13a2
1789JUMPI
178aPUSH10x04
178cCALLDATALOAD
178dPUSH10x24
178fCALLDATALOAD
1790PUSH10x44
1792CALLDATALOAD
1793PUSH20x179a
1796PUSH20x19ff
1799JUMP
179aJUMPDEST
179bPUSH10x84
179dCALLDATALOAD
179ePUSH10x01
17a0PUSH10x01
17a2PUSH10x40
17a4SHL
17a5SUB
17a6DUP2
17a7GT
17a8PUSH20x13a2
17abJUMPI
17acPUSH20x17b9
17afSWAP1
17b0CALLDATASIZE
17b1SWAP1
17b2PUSH10x04
17b4ADD
17b5PUSH20x1a2b
17b8JUMP
17b9JUMPDEST
17baSWAP1
17bbSWAP2
17bcPUSH10x02
17beSLOAD
17bfDUP1
17c0ISZERO
17c1PUSH20x19ba
17c4JUMPI
17c5DUP6
17c6ISZERO
17c7PUSH20x19ab
17caJUMPI
17cbDUP7
17ccPUSH0
17cdMSTORE
17cePUSH10x07
17d0PUSH10x20
17d2MSTORE
17d3PUSH10x40
17d5PUSH0
17d6KECCAK256
17d7DUP6
17d8PUSH0
17d9MSTORE
17daPUSH10x20
17dcMSTORE
17ddPUSH10xff
17dfPUSH10x40
17e1PUSH0
17e2KECCAK256
17e3SLOAD
17e4AND
17e5PUSH20x1994
17e8JUMPI
17e9DUP7
17eaPUSH0
17ebMSTORE
17ecPUSH10x04
17eePUSH10x20
17f0MSTORE
17f1PUSH10x40
17f3PUSH0
17f4KECCAK256
17f5SWAP4
17f6DUP5
17f7SLOAD
17f8DUP1
17f9DUP9
17faGT
17fbPUSH20x197a
17feJUMPI
17ffPOP
1800PUSH20x18eb
1803DUP9
1804PUSH20x18e5
1807PUSH320x3ef380598f06b01333350e61cefa066c10a2088a64ad65aadd22eda04b972b89
1828SWAP10
1829SWAP8
182aSWAP6
182bPUSH10x80
182dSWAP10
182eSWAP8
182fSWAP6
1830PUSH10x01
1832PUSH10x01
1834PUSH10x40
1836SHL
1837SUB
1838SWAP6
1839PUSH10x06
183bSLOAD
183cSWAP8
183dDUP8
183eDUP10
183fAND
1840SWAP6
1841PUSH10x40
1843MLOAD
1844PUSH10x20
1846DUP2
1847ADD
1848SWAP2
1849DUP9
184aDUP4
184bMSTORE
184cPUSH10x40
184eDUP3
184fADD
1850MSTORE
1851DUP14
1852PUSH10x60
1854DUP3
1855ADD
1856MSTORE
1857DUP13
1858DUP16
1859DUP3
185aADD
185bMSTORE
185cDUP15
185dDUP2
185eMSTORE
185fPUSH20x1869
1862PUSH10xa0
1864DUP3
1865PUSH20x1ac8
1868JUMP
1869JUMPDEST
186aMLOAD
186bSWAP1
186cKECCAK256
186dPUSH10x40
186fMLOAD
1870PUSH10x20
1872DUP2
1873ADD
1874SWAP2
1875PUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
1896DUP4
1897MSTORE
1898CHAINID
1899PUSH10x40
189bDUP4
189cADD
189dMSTORE
189eADDRESS
189fPUSH10x60
18a1DUP4
18a2ADD
18a3MSTORE
18a4DUP16
18a5PUSH320x07e27cdd90594a3caa105e64724b3ff44d247017420d1a3df2cc49fa10cdf0f5
18c6SWAP1
18c7DUP4
18c8ADD
18c9MSTORE
18caDUP11
18cbDUP8
18ccAND
18cdPUSH10xa0
18cfDUP4
18d0ADD
18d1MSTORE
18d2PUSH10xc0
18d4DUP3
18d5ADD
18d6MSTORE
18d7PUSH10xc0
18d9DUP2
18daMSTORE
18dbPUSH20x034e
18dePUSH10xe0
18e0DUP3
18e1PUSH20x1ac8
18e4JUMP
18e5JUMPDEST
18e6POP
18e7PUSH20x1afd
18eaJUMP
18ebJUMPDEST
18ecAND
18edSWAP1
18eePUSH10x01
18f0PUSH10x01
18f2PUSH10x40
18f4SHL
18f5SUB
18f6NOT
18f7AND
18f8OR
18f9PUSH10x06
18fbSSTORE
18fcDUP6
18fdPUSH0
18feMSTORE
18ffPUSH10x07
1901PUSH10x20
1903MSTORE
1904PUSH10x40
1906PUSH0
1907KECCAK256
1908DUP3
1909PUSH0
190aMSTORE
190bPUSH10x20
190dMSTORE
190ePUSH10x40
1910PUSH0
1911KECCAK256
1912PUSH10x01
1914PUSH10xff
1916NOT
1917DUP3
1918SLOAD
1919AND
191aOR
191bSWAP1
191cSSTORE
191dDUP3
191eDUP2
191fSLOAD
1920SUB
1921DUP2
1922SSTORE
1923DUP3
1924PUSH10x05
1926SLOAD
1927SUB
1928PUSH10x05
192aSSTORE
192bDUP3
192cPUSH10x03
192eSLOAD
192fADD
1930PUSH10x03
1932SSTORE
1933PUSH10x01
1935PUSH10x01
1937PUSH10x40
1939SHL
193aSUB
193bPUSH10x01
193dDUP3
193eADD
193fSWAP2
1940DUP2
1941PUSH20x194c
1944DUP2
1945DUP6
1946SLOAD
1947AND
1948PUSH20x1afd
194bJUMP
194cJUMPDEST
194dAND
194eDUP3
194fNOT
1950DUP5
1951SLOAD
1952AND
1953OR
1954DUP4
1955SSTORE
1956PUSH20x195d
1959PUSH20x1f17
195cJUMP
195dJUMPDEST
195eSLOAD
195fSWAP2
1960SLOAD
1961AND
1962SWAP1
1963PUSH10x40
1965MLOAD
1966SWAP4
1967DUP5
1968MSTORE
1969PUSH10x20
196bDUP5
196cADD
196dMSTORE
196ePUSH10x40
1970DUP4
1971ADD
1972MSTORE
1973PUSH10x60
1975DUP3
1976ADD
1977MSTORE
1978LOG2
1979STOP
197aJUMPDEST
197bDUP8
197cDUP10
197dPUSH40x7c06acb7
1982PUSH10xe1
1984SHL
1985PUSH0
1986MSTORE
1987PUSH10x04
1989MSTORE
198aPUSH10x24
198cMSTORE
198dPUSH10x44
198fMSTORE
1990PUSH10x64
1992PUSH0
1993REVERT
1994JUMPDEST
1995DUP5
1996DUP8
1997PUSH40x0dd4fdfd
199cPUSH10xe2
199eSHL
199fPUSH0
19a0MSTORE
19a1PUSH10x04
19a3MSTORE
19a4PUSH10x24
19a6MSTORE
19a7PUSH10x44
19a9PUSH0
19aaREVERT
19abJUMPDEST
19acPUSH40x1f2a2005
19b1PUSH10xe0
19b3SHL
19b4PUSH0
19b5MSTORE
19b6PUSH10x04
19b8PUSH0
19b9REVERT
19baJUMPDEST
19bbPUSH40xd311bc39
19c0PUSH10xe0
19c2SHL
19c3PUSH0
19c4MSTORE
19c5PUSH10x04
19c7PUSH0
19c8REVERT
19c9JUMPDEST
19caCALLVALUE
19cbPUSH20x13a2
19ceJUMPI
19cfPUSH10x40
19d1CALLDATASIZE
19d2PUSH10x03
19d4NOT
19d5ADD
19d6SLT
19d7PUSH20x13a2
19daJUMPI
19dbPUSH10x20
19ddSWAP1
19dePUSH10x04
19e0CALLDATALOAD
19e1PUSH0
19e2MSTORE
19e3PUSH10x07
19e5DUP3
19e6MSTORE
19e7PUSH10x40
19e9PUSH0
19eaKECCAK256
19ebPUSH10x24
19edCALLDATALOAD
19eePUSH0
19efMSTORE
19f0DUP3
19f1MSTORE
19f2PUSH10xff
19f4PUSH10x40
19f6PUSH0
19f7KECCAK256
19f8SLOAD
19f9AND
19faISZERO
19fbISZERO
19fcDUP2
19fdMSTORE
19feRETURN
19ffJUMPDEST
1a00PUSH10x64
1a02CALLDATALOAD
1a03SWAP1
1a04PUSH10x01
1a06PUSH10x01
1a08PUSH10x40
1a0aSHL
1a0bSUB
1a0cDUP3
1a0dAND
1a0eDUP3
1a0fSUB
1a10PUSH20x13a2
1a13JUMPI
1a14JUMP
1a15JUMPDEST
1a16PUSH10x44
1a18CALLDATALOAD
1a19SWAP1
1a1aPUSH10x01
1a1cPUSH10x01
1a1ePUSH10x40
1a20SHL
1a21SUB
1a22DUP3
1a23AND
1a24DUP3
1a25SUB
1a26PUSH20x13a2
1a29JUMPI
1a2aJUMP
1a2bJUMPDEST
1a2cSWAP2
1a2dDUP2
1a2ePUSH10x1f
1a30DUP5
1a31ADD
1a32SLT
1a33ISZERO
1a34PUSH20x13a2
1a37JUMPI
1a38DUP3
1a39CALLDATALOAD
1a3aSWAP2
1a3bPUSH10x01
1a3dPUSH10x01
1a3fPUSH10x40
1a41SHL
1a42SUB