Final Chainexplorer Φ₀ · Final Chain · 20678
en

Contract

0xf772868578d82d8cd3ba106eb1d4da16a42e8d55

Address
0xf772868578d82d8cd3ba106eb1d4da16a42e8d55
Kind
verified contract FinalPhiSupply
Balance
0 vETH
Nonce
1
Code
7,635 bytes codehash 0xf68c71f1ac43800e5dfdcbab5b498f1c11b80116d033ffd72fe7cc9daaed2290

account tree

Tree
1 · accounts
Present
no leaf
Key
0x1fedf126d7910fd436a0719e73fbe8cd10bddbb82e8953fd32b61dcce67c63a7
Live root
0xe7eb646dfe4cfe9c975fd970c7c565c8d96e5c1edae4440e5f6a2560325fb328
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-07T05:00:52.405Z
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
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

contracts/finalchain/FinalChainPrecompiles.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

contracts/finalchain/FinalChainTime.sol

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

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

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

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

contracts/finalchain/FinalIdentityRegistry.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    mapping(bytes32 signerId => LmsBinding) private _lmsBinding;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        accountOfCertificate[live.certHash] = account;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}

contracts/finalchain/FinalPhiSupply.sol

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.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 {
    // ------------------------------------------------------------------ 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;

    bytes32 internal constant ACTION_SPAWN = keccak256("FinalPhiSupply.spawn.v01");
    bytes32 internal constant ACTION_DESPAWN = keccak256("FinalPhiSupply.despawn.v01");
    bytes32 internal constant ACTION_MOVE = keccak256("FinalPhiSupply.move.v01");

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

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

    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

    error NotAdmin(address caller);
    error ThresholdUnreachable(uint256 live, uint256 wanted);
    error NotConfigured();
    error ZeroAmount();
    error InsufficientHeld(uint256 want, uint256 have);
    error InsufficientAllocation(bytes32 chainRef, uint256 want, uint256 have);
    error AlreadyConsumed(bytes32 chainRef, uint256 originSeqId);
    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

    event Spawned(bytes32 indexed chainRef, uint256 amount, uint256 outstanding, uint64 epoch, uint64 seq);
    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
    );
    event SupplyConfigured(uint256 role, uint256 threshold);
    event Sealed();
    /// @notice A fresh supply took over the previous supply's ledger.
    event Seeded(uint256 allocations, uint256 despawns, uint64 nonce);

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

    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. NO-WIPE redeploy (ruled 2026-09-03); the supply
     *         gained the lane on 2026-09-05, when the registry cascade made it
     *         a fresh contract.
     * @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;
    }

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

contracts/finalchain/FinalPqQuorum.sol

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            valid++;
        }

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

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

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

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

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": "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": "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": "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": "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 · 7,635 bytes

0x6101a0806040526004361015610013575f80fd5b5f610160525f3560e01c9081620b82b51461158e5750806307a96b5c1461133d5780633fb27b85146112cf57806342cde4e8146112b257806345f7f2491461129557806372f56b2c1461125b5780637b103999146112175780638d0de241146111db5780638eae91b2146111b2578063902d55a51461118d57806394bc4e9614610eab578063a1aab18c146106cc578063a285aed7146106ac578063a2c7376c146104b8578063affed0e01461048f578063b19f480514610452578063e1c783cb14610422578063e96b949114610151578063f4e885db146101315763f851a440146100fd575f80fd5b3461012a576101605136600319011261012a5761016051546040516001600160a01b039091168152602090f35b6101605180fd5b3461012a576101605136600319011261012a576020600154604051908152f35b3461012a5760a036600319011261012a576044356004356024356101736115c4565b916084356001600160401b03811161012a576101939036906004016115f0565b939060025491821561040d5786156103f857831580156103f0575b80156103e7575b6103c9578361016051526004602052604061016051209283548089116103a95750926080926102ef602099937f918413a673b69ea329c13b81535c07cd4e55e8544a26871a6f0d841f203af09996600654936001600160401b0385169b8c6040518f81019182528c60408201528d6060820152898b8201528a815261023b60a082611620565b51902061016051506040518f8101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527f5315ade0aa736cdc5532fa19a1ed12e3ce89b6ad07040b14bac780938e50423b8c8301526001600160401b03871660a083015260c082015260c081526102c160e082611620565b51902090600154927f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6611788565b506001600160401b0361030189611655565b16906001600160401b03191617600655856101605152600488526040610160512090828154038155828254018255600181016001600160401b0361034781835416611655565b166001600160401b0319825416179055600182016001600160401b0361036f81835416611655565b166001600160401b03198254161790556103876119fa565b54905490604051928352888301526040820152856060820152a3604051908152f35b8886637c06acb760e11b6101605152600452602452604452606461016051fd5b50505063108b161760e01b6101605152600452602452604461016051fd5b508484146101b5565b5084156101ae565b631f2a200560e01b6101605152600461016051fd5b63d311bc3960e01b6101605152600461016051fd5b3461012a57602036600319011261012a576004356101605152600460205260206040610160512054604051908152f35b3461012a576101605136600319011261012a5760206040517fbc5858e168b959a61a8fb2d7957ef31dbed683a362770ca030e5d772cc44e0688152f35b3461012a576101605136600319011261012a5760206001600160401b0360065416604051908152f35b3461012a57608036600319011261012a576004356024356104d76115da565b916064356001600160401b03811161012a576104f79036906004016115f0565b929060025494851561040d5782156103f8576003549586841161068f57956080926105fa7f4565741fc5c16ad3a3a3fbbf4311621df9850edf4097f549079a6855d21331359593602099600654936001600160401b0385169a6040518d8101908d82528c6040820152896060820152606081526105748b82611620565b51902061016051506040518e8101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527fef06d5e846d5c9e7b4cf0eb3e3b66d716af68a3cea05cd7bc7743bbc177405228c8301526001600160401b03871660a083015260c082015260c081526102c160e082611620565b506001600160401b0361060c88611655565b16906001600160401b03191617600655846101605152600487528160406101605120910360035581815401815581600554016005556001600160401b0360018201918161065b81855416611655565b16821984541617835561066c6119fa565b5491541690604051928352878301526040820152846060820152a2604051908152f35b8684631125005160e21b6101605152600452602452604461016051fd5b3461012a576101605136600319011261012a576020600354604051908152f35b3461012a5761010036600319011261012a576004356001600160401b03811161012a576106fd9036906004016115f0565b610180526024356001600160401b03811161012a576107209036906004016115f0565b6044929192356001600160401b03811161012a576107429036906004016115f0565b60a0526080526064356001600160401b03811161012a576107679036906004016115f0565b61012052610100526084356001600160401b03811161012a5761078e9036906004016115f0565b60c05260e05260a4356101408190526001600160401b038116900361012a5760c4356001600160401b038116810361012a5760e4356001600160401b03811161012a576107df9036906004016115f0565b6040516328305db160e21b81529091906020816004817f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa66001600160a01b03165afa908115610cf3576101605191610e7c575b508015610dd7575b610b22575b5050506001600160401b036006541615801590610b17575b610b0257806101805114801590610af3575b610abc5760c0516101205103610abc576101605192839291905b6101805184106109e057846a52b7d2dcc80cd2e400000081116109b957806005556a52b7d2dcc80cd2e4000000036a52b7d2dcc80cd2e4000000811161099f57600355610160515b61012051811061094a576001600160401b0361014051166001600160401b031960065416176006556108fb6119fa565b7fb04da588109787f2e1bce4cc15cc0e414cae26034cea4902695311f75568bf8760606040516101805181526101205160208201526001600160401b0361014051166040820152a16101605180f35b8061095e6001926101205161010051611721565b35610160515260076020526040610160512061097f8260c05160e051611721565b356101605152602052604061016051208260ff19825416179055016108cb565b634e487b7160e01b61016051526011600452602461016051fd5b631125005160e21b61016051526004526a52b7d2dcc80cd2e4000000602452604461016051fd5b909192936109f2856101805186611721565b35158015610ad1575b610abc57610a0d856101805186611721565b35610160515260046020526040610160512060018101906001600160401b03825416610a945791610a8a91600193610a46898888611721565b3590556001600160401b03610a68610a638a60a051608051611721565b611745565b166001600160401b0319825416179055610a83878686611721565b3590611687565b9401929190610883565b610aa387876101805190611721565b3563724ac17360e01b6101605152600452602461016051fd5b63200ff7d760e11b6101605152600461016051fd5b506001600160401b03610aec610a638760a051608051611721565b16156109fb565b5060a051610180511415610869565b63dc63d81f60e01b6101605152600461016051fd5b506005541515610857565b60405160c060208201526020610b55610b4260e08401610180518a6116fd565b838103601f19016040850152878a6116fd565b601f1983820301606084015260a05181520181608051610160515b60a0518110610da5575050610bb5610b9e610bd593601f1984820301608085015261012051610100516116fd565b828103601f190160a084015260c05160e0516116fd565b6001600160401b03610140511660c083015203601f198101835282611620565b80516020909101206001600160a01b037f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6163b1561012a5782906001600160401b03604051956322f3f44760e11b8752610160515060848701927f08296c4851c7aca93c422c73902d61d179ed1bf52cf0ed257e6d096b9a8bb85160048901526024880152166044860152608060648601525260a4830160a060048460051b86010101928261016051905b828210610d0157505061016051938593508390039150829050837f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa66001600160a01b03165af18015610cf357610cd8575b808061083f565b61016051610ce591611620565b6101605161012a5783610cd1565b6040513d61016051823e3d90fd5b9091929394609f19600319888303010185528535607e198336030181121561012a57820180356001600160a01b0381169081900361012a57825260208101359160ff831680930361012a57610d98602092826001958580950152610d8a610d7f610d6e6040850185611694565b6080604086015260808501916116c5565b926060810190611694565b9160608185039101526116c5565b9701950193920190610c80565b91509161016051508235906001600160401b03821680920361012a576020816001938293520193019101908391610b70565b5060405163f5778b0360e01b81526020816004817f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa66001600160a01b03165afa908115610cf3576101605191610e3a575b506001600160a01b031633141561083a565b90506020813d602011610e74575b81610e5560209383611620565b8101031261012a57516001600160a01b038116810361012a5787610e28565b3d9150610e48565b610e9e915060203d602011610ea4575b610e968183611620565b8101906116e5565b87610832565b503d610e8c565b3461118957608036600319011261118957602435600435610eca6115da565b6064356001600160401b03811161118957610ee99036906004016115f0565b5f546001600160a01b03163303610ff7575b50505081610f43575b807f43c4ef2494de90aa2f24830e48f3dc8579dec67c48d59b2ffba50c125576d4c4926040926001558060025582519182526020820152a16101605180f35b60405163342f616360e01b8152600481018290526020816024817f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa66001600160a01b03165afa908115610cf3576101605191610fc5575b50828110610fa85750610f04565b9050633770da3360e11b6101605152600452602452604461016051fd5b90506020813d602011610fef575b81610fe060209383611620565b8101031261012a575183610f9a565b3d9150610fd3565b60408051602081018681528183018890529181527f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa66001600160a01b0316939190611043606082611620565b519020833b156111895790826001600160401b039593926040519687956322f3f44760e11b875260848701927fbc5858e168b959a61a8fb2d7957ef31dbed683a362770ca030e5d772cc44e06860048901526024880152166044860152608060648601525260a4830160a060048460051b8601010192825f90607e19813603015b83831061110f5750505050505091815f818582965003925af18015611104576110ef575b8080610efb565b5f6110f991611620565b5f61016052826110e8565b6040513d5f823e3d90fd5b60a3198a88030185529496509294919390929186358281121561118957830180356001600160a01b0381169081900361118957825260208101359160ff831680930361118957611177602092826001958580950152610d8a610d7f610d6e6040850185611694565b980196019301909188969594926110c4565b5f80fd5b34611189575f3660031901126111895760206040516a52b7d2dcc80cd2e40000008152f35b34611189575f3660031901126111895760206111d360035460055490611687565b604051908152f35b34611189576020366003190112611189576004355f5260046020526040805f206001600160401b03600182549201541682519182526020820152f35b34611189575f366003190112611189576040517f000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa66001600160a01b03168152602090f35b34611189575f3660031901126111895760206040517f08296c4851c7aca93c422c73902d61d179ed1bf52cf0ed257e6d096b9a8bb8518152f35b34611189575f366003190112611189576020600554604051908152f35b34611189575f366003190112611189576020600254604051908152f35b34611189575f366003190112611189575f546001600160a01b038116330361132a576bffffffffffffffffffffffff60a01b165f557f1b2d71eb44f882534bf4e86f940c56ccc869ffb927e2bab86561de93950c22165f80a1005b630bd4212160e11b5f523360045260245ffd5b346111895760a03660031901126111895760043560243560443561135f6115c4565b6084356001600160401b0381116111895761137e9036906004016115f0565b9091600254801561157f57851561157057865f52600760205260405f20855f5260205260ff60405f20541661155957865f52600460205260405f2093845480881161153f57506114b0886114aa7f3ef380598f06b01333350e61cefa066c10a2088a64ad65aadd22eda04b972b8999979560809997956001600160401b03956006549787891695604051602081019188835260408201528d60608201528c8f8201528e815261142e60a082611620565b51902060405160208101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301528f7f07e27cdd90594a3caa105e64724b3ff44d247017420d1a3df2cc49fa10cdf0f5908301528a871660a083015260c082015260c081526102c160e082611620565b50611655565b16906001600160401b03191617600655855f52600760205260405f20825f5260205260405f20600160ff19825416179055828154038155826005540360055582600354016003556001600160401b0360018201918161151181855416611655565b1682198454161783556115226119fa565b5491541690604051938452602084015260408301526060820152a2005b8789637c06acb760e11b5f5260045260245260445260645ffd5b8487630dd4fdfd60e21b5f5260045260245260445ffd5b631f2a200560e01b5f5260045ffd5b63d311bc3960e01b5f5260045ffd5b34611189576040366003190112611189576020906004355f526007825260405f206024355f52825260ff60405f20541615158152f35b606435906001600160401b038216820361118957565b604435906001600160401b038216820361118957565b9181601f84011215611189578235916001600160401b038311611189576020808501948460051b01011161118957565b90601f801991011681019081106001600160401b0382111761164157604052565b634e487b7160e01b5f52604160045260245ffd5b6001600160401b036001911601906001600160401b03821161167357565b634e487b7160e01b5f52601160045260245ffd5b9190820180921161167357565b9035601e19823603018112156111895701602081359101916001600160401b03821161118957813603831361118957565b908060209392818452848401375f828201840152601f01601f1916010190565b90816020910312611189575180151581036111895790565b81835290916001600160fb1b0383116111895760209260051b809284830137010190565b91908110156117315760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160401b03811681036111895790565b9190820391821161167357565b356001600160a01b03811681036111895790565b3560ff811681036111895790565b94939195965f9785156119eb576001600160401b03164381116119d5576102586117b28243611759565b116119bf5750604051936020850152602084526117d0604085611620565b5f945f985b888a1015611995578960051b840135607e1985360301811215611189578401966117fe88611766565b6001600160a01b039182169116811015611969575061181c87611766565b9661185d60208761182c84611766565b604051632e4bfa5160e11b81526001600160a01b039091166004820152602481019190915291829081906044820190565b03816001600160a01b038e165afa908115611104575f9161194b575b5015611924576020810190600460ff6118918461177a565b16036118f1576118a288828c611b1a565b156118bd5750505f198114611673576001998a0199016117d5565b906118d26118cc60ff93611766565b9161177a565b9063bbf82ba360e01b5f5260018060a01b03166004521660245260445ffd5b906119006118cc60ff93611766565b9063587548c360e11b5f5260018060a01b031660045216602452600460445260645ffd5b61192e8691611766565b63ae8bb03960e01b5f5260018060a01b031660045260245260445ffd5b611963915060203d8111610ea457610e968183611620565b5f611879565b61197288611766565b6311641feb60e21b5f9081526004929092526001600160a01b0316602452604490fd5b985095509550505050508083106119a95750565b826305bc216760e51b5f5260045260245260445ffd5b630ed38fd160e41b5f526004524360245260445ffd5b637b51505560e01b5f526004524360245260445ffd5b631fc460bf60e11b5f5260045ffd5b600354600554906a52b7d2dcc80cd2e4000000611a178383611687565b03611a20575050565b63e0724a2b60e01b5f5260045260245260445ffd5b6001600160401b03811161164157601f01601f191660200190565b602081830312611189578051906001600160401b038211611189570181601f8201121561118957805190611a8382611a35565b92611a916040519485611620565b8284526020838301011161118957815f9260208093018386015e8301015290565b903590601e198136030182121561118957018035906001600160401b0382116111895760200191813603831361118957565b929192611af082611a35565b91611afe6040519384611620565b829481845281830111611189578281602093845f960137010152565b9160208201600460ff611b2c8361177a565b1614611bde5760ff611b3f60059261177a565b1614611b4c575050505f90565b5f611b5683611766565b604051639e5adaeb60e01b81526001600160a01b0391821660048201529485916024918391165afa91821561110457611baf935f93611bb2575b50611ba2816040611ba9930190611ab2565b3691611ae4565b91611d4f565b90565b611ba9919350611bd6611ba2913d805f833e611bce8183611620565b810190611a50565b939150611b90565b505f611be983611766565b60405163b7af85d760e01b81526001600160a01b0391821660048201529485916024918391165afa91821561110457611baf935f93611c3b575b50611ba2816040611c35930190611ab2565b91611c5f565b611c35919350611c57611ba2913d805f833e611bce8183611620565b939150611c23565b610a20815114801590611d42575b611d3b576020611cc05f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282611620565b51906102045afa3d15611d34573d611cd781611a35565b90611ce56040519283611620565b81523d5f602083013e5b81611d28575b81611cfe575090565b9050602081519101519060208110611d17575b50151590565b5f199060200360031b1b165f611d11565b80516020149150611cf5565b6060611cef565b5050505f90565b5061121383511415611c6d565b6040815114801590611dc6575b611d3b576020611daf5f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282611620565b51906102055afa3d15611d34573d611cd781611a35565b5061746083511415611d5c56
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
0025PUSH20x158e
0028JUMPI
0029POP
002aDUP1
002bPUSH40x07a96b5c
0030EQ
0031PUSH20x133d
0034JUMPI
0035DUP1
0036PUSH40x3fb27b85
003bEQ
003cPUSH20x12cf
003fJUMPI
0040DUP1
0041PUSH40x42cde4e8
0046EQ
0047PUSH20x12b2
004aJUMPI
004bDUP1
004cPUSH40x45f7f249
0051EQ
0052PUSH20x1295
0055JUMPI
0056DUP1
0057PUSH40x72f56b2c
005cEQ
005dPUSH20x125b
0060JUMPI
0061DUP1
0062PUSH40x7b103999
0067EQ
0068PUSH20x1217
006bJUMPI
006cDUP1
006dPUSH40x8d0de241
0072EQ
0073PUSH20x11db
0076JUMPI
0077DUP1
0078PUSH40x8eae91b2
007dEQ
007ePUSH20x11b2
0081JUMPI
0082DUP1
0083PUSH40x902d55a5
0088EQ
0089PUSH20x118d
008cJUMPI
008dDUP1
008ePUSH40x94bc4e96
0093EQ
0094PUSH20x0eab
0097JUMPI
0098DUP1
0099PUSH40xa1aab18c
009eEQ
009fPUSH20x06cc
00a2JUMPI
00a3DUP1
00a4PUSH40xa285aed7
00a9EQ
00aaPUSH20x06ac
00adJUMPI
00aeDUP1
00afPUSH40xa2c7376c
00b4EQ
00b5PUSH20x04b8
00b8JUMPI
00b9DUP1
00baPUSH40xaffed0e0
00bfEQ
00c0PUSH20x048f
00c3JUMPI
00c4DUP1
00c5PUSH40xb19f4805
00caEQ
00cbPUSH20x0452
00ceJUMPI
00cfDUP1
00d0PUSH40xe1c783cb
00d5EQ
00d6PUSH20x0422
00d9JUMPI
00daDUP1
00dbPUSH40xe96b9491
00e0EQ
00e1PUSH20x0151
00e4JUMPI
00e5DUP1
00e6PUSH40xf4e885db
00ebEQ
00ecPUSH20x0131
00efJUMPI
00f0PUSH40xf851a440
00f5EQ
00f6PUSH20x00fd
00f9JUMPI
00faPUSH0
00fbDUP1
00fcREVERT
00fdJUMPDEST
00feCALLVALUE
00ffPUSH20x012a
0102JUMPI
0103PUSH20x0160
0106MLOAD
0107CALLDATASIZE
0108PUSH10x03
010aNOT
010bADD
010cSLT
010dPUSH20x012a
0110JUMPI
0111PUSH20x0160
0114MLOAD
0115SLOAD
0116PUSH10x40
0118MLOAD
0119PUSH10x01
011bPUSH10x01
011dPUSH10xa0
011fSHL
0120SUB
0121SWAP1
0122SWAP2
0123AND
0124DUP2
0125MSTORE
0126PUSH10x20
0128SWAP1
0129RETURN
012aJUMPDEST
012bPUSH20x0160
012eMLOAD
012fDUP1
0130REVERT
0131JUMPDEST
0132CALLVALUE
0133PUSH20x012a
0136JUMPI
0137PUSH20x0160
013aMLOAD
013bCALLDATASIZE
013cPUSH10x03
013eNOT
013fADD
0140SLT
0141PUSH20x012a
0144JUMPI
0145PUSH10x20
0147PUSH10x01
0149SLOAD
014aPUSH10x40
014cMLOAD
014dSWAP1
014eDUP2
014fMSTORE
0150RETURN
0151JUMPDEST
0152CALLVALUE
0153PUSH20x012a
0156JUMPI
0157PUSH10xa0
0159CALLDATASIZE
015aPUSH10x03
015cNOT
015dADD
015eSLT
015fPUSH20x012a
0162JUMPI
0163PUSH10x44
0165CALLDATALOAD
0166PUSH10x04
0168CALLDATALOAD
0169PUSH10x24
016bCALLDATALOAD
016cPUSH20x0173
016fPUSH20x15c4
0172JUMP
0173JUMPDEST
0174SWAP2
0175PUSH10x84
0177CALLDATALOAD
0178PUSH10x01
017aPUSH10x01
017cPUSH10x40
017eSHL
017fSUB
0180DUP2
0181GT
0182PUSH20x012a
0185JUMPI
0186PUSH20x0193
0189SWAP1
018aCALLDATASIZE
018bSWAP1
018cPUSH10x04
018eADD
018fPUSH20x15f0
0192JUMP
0193JUMPDEST
0194SWAP4
0195SWAP1
0196PUSH10x02
0198SLOAD
0199SWAP2
019aDUP3
019bISZERO
019cPUSH20x040d
019fJUMPI
01a0DUP7
01a1ISZERO
01a2PUSH20x03f8
01a5JUMPI
01a6DUP4
01a7ISZERO
01a8DUP1
01a9ISZERO
01aaPUSH20x03f0
01adJUMPI
01aeJUMPDEST
01afDUP1
01b0ISZERO
01b1PUSH20x03e7
01b4JUMPI
01b5JUMPDEST
01b6PUSH20x03c9
01b9JUMPI
01baDUP4
01bbPUSH20x0160
01beMLOAD
01bfMSTORE
01c0PUSH10x04
01c2PUSH10x20
01c4MSTORE
01c5PUSH10x40
01c7PUSH20x0160
01caMLOAD
01cbKECCAK256
01ccSWAP3
01cdDUP4
01ceSLOAD
01cfDUP1
01d0DUP10
01d1GT
01d2PUSH20x03a9
01d5JUMPI
01d6POP
01d7SWAP3
01d8PUSH10x80
01daSWAP3
01dbPUSH20x02ef
01dePUSH10x20
01e0SWAP10
01e1SWAP4
01e2PUSH320x918413a673b69ea329c13b81535c07cd4e55e8544a26871a6f0d841f203af099
0203SWAP7
0204PUSH10x06
0206SLOAD
0207SWAP4
0208PUSH10x01
020aPUSH10x01
020cPUSH10x40
020eSHL
020fSUB
0210DUP6
0211AND
0212SWAP12
0213DUP13
0214PUSH10x40
0216MLOAD
0217DUP16
0218DUP2
0219ADD
021aSWAP2
021bDUP3
021cMSTORE
021dDUP13
021ePUSH10x40
0220DUP3
0221ADD
0222MSTORE
0223DUP14
0224PUSH10x60
0226DUP3
0227ADD
0228MSTORE
0229DUP10
022aDUP12
022bDUP3
022cADD
022dMSTORE
022eDUP11
022fDUP2
0230MSTORE
0231PUSH20x023b
0234PUSH10xa0
0236DUP3
0237PUSH20x1620
023aJUMP
023bJUMPDEST
023cMLOAD
023dSWAP1
023eKECCAK256
023fPUSH20x0160
0242MLOAD
0243POP
0244PUSH10x40
0246MLOAD
0247DUP16
0248DUP2
0249ADD
024aSWAP2
024bPUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
026cDUP4
026dMSTORE
026eCHAINID
026fPUSH10x40
0271DUP4
0272ADD
0273MSTORE
0274ADDRESS
0275PUSH10x60
0277DUP4
0278ADD
0279MSTORE
027aPUSH320x5315ade0aa736cdc5532fa19a1ed12e3ce89b6ad07040b14bac780938e50423b
029bDUP13
029cDUP4
029dADD
029eMSTORE
029fPUSH10x01
02a1PUSH10x01
02a3PUSH10x40
02a5SHL
02a6SUB
02a7DUP8
02a8AND
02a9PUSH10xa0
02abDUP4
02acADD
02adMSTORE
02aePUSH10xc0
02b0DUP3
02b1ADD
02b2MSTORE
02b3PUSH10xc0
02b5DUP2
02b6MSTORE
02b7PUSH20x02c1
02baPUSH10xe0
02bcDUP3
02bdPUSH20x1620
02c0JUMP
02c1JUMPDEST
02c2MLOAD
02c3SWAP1
02c4KECCAK256
02c5SWAP1
02c6PUSH10x01
02c8SLOAD
02c9SWAP3
02caPUSH320x000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6
02ebPUSH20x1788
02eeJUMP
02efJUMPDEST
02f0POP
02f1PUSH10x01
02f3PUSH10x01
02f5PUSH10x40
02f7SHL
02f8SUB
02f9PUSH20x0301
02fcDUP10
02fdPUSH20x1655
0300JUMP
0301JUMPDEST
0302AND
0303SWAP1
0304PUSH10x01
0306PUSH10x01
0308PUSH10x40
030aSHL
030bSUB
030cNOT
030dAND
030eOR
030fPUSH10x06
0311SSTORE
0312DUP6
0313PUSH20x0160
0316MLOAD
0317MSTORE
0318PUSH10x04
031aDUP9
031bMSTORE
031cPUSH10x40
031ePUSH20x0160
0321MLOAD
0322KECCAK256
0323SWAP1
0324DUP3
0325DUP2
0326SLOAD
0327SUB
0328DUP2
0329SSTORE
032aDUP3
032bDUP3
032cSLOAD
032dADD
032eDUP3
032fSSTORE
0330PUSH10x01
0332DUP2
0333ADD
0334PUSH10x01
0336PUSH10x01
0338PUSH10x40
033aSHL
033bSUB
033cPUSH20x0347
033fDUP2
0340DUP4
0341SLOAD
0342AND
0343PUSH20x1655
0346JUMP
0347JUMPDEST
0348AND
0349PUSH10x01
034bPUSH10x01
034dPUSH10x40
034fSHL
0350SUB
0351NOT
0352DUP3
0353SLOAD
0354AND
0355OR
0356SWAP1
0357SSTORE
0358PUSH10x01
035aDUP3
035bADD
035cPUSH10x01
035ePUSH10x01
0360PUSH10x40
0362SHL
0363SUB
0364PUSH20x036f
0367DUP2
0368DUP4
0369SLOAD
036aAND
036bPUSH20x1655
036eJUMP
036fJUMPDEST
0370AND
0371PUSH10x01
0373PUSH10x01
0375PUSH10x40
0377SHL
0378SUB
0379NOT
037aDUP3
037bSLOAD
037cAND
037dOR
037eSWAP1
037fSSTORE
0380PUSH20x0387
0383PUSH20x19fa
0386JUMP
0387JUMPDEST
0388SLOAD
0389SWAP1
038aSLOAD
038bSWAP1
038cPUSH10x40
038eMLOAD
038fSWAP3
0390DUP4
0391MSTORE
0392DUP9
0393DUP4
0394ADD
0395MSTORE
0396PUSH10x40
0398DUP3
0399ADD
039aMSTORE
039bDUP6
039cPUSH10x60
039eDUP3
039fADD
03a0MSTORE
03a1LOG3
03a2PUSH10x40
03a4MLOAD
03a5SWAP1
03a6DUP2
03a7MSTORE
03a8RETURN
03a9JUMPDEST
03aaDUP9
03abDUP7
03acPUSH40x7c06acb7
03b1PUSH10xe1
03b3SHL
03b4PUSH20x0160
03b7MLOAD
03b8MSTORE
03b9PUSH10x04
03bbMSTORE
03bcPUSH10x24
03beMSTORE
03bfPUSH10x44
03c1MSTORE
03c2PUSH10x64
03c4PUSH20x0160
03c7MLOAD
03c8REVERT
03c9JUMPDEST
03caPOP
03cbPOP
03ccPOP
03cdPUSH40x108b1617
03d2PUSH10xe0
03d4SHL
03d5PUSH20x0160
03d8MLOAD
03d9MSTORE
03daPUSH10x04
03dcMSTORE
03ddPUSH10x24
03dfMSTORE
03e0PUSH10x44
03e2PUSH20x0160
03e5MLOAD
03e6REVERT
03e7JUMPDEST
03e8POP
03e9DUP5
03eaDUP5
03ebEQ
03ecPUSH20x01b5
03efJUMP
03f0JUMPDEST
03f1POP
03f2DUP5
03f3ISZERO
03f4PUSH20x01ae
03f7JUMP
03f8JUMPDEST
03f9PUSH40x1f2a2005
03fePUSH10xe0
0400SHL
0401PUSH20x0160
0404MLOAD
0405MSTORE
0406PUSH10x04
0408PUSH20x0160
040bMLOAD
040cREVERT
040dJUMPDEST
040ePUSH40xd311bc39
0413PUSH10xe0
0415SHL
0416PUSH20x0160
0419MLOAD
041aMSTORE
041bPUSH10x04
041dPUSH20x0160
0420MLOAD
0421REVERT
0422JUMPDEST
0423CALLVALUE
0424PUSH20x012a
0427JUMPI
0428PUSH10x20
042aCALLDATASIZE
042bPUSH10x03
042dNOT
042eADD
042fSLT
0430PUSH20x012a
0433JUMPI
0434PUSH10x04
0436CALLDATALOAD
0437PUSH20x0160
043aMLOAD
043bMSTORE
043cPUSH10x04
043ePUSH10x20
0440MSTORE
0441PUSH10x20
0443PUSH10x40
0445PUSH20x0160
0448MLOAD
0449KECCAK256
044aSLOAD
044bPUSH10x40
044dMLOAD
044eSWAP1
044fDUP2
0450MSTORE
0451RETURN
0452JUMPDEST
0453CALLVALUE
0454PUSH20x012a
0457JUMPI
0458PUSH20x0160
045bMLOAD
045cCALLDATASIZE
045dPUSH10x03
045fNOT
0460ADD
0461SLT
0462PUSH20x012a
0465JUMPI
0466PUSH10x20
0468PUSH10x40
046aMLOAD
046bPUSH320xbc5858e168b959a61a8fb2d7957ef31dbed683a362770ca030e5d772cc44e068
048cDUP2
048dMSTORE
048eRETURN
048fJUMPDEST
0490CALLVALUE
0491PUSH20x012a
0494JUMPI
0495PUSH20x0160
0498MLOAD
0499CALLDATASIZE
049aPUSH10x03
049cNOT
049dADD
049eSLT
049fPUSH20x012a
04a2JUMPI
04a3PUSH10x20
04a5PUSH10x01
04a7PUSH10x01
04a9PUSH10x40
04abSHL
04acSUB
04adPUSH10x06
04afSLOAD
04b0AND
04b1PUSH10x40
04b3MLOAD
04b4SWAP1
04b5DUP2
04b6MSTORE
04b7RETURN
04b8JUMPDEST
04b9CALLVALUE
04baPUSH20x012a
04bdJUMPI
04bePUSH10x80
04c0CALLDATASIZE
04c1PUSH10x03
04c3NOT
04c4ADD
04c5SLT
04c6PUSH20x012a
04c9JUMPI
04caPUSH10x04
04ccCALLDATALOAD
04cdPUSH10x24
04cfCALLDATALOAD
04d0PUSH20x04d7
04d3PUSH20x15da
04d6JUMP
04d7JUMPDEST
04d8SWAP2
04d9PUSH10x64
04dbCALLDATALOAD
04dcPUSH10x01
04dePUSH10x01
04e0PUSH10x40
04e2SHL
04e3SUB
04e4DUP2
04e5GT
04e6PUSH20x012a
04e9JUMPI
04eaPUSH20x04f7
04edSWAP1
04eeCALLDATASIZE
04efSWAP1
04f0PUSH10x04
04f2ADD
04f3PUSH20x15f0
04f6JUMP
04f7JUMPDEST
04f8SWAP3
04f9SWAP1
04faPUSH10x02
04fcSLOAD
04fdSWAP5
04feDUP6
04ffISZERO
0500PUSH20x040d
0503JUMPI
0504DUP3
0505ISZERO
0506PUSH20x03f8
0509JUMPI
050aPUSH10x03
050cSLOAD
050dSWAP6
050eDUP7
050fDUP5
0510GT
0511PUSH20x068f
0514JUMPI
0515SWAP6
0516PUSH10x80
0518SWAP3
0519PUSH20x05fa
051cPUSH320x4565741fc5c16ad3a3a3fbbf4311621df9850edf4097f549079a6855d2133135
053dSWAP6
053eSWAP4
053fPUSH10x20
0541SWAP10
0542PUSH10x06
0544SLOAD
0545SWAP4
0546PUSH10x01
0548PUSH10x01
054aPUSH10x40
054cSHL
054dSUB
054eDUP6
054fAND
0550SWAP11
0551PUSH10x40
0553MLOAD
0554DUP14
0555DUP2
0556ADD
0557SWAP1
0558DUP14
0559DUP3
055aMSTORE
055bDUP13
055cPUSH10x40
055eDUP3
055fADD
0560MSTORE
0561DUP10
0562PUSH10x60
0564DUP3
0565ADD
0566MSTORE
0567PUSH10x60
0569DUP2
056aMSTORE
056bPUSH20x0574
056eDUP12
056fDUP3
0570PUSH20x1620
0573JUMP
0574JUMPDEST
0575MLOAD
0576SWAP1
0577KECCAK256
0578PUSH20x0160
057bMLOAD
057cPOP
057dPUSH10x40
057fMLOAD
0580DUP15
0581DUP2
0582ADD
0583SWAP2
0584PUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
05a5DUP4
05a6MSTORE
05a7CHAINID
05a8PUSH10x40
05aaDUP4
05abADD
05acMSTORE
05adADDRESS
05aePUSH10x60
05b0DUP4
05b1ADD
05b2MSTORE
05b3PUSH320xef06d5e846d5c9e7b4cf0eb3e3b66d716af68a3cea05cd7bc7743bbc17740522
05d4DUP13
05d5DUP4
05d6ADD
05d7MSTORE
05d8PUSH10x01
05daPUSH10x01
05dcPUSH10x40
05deSHL
05dfSUB
05e0DUP8
05e1AND
05e2PUSH10xa0
05e4DUP4
05e5ADD
05e6MSTORE
05e7PUSH10xc0
05e9DUP3
05eaADD
05ebMSTORE
05ecPUSH10xc0
05eeDUP2
05efMSTORE
05f0PUSH20x02c1
05f3PUSH10xe0
05f5DUP3
05f6PUSH20x1620
05f9JUMP
05faJUMPDEST
05fbPOP
05fcPUSH10x01
05fePUSH10x01
0600PUSH10x40
0602SHL
0603SUB
0604PUSH20x060c
0607DUP9
0608PUSH20x1655
060bJUMP
060cJUMPDEST
060dAND
060eSWAP1
060fPUSH10x01
0611PUSH10x01
0613PUSH10x40
0615SHL
0616SUB
0617NOT
0618AND
0619OR
061aPUSH10x06
061cSSTORE
061dDUP5
061ePUSH20x0160
0621MLOAD
0622MSTORE
0623PUSH10x04
0625DUP8
0626MSTORE
0627DUP2
0628PUSH10x40
062aPUSH20x0160
062dMLOAD
062eKECCAK256
062fSWAP2
0630SUB
0631PUSH10x03
0633SSTORE
0634DUP2
0635DUP2
0636SLOAD
0637ADD
0638DUP2
0639SSTORE
063aDUP2
063bPUSH10x05
063dSLOAD
063eADD
063fPUSH10x05
0641SSTORE
0642PUSH10x01
0644PUSH10x01
0646PUSH10x40
0648SHL
0649SUB
064aPUSH10x01
064cDUP3
064dADD
064eSWAP2
064fDUP2
0650PUSH20x065b
0653DUP2
0654DUP6
0655SLOAD
0656AND
0657PUSH20x1655
065aJUMP
065bJUMPDEST
065cAND
065dDUP3
065eNOT
065fDUP5
0660SLOAD
0661AND
0662OR
0663DUP4
0664SSTORE
0665PUSH20x066c
0668PUSH20x19fa
066bJUMP
066cJUMPDEST
066dSLOAD
066eSWAP2
066fSLOAD
0670AND
0671SWAP1
0672PUSH10x40
0674MLOAD
0675SWAP3
0676DUP4
0677MSTORE
0678DUP8
0679DUP4
067aADD
067bMSTORE
067cPUSH10x40
067eDUP3
067fADD
0680MSTORE
0681DUP5
0682PUSH10x60
0684DUP3
0685ADD
0686MSTORE
0687LOG2
0688PUSH10x40
068aMLOAD
068bSWAP1
068cDUP2
068dMSTORE
068eRETURN
068fJUMPDEST
0690DUP7
0691DUP5
0692PUSH40x11250051
0697PUSH10xe2
0699SHL
069aPUSH20x0160
069dMLOAD
069eMSTORE
069fPUSH10x04
06a1MSTORE
06a2PUSH10x24
06a4MSTORE
06a5PUSH10x44
06a7PUSH20x0160
06aaMLOAD
06abREVERT
06acJUMPDEST
06adCALLVALUE
06aePUSH20x012a
06b1JUMPI
06b2PUSH20x0160
06b5MLOAD
06b6CALLDATASIZE
06b7PUSH10x03
06b9NOT
06baADD
06bbSLT
06bcPUSH20x012a
06bfJUMPI
06c0PUSH10x20
06c2PUSH10x03
06c4SLOAD
06c5PUSH10x40
06c7MLOAD
06c8SWAP1
06c9DUP2
06caMSTORE
06cbRETURN
06ccJUMPDEST
06cdCALLVALUE
06cePUSH20x012a
06d1JUMPI
06d2PUSH20x0100
06d5CALLDATASIZE
06d6PUSH10x03
06d8NOT
06d9ADD
06daSLT
06dbPUSH20x012a
06deJUMPI
06dfPUSH10x04
06e1CALLDATALOAD
06e2PUSH10x01
06e4PUSH10x01
06e6PUSH10x40
06e8SHL
06e9SUB
06eaDUP2
06ebGT
06ecPUSH20x012a
06efJUMPI
06f0PUSH20x06fd
06f3SWAP1
06f4CALLDATASIZE
06f5SWAP1
06f6PUSH10x04
06f8ADD
06f9PUSH20x15f0
06fcJUMP
06fdJUMPDEST
06fePUSH20x0180
0701MSTORE
0702PUSH10x24
0704CALLDATALOAD
0705PUSH10x01
0707PUSH10x01
0709PUSH10x40
070bSHL
070cSUB
070dDUP2
070eGT
070fPUSH20x012a
0712JUMPI
0713PUSH20x0720
0716SWAP1
0717CALLDATASIZE
0718SWAP1
0719PUSH10x04
071bADD
071cPUSH20x15f0
071fJUMP
0720JUMPDEST
0721PUSH10x44
0723SWAP3
0724SWAP2
0725SWAP3
0726CALLDATALOAD
0727PUSH10x01
0729PUSH10x01
072bPUSH10x40
072dSHL
072eSUB
072fDUP2
0730GT
0731PUSH20x012a
0734JUMPI
0735PUSH20x0742
0738SWAP1
0739CALLDATASIZE
073aSWAP1
073bPUSH10x04
073dADD
073ePUSH20x15f0
0741JUMP
0742JUMPDEST
0743PUSH10xa0
0745MSTORE
0746PUSH10x80
0748MSTORE
0749PUSH10x64
074bCALLDATALOAD
074cPUSH10x01
074ePUSH10x01
0750PUSH10x40
0752SHL
0753SUB
0754DUP2
0755GT
0756PUSH20x012a
0759JUMPI
075aPUSH20x0767
075dSWAP1
075eCALLDATASIZE
075fSWAP1
0760PUSH10x04
0762ADD
0763PUSH20x15f0
0766JUMP
0767JUMPDEST
0768PUSH20x0120
076bMSTORE
076cPUSH20x0100
076fMSTORE
0770PUSH10x84
0772CALLDATALOAD
0773PUSH10x01
0775PUSH10x01
0777PUSH10x40
0779SHL
077aSUB
077bDUP2
077cGT
077dPUSH20x012a
0780JUMPI
0781PUSH20x078e
0784SWAP1
0785CALLDATASIZE
0786SWAP1
0787PUSH10x04
0789ADD
078aPUSH20x15f0
078dJUMP
078eJUMPDEST
078fPUSH10xc0
0791MSTORE
0792PUSH10xe0
0794MSTORE
0795PUSH10xa4
0797CALLDATALOAD
0798PUSH20x0140
079bDUP2
079cSWAP1
079dMSTORE
079ePUSH10x01
07a0PUSH10x01
07a2PUSH10x40
07a4SHL
07a5SUB
07a6DUP2
07a7AND
07a8SWAP1
07a9SUB
07aaPUSH20x012a
07adJUMPI
07aePUSH10xc4
07b0CALLDATALOAD
07b1PUSH10x01
07b3PUSH10x01
07b5PUSH10x40
07b7SHL
07b8SUB
07b9DUP2
07baAND
07bbDUP2
07bcSUB
07bdPUSH20x012a
07c0JUMPI
07c1PUSH10xe4
07c3CALLDATALOAD
07c4PUSH10x01
07c6PUSH10x01
07c8PUSH10x40
07caSHL
07cbSUB
07ccDUP2
07cdGT
07cePUSH20x012a
07d1JUMPI
07d2PUSH20x07df
07d5SWAP1
07d6CALLDATASIZE
07d7SWAP1
07d8PUSH10x04
07daADD
07dbPUSH20x15f0
07deJUMP
07dfJUMPDEST
07e0PUSH10x40
07e2MLOAD
07e3PUSH40x28305db1
07e8PUSH10xe2
07eaSHL
07ebDUP2
07ecMSTORE
07edSWAP1
07eeSWAP2
07efSWAP1
07f0PUSH10x20
07f2DUP2
07f3PUSH10x04
07f5DUP2
07f6PUSH320x000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6
0817PUSH10x01
0819PUSH10x01
081bPUSH10xa0
081dSHL
081eSUB
081fAND
0820GAS
0821STATICCALL
0822SWAP1
0823DUP2
0824ISZERO
0825PUSH20x0cf3
0828JUMPI
0829PUSH20x0160
082cMLOAD
082dSWAP2
082ePUSH20x0e7c
0831JUMPI
0832JUMPDEST
0833POP
0834DUP1
0835ISZERO
0836PUSH20x0dd7
0839JUMPI
083aJUMPDEST
083bPUSH20x0b22
083eJUMPI
083fJUMPDEST
0840POP
0841POP
0842POP
0843PUSH10x01
0845PUSH10x01
0847PUSH10x40
0849SHL
084aSUB
084bPUSH10x06
084dSLOAD
084eAND
084fISZERO
0850DUP1
0851ISZERO
0852SWAP1
0853PUSH20x0b17
0856JUMPI
0857JUMPDEST
0858PUSH20x0b02
085bJUMPI
085cDUP1
085dPUSH20x0180
0860MLOAD
0861EQ
0862DUP1
0863ISZERO
0864SWAP1
0865PUSH20x0af3
0868JUMPI
0869JUMPDEST
086aPUSH20x0abc
086dJUMPI
086ePUSH10xc0
0870MLOAD
0871PUSH20x0120
0874MLOAD
0875SUB
0876PUSH20x0abc
0879JUMPI
087aPUSH20x0160
087dMLOAD
087eSWAP3
087fDUP4
0880SWAP3
0881SWAP2
0882SWAP1
0883JUMPDEST
0884PUSH20x0180
0887MLOAD
0888DUP5
0889LT
088aPUSH20x09e0
088dJUMPI
088eDUP5
088fPUSH110x52b7d2dcc80cd2e4000000
089bDUP2
089cGT
089dPUSH20x09b9
08a0JUMPI
08a1DUP1
08a2PUSH10x05
08a4SSTORE
08a5PUSH110x52b7d2dcc80cd2e4000000
08b1SUB
08b2PUSH110x52b7d2dcc80cd2e4000000
08beDUP2
08bfGT
08c0PUSH20x099f
08c3JUMPI
08c4PUSH10x03
08c6SSTORE
08c7PUSH20x0160
08caMLOAD
08cbJUMPDEST
08ccPUSH20x0120
08cfMLOAD
08d0DUP2
08d1LT
08d2PUSH20x094a
08d5JUMPI
08d6PUSH10x01
08d8PUSH10x01
08daPUSH10x40
08dcSHL
08ddSUB
08dePUSH20x0140
08e1MLOAD
08e2AND
08e3PUSH10x01
08e5PUSH10x01
08e7PUSH10x40
08e9SHL
08eaSUB
08ebNOT
08ecPUSH10x06
08eeSLOAD
08efAND
08f0OR
08f1PUSH10x06
08f3SSTORE
08f4PUSH20x08fb
08f7PUSH20x19fa
08faJUMP
08fbJUMPDEST
08fcPUSH320xb04da588109787f2e1bce4cc15cc0e414cae26034cea4902695311f75568bf87
091dPUSH10x60
091fPUSH10x40
0921MLOAD
0922PUSH20x0180
0925MLOAD
0926DUP2
0927MSTORE
0928PUSH20x0120
092bMLOAD
092cPUSH10x20
092eDUP3
092fADD
0930MSTORE
0931PUSH10x01
0933PUSH10x01
0935PUSH10x40
0937SHL
0938SUB
0939PUSH20x0140
093cMLOAD
093dAND
093ePUSH10x40
0940DUP3
0941ADD
0942MSTORE
0943LOG1
0944PUSH20x0160
0947MLOAD
0948DUP1
0949RETURN
094aJUMPDEST
094bDUP1
094cPUSH20x095e
094fPUSH10x01
0951SWAP3
0952PUSH20x0120
0955MLOAD
0956PUSH20x0100
0959MLOAD
095aPUSH20x1721
095dJUMP
095eJUMPDEST
095fCALLDATALOAD
0960PUSH20x0160
0963MLOAD
0964MSTORE
0965PUSH10x07
0967PUSH10x20
0969MSTORE
096aPUSH10x40
096cPUSH20x0160
096fMLOAD
0970KECCAK256
0971PUSH20x097f
0974DUP3
0975PUSH10xc0
0977MLOAD
0978PUSH10xe0
097aMLOAD
097bPUSH20x1721
097eJUMP
097fJUMPDEST
0980CALLDATALOAD
0981PUSH20x0160
0984MLOAD
0985MSTORE
0986PUSH10x20
0988MSTORE
0989PUSH10x40
098bPUSH20x0160
098eMLOAD
098fKECCAK256
0990DUP3
0991PUSH10xff
0993NOT
0994DUP3
0995SLOAD
0996AND
0997OR
0998SWAP1
0999SSTORE
099aADD
099bPUSH20x08cb
099eJUMP
099fJUMPDEST
09a0PUSH40x4e487b71
09a5PUSH10xe0
09a7SHL
09a8PUSH20x0160
09abMLOAD
09acMSTORE
09adPUSH10x11
09afPUSH10x04
09b1MSTORE
09b2PUSH10x24
09b4PUSH20x0160
09b7MLOAD
09b8REVERT
09b9JUMPDEST
09baPUSH40x11250051
09bfPUSH10xe2
09c1SHL
09c2PUSH20x0160
09c5MLOAD
09c6MSTORE
09c7PUSH10x04
09c9MSTORE
09caPUSH110x52b7d2dcc80cd2e4000000
09d6PUSH10x24
09d8MSTORE
09d9PUSH10x44
09dbPUSH20x0160
09deMLOAD
09dfREVERT
09e0JUMPDEST
09e1SWAP1
09e2SWAP2
09e3SWAP3
09e4SWAP4
09e5PUSH20x09f2
09e8DUP6
09e9PUSH20x0180
09ecMLOAD
09edDUP7
09eePUSH20x1721
09f1JUMP
09f2JUMPDEST
09f3CALLDATALOAD
09f4ISZERO
09f5DUP1
09f6ISZERO
09f7PUSH20x0ad1
09faJUMPI
09fbJUMPDEST
09fcPUSH20x0abc
09ffJUMPI
0a00PUSH20x0a0d
0a03DUP6
0a04PUSH20x0180
0a07MLOAD
0a08DUP7
0a09PUSH20x1721
0a0cJUMP
0a0dJUMPDEST
0a0eCALLDATALOAD
0a0fPUSH20x0160
0a12MLOAD
0a13MSTORE
0a14PUSH10x04
0a16PUSH10x20
0a18MSTORE
0a19PUSH10x40
0a1bPUSH20x0160
0a1eMLOAD
0a1fKECCAK256
0a20PUSH10x01
0a22DUP2
0a23ADD
0a24SWAP1
0a25PUSH10x01
0a27PUSH10x01
0a29PUSH10x40
0a2bSHL
0a2cSUB
0a2dDUP3
0a2eSLOAD
0a2fAND
0a30PUSH20x0a94
0a33JUMPI
0a34SWAP2
0a35PUSH20x0a8a
0a38SWAP2
0a39PUSH10x01
0a3bSWAP4
0a3cPUSH20x0a46
0a3fDUP10
0a40DUP9
0a41DUP9
0a42PUSH20x1721
0a45JUMP
0a46JUMPDEST
0a47CALLDATALOAD
0a48SWAP1
0a49SSTORE
0a4aPUSH10x01
0a4cPUSH10x01
0a4ePUSH10x40
0a50SHL
0a51SUB
0a52PUSH20x0a68
0a55PUSH20x0a63
0a58DUP11
0a59PUSH10xa0
0a5bMLOAD
0a5cPUSH10x80
0a5eMLOAD
0a5fPUSH20x1721
0a62JUMP
0a63JUMPDEST
0a64PUSH20x1745
0a67JUMP
0a68JUMPDEST
0a69AND
0a6aPUSH10x01
0a6cPUSH10x01
0a6ePUSH10x40
0a70SHL
0a71SUB
0a72NOT
0a73DUP3
0a74SLOAD
0a75AND
0a76OR
0a77SWAP1
0a78SSTORE
0a79PUSH20x0a83
0a7cDUP8
0a7dDUP7
0a7eDUP7
0a7fPUSH20x1721
0a82JUMP
0a83JUMPDEST
0a84CALLDATALOAD
0a85SWAP1
0a86PUSH20x1687
0a89JUMP
0a8aJUMPDEST
0a8bSWAP5
0a8cADD
0a8dSWAP3
0a8eSWAP2
0a8fSWAP1
0a90PUSH20x0883
0a93JUMP
0a94JUMPDEST
0a95PUSH20x0aa3
0a98DUP8
0a99DUP8
0a9aPUSH20x0180
0a9dMLOAD
0a9eSWAP1
0a9fPUSH20x1721
0aa2JUMP
0aa3JUMPDEST
0aa4CALLDATALOAD
0aa5PUSH40x724ac173
0aaaPUSH10xe0
0aacSHL
0aadPUSH20x0160
0ab0MLOAD
0ab1MSTORE
0ab2PUSH10x04
0ab4MSTORE
0ab5PUSH10x24
0ab7PUSH20x0160
0abaMLOAD
0abbREVERT
0abcJUMPDEST
0abdPUSH40x200ff7d7
0ac2PUSH10xe1
0ac4SHL
0ac5PUSH20x0160
0ac8MLOAD
0ac9MSTORE
0acaPUSH10x04
0accPUSH20x0160
0acfMLOAD
0ad0REVERT
0ad1JUMPDEST
0ad2POP
0ad3PUSH10x01
0ad5PUSH10x01
0ad7PUSH10x40
0ad9SHL
0adaSUB
0adbPUSH20x0aec
0adePUSH20x0a63
0ae1DUP8
0ae2PUSH10xa0
0ae4MLOAD
0ae5PUSH10x80
0ae7MLOAD
0ae8PUSH20x1721
0aebJUMP
0aecJUMPDEST
0aedAND
0aeeISZERO
0aefPUSH20x09fb
0af2JUMP
0af3JUMPDEST
0af4POP
0af5PUSH10xa0
0af7MLOAD
0af8PUSH20x0180
0afbMLOAD
0afcEQ
0afdISZERO
0afePUSH20x0869
0b01JUMP
0b02JUMPDEST
0b03PUSH40xdc63d81f
0b08PUSH10xe0
0b0aSHL
0b0bPUSH20x0160
0b0eMLOAD
0b0fMSTORE
0b10PUSH10x04
0b12PUSH20x0160
0b15MLOAD
0b16REVERT
0b17JUMPDEST
0b18POP
0b19PUSH10x05
0b1bSLOAD
0b1cISZERO
0b1dISZERO
0b1ePUSH20x0857
0b21JUMP
0b22JUMPDEST
0b23PUSH10x40
0b25MLOAD
0b26PUSH10xc0
0b28PUSH10x20
0b2aDUP3
0b2bADD
0b2cMSTORE
0b2dPUSH10x20
0b2fPUSH20x0b55
0b32PUSH20x0b42
0b35PUSH10xe0
0b37DUP5
0b38ADD
0b39PUSH20x0180
0b3cMLOAD
0b3dDUP11
0b3ePUSH20x16fd
0b41JUMP
0b42JUMPDEST
0b43DUP4
0b44DUP2
0b45SUB
0b46PUSH10x1f
0b48NOT
0b49ADD
0b4aPUSH10x40
0b4cDUP6
0b4dADD
0b4eMSTORE
0b4fDUP8
0b50DUP11
0b51PUSH20x16fd
0b54JUMP
0b55JUMPDEST
0b56PUSH10x1f
0b58NOT
0b59DUP4
0b5aDUP3
0b5bSUB
0b5cADD
0b5dPUSH10x60
0b5fDUP5
0b60ADD
0b61MSTORE
0b62PUSH10xa0
0b64MLOAD
0b65DUP2
0b66MSTORE
0b67ADD
0b68DUP2
0b69PUSH10x80
0b6bMLOAD
0b6cPUSH20x0160
0b6fMLOAD
0b70JUMPDEST
0b71PUSH10xa0
0b73MLOAD
0b74DUP2
0b75LT
0b76PUSH20x0da5
0b79JUMPI
0b7aPOP
0b7bPOP
0b7cPUSH20x0bb5
0b7fPUSH20x0b9e
0b82PUSH20x0bd5
0b85SWAP4
0b86PUSH10x1f
0b88NOT
0b89DUP5
0b8aDUP3
0b8bSUB
0b8cADD
0b8dPUSH10x80
0b8fDUP6
0b90ADD
0b91MSTORE
0b92PUSH20x0120
0b95MLOAD
0b96PUSH20x0100
0b99MLOAD
0b9aPUSH20x16fd
0b9dJUMP
0b9eJUMPDEST
0b9fDUP3
0ba0DUP2
0ba1SUB
0ba2PUSH10x1f
0ba4NOT
0ba5ADD
0ba6PUSH10xa0
0ba8DUP5
0ba9ADD
0baaMSTORE
0babPUSH10xc0
0badMLOAD
0baePUSH10xe0
0bb0MLOAD
0bb1PUSH20x16fd
0bb4JUMP
0bb5JUMPDEST
0bb6PUSH10x01
0bb8PUSH10x01
0bbaPUSH10x40
0bbcSHL
0bbdSUB
0bbePUSH20x0140
0bc1MLOAD
0bc2AND
0bc3PUSH10xc0
0bc5DUP4
0bc6ADD
0bc7MSTORE
0bc8SUB
0bc9PUSH10x1f
0bcbNOT
0bccDUP2
0bcdADD
0bceDUP4
0bcfMSTORE
0bd0DUP3
0bd1PUSH20x1620
0bd4JUMP
0bd5JUMPDEST
0bd6DUP1
0bd7MLOAD
0bd8PUSH10x20
0bdaSWAP1
0bdbSWAP2
0bdcADD
0bddKECCAK256
0bdePUSH10x01
0be0PUSH10x01
0be2PUSH10xa0
0be4SHL
0be5SUB
0be6PUSH320x000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6
0c07AND
0c08EXTCODESIZE
0c09ISZERO
0c0aPUSH20x012a
0c0dJUMPI
0c0eDUP3
0c0fSWAP1
0c10PUSH10x01
0c12PUSH10x01
0c14PUSH10x40
0c16SHL
0c17SUB
0c18PUSH10x40
0c1aMLOAD
0c1bSWAP6
0c1cPUSH40x22f3f447
0c21PUSH10xe1
0c23SHL
0c24DUP8
0c25MSTORE
0c26PUSH20x0160
0c29MLOAD
0c2aPOP
0c2bPUSH10x84
0c2dDUP8
0c2eADD
0c2fSWAP3
0c30PUSH320x08296c4851c7aca93c422c73902d61d179ed1bf52cf0ed257e6d096b9a8bb851
0c51PUSH10x04
0c53DUP10
0c54ADD
0c55MSTORE
0c56PUSH10x24
0c58DUP9
0c59ADD
0c5aMSTORE
0c5bAND
0c5cPUSH10x44
0c5eDUP7
0c5fADD
0c60MSTORE
0c61PUSH10x80
0c63PUSH10x64
0c65DUP7
0c66ADD
0c67MSTORE
0c68MSTORE
0c69PUSH10xa4
0c6bDUP4
0c6cADD
0c6dPUSH10xa0
0c6fPUSH10x04
0c71DUP5
0c72PUSH10x05
0c74SHL
0c75DUP7
0c76ADD
0c77ADD
0c78ADD
0c79SWAP3
0c7aDUP3
0c7bPUSH20x0160
0c7eMLOAD
0c7fSWAP1
0c80JUMPDEST
0c81DUP3
0c82DUP3
0c83LT
0c84PUSH20x0d01
0c87JUMPI
0c88POP
0c89POP
0c8aPUSH20x0160
0c8dMLOAD
0c8eSWAP4
0c8fDUP6
0c90SWAP4
0c91POP
0c92DUP4
0c93SWAP1
0c94SUB
0c95SWAP2
0c96POP
0c97DUP3
0c98SWAP1
0c99POP
0c9aDUP4
0c9bPUSH320x000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6
0cbcPUSH10x01
0cbePUSH10x01
0cc0PUSH10xa0
0cc2SHL
0cc3SUB
0cc4AND
0cc5GAS
0cc6CALL
0cc7DUP1
0cc8ISZERO
0cc9PUSH20x0cf3
0cccJUMPI
0ccdPUSH20x0cd8
0cd0JUMPI
0cd1JUMPDEST
0cd2DUP1
0cd3DUP1
0cd4PUSH20x083f
0cd7JUMP
0cd8JUMPDEST
0cd9PUSH20x0160
0cdcMLOAD
0cddPUSH20x0ce5
0ce0SWAP2
0ce1PUSH20x1620
0ce4JUMP
0ce5JUMPDEST
0ce6PUSH20x0160
0ce9MLOAD
0ceaPUSH20x012a
0cedJUMPI
0ceeDUP4
0cefPUSH20x0cd1
0cf2JUMP
0cf3JUMPDEST
0cf4PUSH10x40
0cf6MLOAD
0cf7RETURNDATASIZE
0cf8PUSH20x0160
0cfbMLOAD
0cfcDUP3
0cfdRETURNDATACOPY
0cfeRETURNDATASIZE
0cffSWAP1
0d00REVERT
0d01JUMPDEST
0d02SWAP1
0d03SWAP2
0d04SWAP3
0d05SWAP4
0d06SWAP5
0d07PUSH10x9f
0d09NOT
0d0aPUSH10x03
0d0cNOT
0d0dDUP9
0d0eDUP4
0d0fSUB
0d10ADD
0d11ADD
0d12DUP6
0d13MSTORE
0d14DUP6
0d15CALLDATALOAD
0d16PUSH10x7e
0d18NOT
0d19DUP4
0d1aCALLDATASIZE
0d1bSUB
0d1cADD
0d1dDUP2
0d1eSLT
0d1fISZERO
0d20PUSH20x012a
0d23JUMPI
0d24DUP3
0d25ADD
0d26DUP1
0d27CALLDATALOAD
0d28PUSH10x01
0d2aPUSH10x01
0d2cPUSH10xa0
0d2eSHL
0d2fSUB
0d30DUP2
0d31AND
0d32SWAP1
0d33DUP2
0d34SWAP1
0d35SUB
0d36PUSH20x012a
0d39JUMPI
0d3aDUP3
0d3bMSTORE
0d3cPUSH10x20
0d3eDUP2
0d3fADD
0d40CALLDATALOAD
0d41SWAP2
0d42PUSH10xff
0d44DUP4
0d45AND
0d46DUP1
0d47SWAP4
0d48SUB
0d49PUSH20x012a
0d4cJUMPI
0d4dPUSH20x0d98
0d50PUSH10x20
0d52SWAP3
0d53DUP3
0d54PUSH10x01
0d56SWAP6
0d57DUP6
0d58DUP1
0d59SWAP6
0d5aADD
0d5bMSTORE
0d5cPUSH20x0d8a
0d5fPUSH20x0d7f
0d62PUSH20x0d6e
0d65PUSH10x40
0d67DUP6
0d68ADD
0d69DUP6
0d6aPUSH20x1694
0d6dJUMP
0d6eJUMPDEST
0d6fPUSH10x80
0d71PUSH10x40
0d73DUP7
0d74ADD
0d75MSTORE
0d76PUSH10x80
0d78DUP6
0d79ADD
0d7aSWAP2
0d7bPUSH20x16c5
0d7eJUMP
0d7fJUMPDEST
0d80SWAP3
0d81PUSH10x60
0d83DUP2
0d84ADD
0d85SWAP1
0d86PUSH20x1694
0d89JUMP
0d8aJUMPDEST
0d8bSWAP2
0d8cPUSH10x60
0d8eDUP2
0d8fDUP6
0d90SUB
0d91SWAP2
0d92ADD
0d93MSTORE
0d94PUSH20x16c5
0d97JUMP
0d98JUMPDEST
0d99SWAP8
0d9aADD
0d9bSWAP6
0d9cADD
0d9dSWAP4
0d9eSWAP3
0d9fADD
0da0SWAP1
0da1PUSH20x0c80
0da4JUMP
0da5JUMPDEST
0da6SWAP2
0da7POP
0da8SWAP2
0da9PUSH20x0160
0dacMLOAD
0dadPOP
0daeDUP3
0dafCALLDATALOAD
0db0SWAP1
0db1PUSH10x01
0db3PUSH10x01
0db5PUSH10x40
0db7SHL
0db8SUB
0db9DUP3
0dbaAND
0dbbDUP1
0dbcSWAP3
0dbdSUB
0dbePUSH20x012a
0dc1JUMPI
0dc2PUSH10x20
0dc4DUP2
0dc5PUSH10x01
0dc7SWAP4
0dc8DUP3
0dc9SWAP4
0dcaMSTORE
0dcbADD
0dccSWAP4
0dcdADD
0dceSWAP2
0dcfADD
0dd0SWAP1
0dd1DUP4
0dd2SWAP2
0dd3PUSH20x0b70
0dd6JUMP
0dd7JUMPDEST
0dd8POP
0dd9PUSH10x40
0ddbMLOAD
0ddcPUSH40xf5778b03
0de1PUSH10xe0
0de3SHL
0de4DUP2
0de5MSTORE
0de6PUSH10x20
0de8DUP2
0de9PUSH10x04
0debDUP2
0decPUSH320x000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6
0e0dPUSH10x01
0e0fPUSH10x01
0e11PUSH10xa0
0e13SHL
0e14SUB
0e15AND
0e16GAS
0e17STATICCALL
0e18SWAP1
0e19DUP2
0e1aISZERO
0e1bPUSH20x0cf3
0e1eJUMPI
0e1fPUSH20x0160
0e22MLOAD
0e23SWAP2
0e24PUSH20x0e3a
0e27JUMPI
0e28JUMPDEST
0e29POP
0e2aPUSH10x01
0e2cPUSH10x01
0e2ePUSH10xa0
0e30SHL
0e31SUB
0e32AND
0e33CALLER
0e34EQ
0e35ISZERO
0e36PUSH20x083a
0e39JUMP
0e3aJUMPDEST
0e3bSWAP1
0e3cPOP
0e3dPUSH10x20
0e3fDUP2
0e40RETURNDATASIZE
0e41PUSH10x20
0e43GT
0e44PUSH20x0e74
0e47JUMPI
0e48JUMPDEST
0e49DUP2
0e4aPUSH20x0e55
0e4dPUSH10x20
0e4fSWAP4
0e50DUP4
0e51PUSH20x1620
0e54JUMP
0e55JUMPDEST
0e56DUP2
0e57ADD
0e58SUB
0e59SLT
0e5aPUSH20x012a
0e5dJUMPI
0e5eMLOAD
0e5fPUSH10x01
0e61PUSH10x01
0e63PUSH10xa0
0e65SHL
0e66SUB
0e67DUP2
0e68AND
0e69DUP2
0e6aSUB
0e6bPUSH20x012a
0e6eJUMPI
0e6fDUP8
0e70PUSH20x0e28
0e73JUMP
0e74JUMPDEST
0e75RETURNDATASIZE
0e76SWAP2
0e77POP
0e78PUSH20x0e48
0e7bJUMP
0e7cJUMPDEST
0e7dPUSH20x0e9e
0e80SWAP2
0e81POP
0e82PUSH10x20
0e84RETURNDATASIZE
0e85PUSH10x20
0e87GT
0e88PUSH20x0ea4
0e8bJUMPI
0e8cJUMPDEST
0e8dPUSH20x0e96
0e90DUP2
0e91DUP4
0e92PUSH20x1620
0e95JUMP
0e96JUMPDEST
0e97DUP2
0e98ADD
0e99SWAP1
0e9aPUSH20x16e5
0e9dJUMP
0e9eJUMPDEST
0e9fDUP8
0ea0PUSH20x0832
0ea3JUMP
0ea4JUMPDEST
0ea5POP
0ea6RETURNDATASIZE
0ea7PUSH20x0e8c
0eaaJUMP
0eabJUMPDEST
0eacCALLVALUE
0eadPUSH20x1189
0eb0JUMPI
0eb1PUSH10x80
0eb3CALLDATASIZE
0eb4PUSH10x03
0eb6NOT
0eb7ADD
0eb8SLT
0eb9PUSH20x1189
0ebcJUMPI
0ebdPUSH10x24
0ebfCALLDATALOAD
0ec0PUSH10x04
0ec2CALLDATALOAD
0ec3PUSH20x0eca
0ec6PUSH20x15da
0ec9JUMP
0ecaJUMPDEST
0ecbPUSH10x64
0ecdCALLDATALOAD
0ecePUSH10x01
0ed0PUSH10x01
0ed2PUSH10x40
0ed4SHL
0ed5SUB
0ed6DUP2
0ed7GT
0ed8PUSH20x1189
0edbJUMPI
0edcPUSH20x0ee9
0edfSWAP1
0ee0CALLDATASIZE
0ee1SWAP1
0ee2PUSH10x04
0ee4ADD
0ee5PUSH20x15f0
0ee8JUMP
0ee9JUMPDEST
0eeaPUSH0
0eebSLOAD
0eecPUSH10x01
0eeePUSH10x01
0ef0PUSH10xa0
0ef2SHL
0ef3SUB
0ef4AND
0ef5CALLER
0ef6SUB
0ef7PUSH20x0ff7
0efaJUMPI
0efbJUMPDEST
0efcPOP
0efdPOP
0efePOP
0effDUP2
0f00PUSH20x0f43
0f03JUMPI
0f04JUMPDEST
0f05DUP1
0f06PUSH320x43c4ef2494de90aa2f24830e48f3dc8579dec67c48d59b2ffba50c125576d4c4
0f27SWAP3
0f28PUSH10x40
0f2aSWAP3
0f2bPUSH10x01
0f2dSSTORE
0f2eDUP1
0f2fPUSH10x02
0f31SSTORE
0f32DUP3
0f33MLOAD
0f34SWAP2
0f35DUP3
0f36MSTORE
0f37PUSH10x20
0f39DUP3
0f3aADD
0f3bMSTORE
0f3cLOG1
0f3dPUSH20x0160
0f40MLOAD
0f41DUP1
0f42RETURN
0f43JUMPDEST
0f44PUSH10x40
0f46MLOAD
0f47PUSH40x342f6163
0f4cPUSH10xe0
0f4eSHL
0f4fDUP2
0f50MSTORE
0f51PUSH10x04
0f53DUP2
0f54ADD
0f55DUP3
0f56SWAP1
0f57MSTORE
0f58PUSH10x20
0f5aDUP2
0f5bPUSH10x24
0f5dDUP2
0f5ePUSH320x000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6
0f7fPUSH10x01
0f81PUSH10x01
0f83PUSH10xa0
0f85SHL
0f86SUB
0f87AND
0f88GAS
0f89STATICCALL
0f8aSWAP1
0f8bDUP2
0f8cISZERO
0f8dPUSH20x0cf3
0f90JUMPI
0f91PUSH20x0160
0f94MLOAD
0f95SWAP2
0f96PUSH20x0fc5
0f99JUMPI
0f9aJUMPDEST
0f9bPOP
0f9cDUP3
0f9dDUP2
0f9eLT
0f9fPUSH20x0fa8
0fa2JUMPI
0fa3POP
0fa4PUSH20x0f04
0fa7JUMP
0fa8JUMPDEST
0fa9SWAP1
0faaPOP
0fabPUSH40x3770da33
0fb0PUSH10xe1
0fb2SHL
0fb3PUSH20x0160
0fb6MLOAD
0fb7MSTORE
0fb8PUSH10x04
0fbaMSTORE
0fbbPUSH10x24
0fbdMSTORE
0fbePUSH10x44
0fc0PUSH20x0160
0fc3MLOAD
0fc4REVERT
0fc5JUMPDEST
0fc6SWAP1
0fc7POP
0fc8PUSH10x20
0fcaDUP2
0fcbRETURNDATASIZE
0fccPUSH10x20
0fceGT
0fcfPUSH20x0fef
0fd2JUMPI
0fd3JUMPDEST
0fd4DUP2
0fd5PUSH20x0fe0
0fd8PUSH10x20
0fdaSWAP4
0fdbDUP4
0fdcPUSH20x1620
0fdfJUMP
0fe0JUMPDEST
0fe1DUP2
0fe2ADD
0fe3SUB
0fe4SLT
0fe5PUSH20x012a
0fe8JUMPI
0fe9MLOAD
0feaDUP4
0febPUSH20x0f9a
0feeJUMP
0fefJUMPDEST
0ff0RETURNDATASIZE
0ff1SWAP2
0ff2POP
0ff3PUSH20x0fd3
0ff6JUMP
0ff7JUMPDEST
0ff8PUSH10x40
0ffaDUP1
0ffbMLOAD
0ffcPUSH10x20
0ffeDUP2
0fffADD
1000DUP7
1001DUP2
1002MSTORE
1003DUP2
1004DUP4
1005ADD
1006DUP9
1007SWAP1
1008MSTORE
1009SWAP2
100aDUP2
100bMSTORE
100cPUSH320x000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6
102dPUSH10x01
102fPUSH10x01
1031PUSH10xa0
1033SHL
1034SUB
1035AND
1036SWAP4
1037SWAP2
1038SWAP1
1039PUSH20x1043
103cPUSH10x60
103eDUP3
103fPUSH20x1620
1042JUMP
1043JUMPDEST
1044MLOAD
1045SWAP1
1046KECCAK256
1047DUP4
1048EXTCODESIZE
1049ISZERO
104aPUSH20x1189
104dJUMPI
104eSWAP1
104fDUP3
1050PUSH10x01
1052PUSH10x01
1054PUSH10x40
1056SHL
1057SUB
1058SWAP6
1059SWAP4
105aSWAP3
105bPUSH10x40
105dMLOAD
105eSWAP7
105fDUP8
1060SWAP6
1061PUSH40x22f3f447
1066PUSH10xe1
1068SHL
1069DUP8
106aMSTORE
106bPUSH10x84
106dDUP8
106eADD
106fSWAP3
1070PUSH320xbc5858e168b959a61a8fb2d7957ef31dbed683a362770ca030e5d772cc44e068
1091PUSH10x04
1093DUP10
1094ADD
1095MSTORE
1096PUSH10x24
1098DUP9
1099ADD
109aMSTORE
109bAND
109cPUSH10x44
109eDUP7
109fADD
10a0MSTORE
10a1PUSH10x80
10a3PUSH10x64
10a5DUP7
10a6ADD
10a7MSTORE
10a8MSTORE
10a9PUSH10xa4
10abDUP4
10acADD
10adPUSH10xa0
10afPUSH10x04
10b1DUP5
10b2PUSH10x05
10b4SHL
10b5DUP7
10b6ADD
10b7ADD
10b8ADD
10b9SWAP3
10baDUP3
10bbPUSH0
10bcSWAP1
10bdPUSH10x7e
10bfNOT
10c0DUP2
10c1CALLDATASIZE
10c2SUB
10c3ADD
10c4JUMPDEST
10c5DUP4
10c6DUP4
10c7LT
10c8PUSH20x110f
10cbJUMPI
10ccPOP
10cdPOP
10cePOP
10cfPOP
10d0POP
10d1POP
10d2SWAP2
10d3DUP2
10d4PUSH0
10d5DUP2
10d6DUP6
10d7DUP3
10d8SWAP7
10d9POP
10daSUB
10dbSWAP3
10dcGAS
10ddCALL
10deDUP1
10dfISZERO
10e0PUSH20x1104
10e3JUMPI
10e4PUSH20x10ef
10e7JUMPI
10e8JUMPDEST
10e9DUP1
10eaDUP1
10ebPUSH20x0efb
10eeJUMP
10efJUMPDEST
10f0PUSH0
10f1PUSH20x10f9
10f4SWAP2
10f5PUSH20x1620
10f8JUMP
10f9JUMPDEST
10faPUSH0
10fbPUSH20x0160
10feMSTORE
10ffDUP3
1100PUSH20x10e8
1103JUMP
1104JUMPDEST
1105PUSH10x40
1107MLOAD
1108RETURNDATASIZE
1109PUSH0
110aDUP3
110bRETURNDATACOPY
110cRETURNDATASIZE
110dSWAP1
110eREVERT
110fJUMPDEST
1110PUSH10xa3
1112NOT
1113DUP11
1114DUP9
1115SUB
1116ADD
1117DUP6
1118MSTORE
1119SWAP5
111aSWAP7
111bPOP
111cSWAP3
111dSWAP5
111eSWAP2
111fSWAP4
1120SWAP1
1121SWAP3
1122SWAP2
1123DUP7
1124CALLDATALOAD
1125DUP3
1126DUP2
1127SLT
1128ISZERO
1129PUSH20x1189
112cJUMPI
112dDUP4
112eADD
112fDUP1
1130CALLDATALOAD
1131PUSH10x01
1133PUSH10x01
1135PUSH10xa0
1137SHL
1138SUB
1139DUP2
113aAND
113bSWAP1
113cDUP2
113dSWAP1
113eSUB
113fPUSH20x1189
1142JUMPI
1143DUP3
1144MSTORE
1145PUSH10x20
1147DUP2
1148ADD
1149CALLDATALOAD
114aSWAP2
114bPUSH10xff
114dDUP4
114eAND
114fDUP1
1150SWAP4
1151SUB
1152PUSH20x1189
1155JUMPI
1156PUSH20x1177
1159PUSH10x20
115bSWAP3
115cDUP3
115dPUSH10x01
115fSWAP6
1160DUP6
1161DUP1
1162SWAP6
1163ADD
1164MSTORE
1165PUSH20x0d8a
1168PUSH20x0d7f
116bPUSH20x0d6e
116ePUSH10x40
1170DUP6
1171ADD
1172DUP6
1173PUSH20x1694
1176JUMP
1177JUMPDEST
1178SWAP9
1179ADD
117aSWAP7
117bADD
117cSWAP4
117dADD
117eSWAP1
117fSWAP2
1180DUP9
1181SWAP7
1182SWAP6
1183SWAP5
1184SWAP3
1185PUSH20x10c4
1188JUMP
1189JUMPDEST
118aPUSH0
118bDUP1
118cREVERT
118dJUMPDEST
118eCALLVALUE
118fPUSH20x1189
1192JUMPI
1193PUSH0
1194CALLDATASIZE
1195PUSH10x03
1197NOT
1198ADD
1199SLT
119aPUSH20x1189
119dJUMPI
119ePUSH10x20
11a0PUSH10x40
11a2MLOAD
11a3PUSH110x52b7d2dcc80cd2e4000000
11afDUP2
11b0MSTORE
11b1RETURN
11b2JUMPDEST
11b3CALLVALUE
11b4PUSH20x1189
11b7JUMPI
11b8PUSH0
11b9CALLDATASIZE
11baPUSH10x03
11bcNOT
11bdADD
11beSLT
11bfPUSH20x1189
11c2JUMPI
11c3PUSH10x20
11c5PUSH20x11d3
11c8PUSH10x03
11caSLOAD
11cbPUSH10x05
11cdSLOAD
11ceSWAP1
11cfPUSH20x1687
11d2JUMP
11d3JUMPDEST
11d4PUSH10x40
11d6MLOAD
11d7SWAP1
11d8DUP2
11d9MSTORE
11daRETURN
11dbJUMPDEST
11dcCALLVALUE
11ddPUSH20x1189
11e0JUMPI
11e1PUSH10x20
11e3CALLDATASIZE
11e4PUSH10x03
11e6NOT
11e7ADD
11e8SLT
11e9PUSH20x1189
11ecJUMPI
11edPUSH10x04
11efCALLDATALOAD
11f0PUSH0
11f1MSTORE
11f2PUSH10x04
11f4PUSH10x20
11f6MSTORE
11f7PUSH10x40
11f9DUP1
11faPUSH0
11fbKECCAK256
11fcPUSH10x01
11fePUSH10x01
1200PUSH10x40
1202SHL
1203SUB
1204PUSH10x01
1206DUP3
1207SLOAD
1208SWAP3
1209ADD
120aSLOAD
120bAND
120cDUP3
120dMLOAD
120eSWAP2
120fDUP3
1210MSTORE
1211PUSH10x20
1213DUP3
1214ADD
1215MSTORE
1216RETURN
1217JUMPDEST
1218CALLVALUE
1219PUSH20x1189
121cJUMPI
121dPUSH0
121eCALLDATASIZE
121fPUSH10x03
1221NOT
1222ADD
1223SLT
1224PUSH20x1189
1227JUMPI
1228PUSH10x40
122aMLOAD
122bPUSH320x000000000000000000000000a2e71fc2fb02d1ce93aa958e56cab83d26f3bfa6
124cPUSH10x01
124ePUSH10x01
1250PUSH10xa0
1252SHL
1253SUB
1254AND
1255DUP2
1256MSTORE
1257PUSH10x20
1259SWAP1
125aRETURN
125bJUMPDEST
125cCALLVALUE
125dPUSH20x1189
1260JUMPI
1261PUSH0
1262CALLDATASIZE
1263PUSH10x03
1265NOT
1266ADD
1267SLT
1268PUSH20x1189
126bJUMPI
126cPUSH10x20
126ePUSH10x40
1270MLOAD
1271PUSH320x08296c4851c7aca93c422c73902d61d179ed1bf52cf0ed257e6d096b9a8bb851
1292DUP2
1293MSTORE
1294RETURN
1295JUMPDEST
1296CALLVALUE
1297PUSH20x1189
129aJUMPI
129bPUSH0
129cCALLDATASIZE
129dPUSH10x03
129fNOT
12a0ADD
12a1SLT
12a2PUSH20x1189
12a5JUMPI
12a6PUSH10x20
12a8PUSH10x05
12aaSLOAD
12abPUSH10x40
12adMLOAD
12aeSWAP1
12afDUP2
12b0MSTORE
12b1RETURN
12b2JUMPDEST
12b3CALLVALUE
12b4PUSH20x1189
12b7JUMPI
12b8PUSH0
12b9CALLDATASIZE
12baPUSH10x03
12bcNOT
12bdADD
12beSLT
12bfPUSH20x1189
12c2JUMPI
12c3PUSH10x20
12c5PUSH10x02
12c7SLOAD
12c8PUSH10x40
12caMLOAD
12cbSWAP1
12ccDUP2
12cdMSTORE
12ceRETURN
12cfJUMPDEST
12d0CALLVALUE
12d1PUSH20x1189
12d4JUMPI
12d5PUSH0
12d6CALLDATASIZE
12d7PUSH10x03
12d9NOT
12daADD
12dbSLT
12dcPUSH20x1189
12dfJUMPI
12e0PUSH0
12e1SLOAD
12e2PUSH10x01
12e4PUSH10x01
12e6PUSH10xa0
12e8SHL
12e9SUB
12eaDUP2
12ebAND
12ecCALLER
12edSUB
12eePUSH20x132a
12f1JUMPI
12f2PUSH120xffffffffffffffffffffffff
12ffPUSH10xa0
1301SHL
1302AND
1303PUSH0
1304SSTORE
1305PUSH320x1b2d71eb44f882534bf4e86f940c56ccc869ffb927e2bab86561de93950c2216
1326PUSH0
1327DUP1
1328LOG1
1329STOP
132aJUMPDEST
132bPUSH40x0bd42121
1330PUSH10xe1
1332SHL
1333PUSH0
1334MSTORE
1335CALLER
1336PUSH10x04
1338MSTORE
1339PUSH10x24
133bPUSH0
133cREVERT
133dJUMPDEST
133eCALLVALUE
133fPUSH20x1189
1342JUMPI
1343PUSH10xa0
1345CALLDATASIZE
1346PUSH10x03
1348NOT
1349ADD
134aSLT
134bPUSH20x1189
134eJUMPI
134fPUSH10x04
1351CALLDATALOAD
1352PUSH10x24
1354CALLDATALOAD
1355PUSH10x44
1357CALLDATALOAD
1358PUSH20x135f
135bPUSH20x15c4
135eJUMP
135fJUMPDEST
1360PUSH10x84
1362CALLDATALOAD
1363PUSH10x01
1365PUSH10x01
1367PUSH10x40
1369SHL
136aSUB
136bDUP2
136cGT
136dPUSH20x1189
1370JUMPI
1371PUSH20x137e
1374SWAP1
1375CALLDATASIZE
1376SWAP1
1377PUSH10x04
1379ADD
137aPUSH20x15f0
137dJUMP
137eJUMPDEST
137fSWAP1
1380SWAP2
1381PUSH10x02
1383SLOAD
1384DUP1
1385ISZERO
1386PUSH20x157f
1389JUMPI
138aDUP6
138bISZERO
138cPUSH20x1570
138fJUMPI
1390DUP7
1391PUSH0
1392MSTORE
1393PUSH10x07
1395PUSH10x20
1397MSTORE
1398PUSH10x40
139aPUSH0
139bKECCAK256
139cDUP6
139dPUSH0
139eMSTORE
139fPUSH10x20
13a1MSTORE
13a2PUSH10xff
13a4PUSH10x40
13a6PUSH0
13a7KECCAK256
13a8SLOAD
13a9AND
13aaPUSH20x1559
13adJUMPI
13aeDUP7
13afPUSH0
13b0MSTORE
13b1PUSH10x04
13b3PUSH10x20
13b5MSTORE
13b6PUSH10x40
13b8PUSH0
13b9KECCAK256
13baSWAP4
13bbDUP5
13bcSLOAD
13bdDUP1
13beDUP9
13bfGT
13c0PUSH20x153f
13c3JUMPI
13c4POP
13c5PUSH20x14b0
13c8DUP9
13c9PUSH20x14aa
13ccPUSH320x3ef380598f06b01333350e61cefa066c10a2088a64ad65aadd22eda04b972b89
13edSWAP10
13eeSWAP8
13efSWAP6
13f0PUSH10x80
13f2SWAP10
13f3SWAP8
13f4SWAP6
13f5PUSH10x01
13f7PUSH10x01
13f9PUSH10x40
13fbSHL
13fcSUB
13fdSWAP6
13fePUSH10x06
1400SLOAD
1401SWAP8
1402DUP8
1403DUP10
1404AND
1405SWAP6
1406PUSH10x40
1408MLOAD
1409PUSH10x20
140bDUP2
140cADD
140dSWAP2
140eDUP9
140fDUP4
1410MSTORE
1411PUSH10x40
1413DUP3
1414ADD
1415MSTORE
1416DUP14
1417PUSH10x60
1419DUP3
141aADD
141bMSTORE
141cDUP13
141dDUP16
141eDUP3
141fADD
1420MSTORE
1421DUP15
1422DUP2
1423MSTORE
1424PUSH20x142e
1427PUSH10xa0
1429DUP3
142aPUSH20x1620
142dJUMP
142eJUMPDEST
142fMLOAD
1430SWAP1
1431KECCAK256
1432PUSH10x40
1434MLOAD
1435PUSH10x20
1437DUP2
1438ADD
1439SWAP2
143aPUSH320xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675
145bDUP4
145cMSTORE
145dCHAINID
145ePUSH10x40
1460DUP4
1461ADD
1462MSTORE
1463ADDRESS
1464PUSH10x60
1466DUP4
1467ADD
1468MSTORE
1469DUP16
146aPUSH320x07e27cdd90594a3caa105e64724b3ff44d247017420d1a3df2cc49fa10cdf0f5
148bSWAP1
148cDUP4
148dADD
148eMSTORE
148fDUP11
1490DUP8
1491AND
1492PUSH10xa0
1494DUP4
1495ADD
1496MSTORE
1497PUSH10xc0
1499DUP3
149aADD
149bMSTORE
149cPUSH10xc0
149eDUP2
149fMSTORE
14a0PUSH20x02c1
14a3PUSH10xe0
14a5DUP3
14a6PUSH20x1620
14a9JUMP
14aaJUMPDEST
14abPOP
14acPUSH20x1655
14afJUMP
14b0JUMPDEST
14b1AND
14b2SWAP1
14b3PUSH10x01
14b5PUSH10x01
14b7PUSH10x40
14b9SHL
14baSUB
14bbNOT
14bcAND
14bdOR
14bePUSH10x06
14c0SSTORE
14c1DUP6
14c2PUSH0
14c3MSTORE
14c4PUSH10x07
14c6PUSH10x20
14c8MSTORE
14c9PUSH10x40
14cbPUSH0
14ccKECCAK256
14cdDUP3
14cePUSH0
14cfMSTORE
14d0PUSH10x20
14d2MSTORE
14d3PUSH10x40
14d5PUSH0
14d6KECCAK256
14d7PUSH10x01
14d9PUSH10xff
14dbNOT
14dcDUP3
14ddSLOAD
14deAND
14dfOR
14e0SWAP1
14e1SSTORE
14e2DUP3
14e3DUP2
14e4SLOAD
14e5SUB
14e6DUP2
14e7SSTORE
14e8DUP3
14e9PUSH10x05
14ebSLOAD
14ecSUB
14edPUSH10x05
14efSSTORE
14f0DUP3
14f1PUSH10x03
14f3SLOAD
14f4ADD
14f5PUSH10x03
14f7SSTORE
14f8PUSH10x01
14faPUSH10x01
14fcPUSH10x40
14feSHL
14ffSUB
1500PUSH10x01
1502DUP3
1503ADD
1504SWAP2
1505DUP2
1506PUSH20x1511
1509DUP2
150aDUP6
150bSLOAD
150cAND
150dPUSH20x1655
1510JUMP
1511JUMPDEST
1512AND
1513DUP3
1514NOT
1515DUP5
1516SLOAD
1517AND
1518OR
1519DUP4
151aSSTORE
151bPUSH20x1522
151ePUSH20x19fa
1521JUMP
1522JUMPDEST
1523SLOAD
1524SWAP2
1525SLOAD
1526AND
1527SWAP1
1528PUSH10x40
152aMLOAD
152bSWAP4
152cDUP5
152dMSTORE
152ePUSH10x20
1530DUP5
1531ADD
1532MSTORE
1533PUSH10x40
1535DUP4
1536ADD
1537MSTORE
1538PUSH10x60
153aDUP3
153bADD
153cMSTORE
153dLOG2
153eSTOP
153fJUMPDEST
1540DUP8
1541DUP10
1542PUSH40x7c06acb7
1547PUSH10xe1
1549SHL
154aPUSH0
154bMSTORE
154cPUSH10x04
154eMSTORE
154fPUSH10x24
1551MSTORE
1552PUSH10x44
1554MSTORE
1555PUSH10x64
1557PUSH0
1558REVERT
1559JUMPDEST
155aDUP5
155bDUP8
155cPUSH40x0dd4fdfd
1561PUSH10xe2
1563SHL
1564PUSH0
1565MSTORE
1566PUSH10x04
1568MSTORE
1569PUSH10x24
156bMSTORE
156cPUSH10x44
156ePUSH0
156fREVERT
1570JUMPDEST
1571PUSH40x1f2a2005
1576PUSH10xe0
1578SHL
1579PUSH0
157aMSTORE
157bPUSH10x04
157dPUSH0
157eREVERT
157fJUMPDEST
1580PUSH40xd311bc39
1585PUSH10xe0
1587SHL
1588PUSH0
1589MSTORE
158aPUSH10x04
158cPUSH0
158dREVERT
158eJUMPDEST
158fCALLVALUE
1590PUSH20x1189
1593JUMPI
1594PUSH10x40
1596CALLDATASIZE
1597PUSH10x03
1599NOT
159aADD
159bSLT
159cPUSH20x1189
159fJUMPI
15a0PUSH10x20
15a2SWAP1
15a3PUSH10x04
15a5CALLDATALOAD
15a6PUSH0
15a7MSTORE
15a8PUSH10x07
15aaDUP3
15abMSTORE
15acPUSH10x40
15aePUSH0
15afKECCAK256
15b0PUSH10x24
15b2CALLDATALOAD
15b3PUSH0
15b4MSTORE
15b5DUP3
15b6MSTORE
15b7PUSH10xff
15b9PUSH10x40
15bbPUSH0
15bcKECCAK256
15bdSLOAD
15beAND
15bfISZERO
15c0ISZERO
15c1DUP2
15c2MSTORE
15c3RETURN
15c4JUMPDEST
15c5PUSH10x64
15c7CALLDATALOAD
15c8SWAP1
15c9PUSH10x01
15cbPUSH10x01
15cdPUSH10x40
15cfSHL
15d0SUB
15d1DUP3
15d2AND
15d3DUP3
15d4SUB
15d5PUSH20x1189
15d8JUMPI
15d9JUMP
15daJUMPDEST
15dbPUSH10x44
15ddCALLDATALOAD
15deSWAP1
15dfPUSH10x01
15e1PUSH10x01
15e3PUSH10x40
15e5SHL
15e6SUB
15e7DUP3
15e8AND
15e9DUP3
15eaSUB
15ebPUSH20x1189
15eeJUMPI
15efJUMP
15f0JUMPDEST
15f1SWAP2
15f2DUP2
15f3PUSH10x1f
15f5DUP5
15f6ADD
15f7SLT
15f8ISZERO
15f9PUSH20x1189
15fcJUMPI
15fdDUP3
15feCALLDATALOAD
15ffSWAP2
1600PUSH10x01
1602PUSH10x01
1604PUSH10x40
1606SHL
1607SUB
1608DUP4
1609GT
160aPUSH20x1189
160dJUMPI
160ePUSH10x20
1610DUP1
1611DUP6
1612ADD
1613SWAP5
1614DUP5
1615PUSH10x05
1617SHL
1618ADD
1619ADD
161aGT
161bPUSH20x1189
161eJUMPI
161fJUMP
1620JUMPDEST
1621SWAP1
1622PUSH10x1f
1624DUP1
1625NOT
1626SWAP2
1627ADD
1628AND
1629DUP2
162aADD
162bSWAP1
162cDUP2
162dLT
162ePUSH10x01
1630PUSH10x01
1632PUSH10x40
1634SHL
1635SUB
1636DUP3
1637GT
1638OR
1639PUSH20x1641
163cJUMPI
163dPUSH10x40
163fMSTORE
1640JUMP
1641JUMPDEST
1642PUSH40x4e487b71
1647PUSH10xe0
1649SHL
164aPUSH0
164bMSTORE
164cPUSH10x41
164ePUSH10x04
1650MSTORE
1651PUSH10x24
1653PUSH0
1654REVERT
1655JUMPDEST
1656PUSH10x01
1658PUSH10x01
165aPUSH10x40
165cSHL
165dSUB
165ePUSH10x01
1660SWAP2
1661AND
1662ADD
1663SWAP1
1664PUSH10x01
1666PUSH10x01
1668PUSH10x40
166aSHL
166bSUB
166cDUP3
166dGT
166ePUSH20x1673
1671JUMPI
1672JUMP
1673JUMPDEST
1674PUSH40x4e487b71
1679PUSH10xe0
167bSHL
167cPUSH0
167dMSTORE
167ePUSH10x11
1680PUSH10x04
1682MSTORE
1683PUSH10x24
1685PUSH0
1686REVERT
1687JUMPDEST
1688SWAP2
1689SWAP1
168aDUP3
168bADD
168cDUP1
168dSWAP3
168eGT
168fPUSH20x1673
1692JUMPI
1693JUMP
1694JUMPDEST
1695SWAP1
1696CALLDATALOAD
1697PUSH10x1e
1699NOT
169aDUP3
169bCALLDATASIZE
169cSUB
169dADD
169eDUP2
169fSLT
16a0ISZERO
16a1PUSH20x1189
16a4JUMPI
16a5ADD
16a6PUSH10x20
16a8DUP2
16a9CALLDATALOAD
16aaSWAP2
16abADD
16acSWAP2
16adPUSH10x01
16afPUSH10x01
16b1PUSH10x40
16b3SHL
16b4SUB
16b5DUP3
16b6GT
16b7PUSH20x1189
16baJUMPI
16bbDUP2
16bcCALLDATASIZE
16bdSUB
16beDUP4
16bfSGT
16c0PUSH20x1189
16c3JUMPI
16c4JUMP
16c5JUMPDEST
16c6SWAP1
16c7DUP1
16c8PUSH10x20
16caSWAP4
16cbSWAP3
16ccDUP2
16cdDUP5
16ceMSTORE
16cfDUP5
16d0DUP5
16d1ADD
16d2CALLDATACOPY
16d3PUSH0
16d4DUP3
16d5DUP3
16d6ADD
16d7DUP5
16d8ADD
16d9MSTORE
16daPUSH10x1f
16dcADD
16ddPUSH10x1f
16dfNOT
16e0AND
16e1ADD
16e2ADD
16e3SWAP1
16e4JUMP
16e5JUMPDEST
16e6SWAP1
16e7DUP2
16e8PUSH10x20
16eaSWAP2
16ebSUB
16ecSLT
16edPUSH20x1189
16f0JUMPI
16f1MLOAD
16f2DUP1
16f3ISZERO
16f4ISZERO
16f5DUP2
16f6SUB
16f7PUSH20x1189
16faJUMPI
16fbSWAP1
16fcJUMP
16fdJUMPDEST
16feDUP2
16ffDUP4
1700MSTORE
1701SWAP1
1702SWAP2
1703PUSH10x01
1705PUSH10x01
1707PUSH10xfb
1709SHL
170aSUB
170bDUP4
170cGT
170dPUSH20x1189
1710JUMPI
1711PUSH10x20
1713SWAP3
1714PUSH10x05
1716SHL
1717DUP1
1718SWAP3
1719DUP5
171aDUP4
171bADD
171cCALLDATACOPY
171dADD
171eADD
171fSWAP1
1720JUMP
1721JUMPDEST
1722SWAP2
1723SWAP1
1724DUP2
1725LT
1726ISZERO
1727PUSH20x1731
172aJUMPI
172bPUSH10x05
172dSHL
172eADD
172fSWAP1
1730JUMP
1731JUMPDEST
1732PUSH40x4e487b71
1737PUSH10xe0
1739SHL
173aPUSH0
173bMSTORE
173cPUSH10x32
173ePUSH10x04
1740MSTORE
1741PUSH10x24
1743PUSH0
1744REVERT
1745JUMPDEST
1746CALLDATALOAD
1747PUSH10x01
1749PUSH10x01
174bPUSH10x40
174dSHL
174eSUB
174fDUP2
1750AND
1751DUP2
1752SUB
1753PUSH20x1189
1756JUMPI
1757SWAP1
1758JUMP
1759JUMPDEST
175aSWAP2
175bSWAP1
175cDUP3
175dSUB
175eSWAP2
175fDUP3
1760GT
1761PUSH20x1673
1764JUMPI
1765JUMP
1766JUMPDEST
1767CALLDATALOAD
1768PUSH10x01
176aPUSH10x01
176cPUSH10xa0
176eSHL
176fSUB
1770DUP2
1771AND
1772DUP2
1773SUB
1774PUSH20x1189
1777JUMPI
1778SWAP1
1779JUMP
177aJUMPDEST
177bCALLDATALOAD
177cPUSH10xff
177eDUP2
177fAND
1780DUP2
1781SUB
1782PUSH20x1189
1785JUMPI
1786SWAP1
1787JUMP
1788JUMPDEST
1789SWAP5
178aSWAP4
178bSWAP2
178cSWAP6
178dSWAP7
178ePUSH0
178fSWAP8
1790DUP6
1791ISZERO
1792PUSH20x19eb
1795JUMPI
1796PUSH10x01
1798PUSH10x01
179aPUSH10x40
179cSHL
179dSUB
179eAND
179fNUMBER
17a0DUP2
17a1GT
17a2PUSH20x19d5
17a5JUMPI
17a6PUSH20x0258
17a9PUSH20x17b2
17acDUP3
17adNUMBER
17aePUSH20x1759
17b1JUMP
17b2JUMPDEST
17b3GT
17b4PUSH20x19bf
17b7JUMPI
17b8POP
17b9PUSH10x40
17bbMLOAD
17bcSWAP4
17bdPUSH10x20
17bfDUP6
17c0ADD
17c1MSTORE
17c2PUSH10x20
17c4DUP5
17c5MSTORE
17c6PUSH20x17d0
17c9PUSH10x40
17cbDUP6
17ccPUSH20x1620
17cfJUMP
17d0JUMPDEST
17d1PUSH0
17d2SWAP5
17d3PUSH0
17d4SWAP9
17d5JUMPDEST
17d6DUP9
17d7DUP11
17d8LT
17d9ISZERO
17daPUSH20x1995
17ddJUMPI
17deDUP10
17dfPUSH10x05
17e1SHL
17e2DUP5
17e3ADD
17e4CALLDATALOAD
17e5PUSH10x7e
17e7NOT
17e8DUP6
17e9CALLDATASIZE
17eaSUB
17ebADD
17ecDUP2
17edSLT
17eeISZERO
17efPUSH20x1189
17f2JUMPI
17f3DUP5
17f4ADD
17f5SWAP7
17f6PUSH20x17fe
17f9DUP9
17faPUSH20x1766
17fdJUMP
17feJUMPDEST
17ffPUSH10x01
1801PUSH10x01
1803PUSH10xa0
1805SHL
1806SUB
1807SWAP2
1808DUP3
1809AND
180aSWAP2
180bAND
180cDUP2
180dLT
180eISZERO
180fPUSH20x1969
1812JUMPI
1813POP
1814PUSH20x181c
1817DUP8
1818PUSH20x1766
181bJUMP
181cJUMPDEST
181dSWAP7
181ePUSH20x185d
1821PUSH10x20
1823DUP8
1824PUSH20x182c
1827DUP5
1828PUSH20x1766
182bJUMP
182cJUMPDEST
182dPUSH10x40
182fMLOAD
1830PUSH40x2e4bfa51
1835PUSH10xe1
1837SHL
1838DUP2
1839MSTORE
183aPUSH10x01
183cPUSH10x01
183ePUSH10xa0
1840SHL
1841SUB
1842SWAP1
1843SWAP2
1844AND
1845PUSH10x04
1847DUP3
1848ADD
1849MSTORE
184aPUSH10x24
184cDUP2
184dADD
184eSWAP2
184fSWAP1
1850SWAP2
1851MSTORE
1852SWAP2
1853DUP3
1854SWAP1
1855DUP2
1856SWAP1
1857PUSH10x44
1859DUP3
185aADD
185bSWAP1
185cJUMP
185dJUMPDEST
185eSUB
185fDUP2
1860PUSH10x01
1862PUSH10x01
1864PUSH10xa0
1866SHL
1867SUB
1868DUP15
1869AND
186aGAS
186bSTATICCALL
186cSWAP1
186dDUP2
186eISZERO
186fPUSH20x1104
1872JUMPI
1873PUSH0
1874SWAP2
1875PUSH20x194b
1878JUMPI
1879JUMPDEST
187aPOP
187bISZERO
187cPUSH20x1924
187fJUMPI
1880PUSH10x20
1882DUP2
1883ADD
1884SWAP1
1885PUSH10x04
1887PUSH10xff
1889PUSH20x1891
188cDUP5
188dPUSH20x177a
1890JUMP
1891JUMPDEST
1892AND
1893SUB
1894PUSH20x18f1
1897JUMPI
1898PUSH20x18a2
189bDUP9
189cDUP3
189dDUP13
189ePUSH20x1b1a
18a1JUMP
18a2JUMPDEST
18a3ISZERO
18a4PUSH20x18bd
18a7JUMPI
18a8POP
18a9POP
18aaPUSH0
18abNOT
18acDUP2
18adEQ
18aePUSH20x1673
18b1JUMPI
18b2PUSH10x01
18b4SWAP10
18b5DUP11
18b6ADD
18b7SWAP10
18b8ADD
18b9PUSH20x17d5
18bcJUMP
18bdJUMPDEST
18beSWAP1
18bfPUSH20x18d2
18c2PUSH20x18cc
18c5PUSH10xff
18c7SWAP4
18c8PUSH20x1766
18cbJUMP
18ccJUMPDEST
18cdSWAP2
18cePUSH20x177a
18d1JUMP
18d2JUMPDEST
18d3SWAP1
18d4PUSH40xbbf82ba3
18d9PUSH10xe0
18dbSHL
18dcPUSH0
18ddMSTORE
18dePUSH10x01
18e0DUP1
18e1PUSH10xa0
18e3SHL
18e4SUB
18e5AND
18e6PUSH10x04
18e8MSTORE
18e9AND
18eaPUSH10x24
18ecMSTORE
18edPUSH10x44
18efPUSH0
18f0REVERT
18f1JUMPDEST
18f2SWAP1
18f3PUSH20x1900
18f6PUSH20x18cc
18f9PUSH10xff
18fbSWAP4
18fcPUSH20x1766
18ffJUMP
1900JUMPDEST
1901SWAP1
1902PUSH40x587548c3
1907PUSH10xe1
1909SHL
190aPUSH0
190bMSTORE
190cPUSH10x01
190eDUP1
190fPUSH10xa0
1911SHL
1912SUB
1913AND
1914PUSH10x04
1916MSTORE
1917AND
1918PUSH10x24
191aMSTORE
191bPUSH10x04
191dPUSH10x44
191fMSTORE
1920PUSH10x64
1922PUSH0
1923REVERT
1924JUMPDEST
1925PUSH20x192e
1928DUP7
1929SWAP2
192aPUSH20x1766
192dJUMP
192eJUMPDEST
192fPUSH40xae8bb039
1934PUSH10xe0
1936SHL
1937PUSH0
1938MSTORE
1939PUSH10x01
193bDUP1
193cPUSH10xa0
193eSHL
193fSUB
1940AND
1941PUSH10x04
1943MSTORE
1944PUSH10x24
1946MSTORE
1947PUSH10x44
1949PUSH0
194aREVERT
194bJUMPDEST
194cPUSH20x1963
194fSWAP2
1950POP
1951PUSH10x20
1953RETURNDATASIZE
1954DUP2
1955GT
1956PUSH20x0ea4
1959JUMPI
195aPUSH20x0e96
195dDUP2
195eDUP4
195fPUSH20x1620
1962JUMP
1963JUMPDEST
1964PUSH0
1965PUSH20x1879
1968JUMP
1969JUMPDEST
196aPUSH20x1972
196dDUP9
196ePUSH20x1766
1971JUMP
1972JUMPDEST
1973PUSH40x11641feb
1978PUSH10xe2
197aSHL
197bPUSH0
197cSWAP1
197dDUP2
197eMSTORE
197fPUSH10x04
1981SWAP3
1982SWAP1
1983SWAP3
1984MSTORE
1985PUSH10x01
1987PUSH10x01
1989PUSH10xa0
198bSHL
198cSUB
198dAND
198ePUSH10x24
1990MSTORE
1991PUSH10x44
1993SWAP1
1994REVERT
1995JUMPDEST
1996SWAP9
1997POP
1998SWAP6
1999POP
199aSWAP6
199bPOP
199cPOP
199dPOP
199ePOP
199fPOP
19a0DUP1
19a1DUP4
19a2LT
19a3PUSH20x19a9
19a6JUMPI
19a7POP
19a8JUMP
19a9JUMPDEST
19aaDUP3
19abPUSH40x05bc2167
19b0PUSH10xe5
19b2SHL
19b3PUSH0