Final Testnetexplorer K_J · Final Testnet · 48359
en

Contract

0x85ebf3c638e94084ba2876ea24c576cfbe51ac38

Address
0x85ebf3c638e94084ba2876ea24c576cfbe51ac38
Kind
verified contract FinalIntentLog
Balance
0 vETH
Nonce
1
Code
12,995 bytes codehash 0xd37a808c6f547a51f20dc5c533cc0e36523a9af1aca69a4213c02a63937dfd2b

account tree

Tree
1 · accounts
Present
no leaf
Key
0xc040b8f69a69d3514d4f7873954c4ebffa6c7a6e0193ea2366717020d102b214
Live root
0x18f30182962d8af79e7ab628ce200be69d28f54119890d737c7e736de62e703c
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
FinalIntentLog exact match · immutables masked
Compiler
v0.8.33+commit.64118f21
Optimizer
enabled · 200 runs
EVM version
prague
Verified
2026-09-06T09:34:54.217Z
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/FinalIntentLog.sol

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

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

/**
 * @title FinalIntentLog
 * @notice Every intent is posted here before it may be anchored. Ordering is
 *         block order.
 *
 * @dev ## Why this is a log and not a tree
 *
 * Final Chain carries six trees, and they publish in ROUNDS — every root
 * together, one version bump each. That structure is for state that changes
 * slowly and must be proven elsewhere. An intent is neither: it is high
 * cadence, it lives 180 seconds, and putting it in a round-published tree would
 * bump a version on every transaction and age every outstanding proof — the
 * same objection that kept a liveness timestamp out of the tree-1 account leaf.
 *
 * It is not an MMR either. `FinalBundleLog`'s payload already IS the bundle's
 * intent-Merkle root, so a second append-only log over the same leaves would
 * prove, one level earlier, a fact the bundle anchor already proves.
 *
 * What an intent actually needs is ordering, public availability, and a
 * presence check the anchoring quorum can run objectively. Block order gives
 * the first, calldata gives the second, and a mapping gives the third. That is
 * the whole contract.
 *
 * ## What it buys
 *
 * `FinalBundleLog.append` consumes from here, so a bundle cannot be anchored
 * unless every intent in it was posted first — and the execution chains'
 * authorization is that anchor. Final-Chain-first admission stops being a
 * quorum policy and becomes bytecode. Replay protection falls out of the same
 * mapping: a leaf is consumed exactly once, ever.
 *
 * For the user it closes substitution. The intent they composed is ordered
 * publicly before anything executes, so a relayer or a frontend cannot swap it
 * for another — the anchor will only carry what this log holds.
 *
 * ## The body is encrypted and this contract cannot read it
 *
 * `header` is routing metadata: a chain reference, a deadline, KEM ciphertexts,
 * a commitment to the body, and the intent leaf. Account, destination, fee and
 * calldata are all inside `ciphertext`. That is what closes content-based
 * front-running — an operator watching this log sees a chain id and a deadline
 * and cannot build a profitable order from either.
 *
 * There is deliberately no decryption path on chain and no AEAD precompile. The
 * chain checks a hash and nothing else.
 *
 * ## The envelope is STORED, not left in the event log
 *
 * A two-KEM envelope is ~16 KB and keeping it in state is not free. It is kept
 * anyway, for the reason `FinalBundleLog` keeps its payloads: this chain caps a
 * `getLogs` range at 100,000 blocks and mints one every 100 ms, so the log is
 * reachable for **2.8 hours**. A forced-path intent lives **48**.
 *
 * An event-only envelope would therefore be unreadable long before it expired —
 * precisely for the guardians who are meant to review a forced intent, and
 * precisely in the window the forced path exists to cover. That is not a
 * degraded read, it is the escape hatch not working.
 *
 * So the event is for DISCOVERY and carries no bytes; state is for retrieval,
 * over one `eth_call` and no range. Emitting the envelope as well would be
 * ~128k gas of duplication for data the same transaction already stored.
 *
 * Measured in `FinalIntentLog.t.sol`: **11,517,049 gas** for a 16,426-byte
 * envelope, which at a 60M block gas limit is **5 intents per block** — 50 a
 * second at 100 ms. That is the throughput ceiling on this path, and it is
 * dominated by HQC-5's 14,421-byte ciphertext.
 *
 * Two levers if it ever binds. SSTORE2-style data contracts are ~3x cheaper per
 * byte, and would need chunking because a two-recipient envelope exceeds
 * EIP-170's 24,576. Pruning consumed envelopes is the other, and it trades the
 * record for the space — a decision, not a cleanup.
 *
 * **The bond becomes load-bearing when the RPC opens, not before.** Posting is
 * permissionless in this contract and the base fee is 1 wei, so the only thing
 * standing between an attacker and 60M gas a block of permanent state is that
 * Final Chain's RPC is fronted by a default-deny Cloud Armor allowlist and
 * reaches operators only. That is a perimeter, not a mechanism, and it is the
 * one that has to be removed for users to post their own intents. Nothing here
 * rate-limits; the bond is what is supposed to, and this is why it cannot ship
 * after the RPC does.
 *
 * ## Approve and cancel, without naming an account
 *
 * An approval-gated intent executes only after a second on-chain act, made
 * after the user decrypts the posted envelope and reads what the CHAIN holds —
 * the read-back that closes execution of what the user has not seen. Who may
 * approve is a **per-intent, wallet-generated ML-DSA-87 key**, committed in the
 * plaintext header: the commitment names a key, never an account, which is what
 * answers "a per-intent veto has to say who may veto, and the account is
 * encrypted". A substituted commitment strands the intent — the execution
 * signature bound into the leaf still authorizes every field — and cancel is
 * the same key, any time before consumption.
 *
 * `approve` also DELIVERS the executor section: the content key and the
 * account's KEM public keys, sealed to the executor set. Before approval the
 * executor holds ciphertext it cannot open; that is cryptography, not policy.
 * An AUTO_APPROVE posting (header flag, bit 0) skips the gate for automation
 * and scheduling and carries its executor section at posting — flipping the
 * flag skips the second look and can never change what executes.
 *
 * The account-wide veto stays `FinalAccountLedger`'s freeze; auto-approved
 * intents have no per-intent cancel because they commit to no approval key.
 *
 * ## Tree 7 is the search structure; the MMR is the record
 *
 * Every transition — posted, approved, cancelled, consumed — is mirrored into
 * `FinalStateTrees.TREE_INTENTS` through `treeWriter[7]`, keyed by a RING over
 * the posting sequence (`seq mod 2^20`), so slots recycle and the tree is an
 * index with a ~1M-posting retention window rather than a permanent record.
 * Backends enumerate it instead of scanning logs; proofs against it are proofs
 * about the current window.
 */
contract FinalIntentLog {
    /// @notice Where roles are resolved. Immutable.
    ///
    /// @dev The only thing this log needs a registry for is authorizing
    ///      `setConsumer`, and that could have been a deployer check. It is the
    ///      registry instead so the wiring falls under the SAME bootstrap window
    ///      as `FinalStateTrees.setTreeWriter` and `FinalBundleLog.configure` —
    ///      one rule for who may wire Final Chain together, rather than three.
    FinalIdentityRegistry public immutable registry;

    /// @notice Where every status transition is mirrored (tree 7). Immutable:
    /// a movable index would be an index whose history can be swapped.
    FinalStateTrees public immutable trees;

    /// @dev Header layout, fixed offsets. Must match `encodeHeader` in
    ///      `FinalBackend/src/intents/intentEnvelope.js` — the cross-repo parity
    ///      suite pins it. Length-prefixed and canonical precisely so these
    ///      offsets exist: a JSON header would have no stable position to read.
    ///
    ///        0         version           uint8   = 2 | 3 (v3 salts recipientsHash; offsets identical)
    ///        1         flags             uint8   bit0 AUTO_APPROVE; rest zero
    ///        2..33     targetChainRef    bytes32
    ///        34..41    executeNotBefore  uint64  ms; 0 = immediate
    ///        42..49    deadline          uint64  ms
    ///        50..81    bodyCommitment    bytes32
    ///        82..113   intentLeaf        bytes32
    ///        114..145  approvalKeyCommit bytes32 zero iff AUTO_APPROVE
    ///        146..147  kemSet            uint16
    ///        148..149  kemKeyVersion     uint16
    ///        150..181  recipientsHash    bytes32
    ///        182..     recipients, executorSection, bond
    uint256 private constant OFF_FLAGS = 1;
    uint256 private constant OFF_TARGET_CHAIN_REF = 2;
    uint256 private constant OFF_EXECUTE_NOT_BEFORE = 34;
    uint256 private constant OFF_DEADLINE = 42;
    uint256 private constant OFF_BODY_COMMITMENT = 50;
    uint256 private constant OFF_INTENT_LEAF = 82;
    uint256 private constant OFF_APPROVAL_KEY_COMMIT = 114;
    /// @dev The whole fixed prefix, including `recipientsHash` — which this
    ///      contract never reads. Requiring it anyway is the cheap half of
    ///      "well-formed": a header truncated after the leaf would parse
    ///      perfectly here and then fail to decrypt for its recipient. The
    ///      contract reads nothing past offset 145.
    uint256 private constant HEADER_MIN_BYTES = 182;

    /// @notice The oldest envelope wire version this contract reads.
    uint8 public constant HEADER_VERSION = 2;

    /// @notice The newest. v3 (B6) keeps every offset and salts `recipientsHash`
    ///         — a field this contract never reads — with a holder-derived value,
    ///         so a wallet's postings stop sharing a fingerprint in the clear.
    ///         Both versions parse identically here; the header's own AAD is what
    ///         tells them apart for the recipients.
    uint8 public constant HEADER_VERSION_MAX = 3;

    /// @notice Header flag bit 0: skip the approval gate.
    /// @dev Poster-controlled routing, honestly scoped: flipping it skips the
    /// user's second look and can never change what executes — the user's own
    /// signature over the intent fields, bound into the leaf, remains the only
    /// execution authorization.
    uint8 public constant FLAG_AUTO_APPROVE = 0x01;

    // ---------------------------------------------------- approval domains

    /// @dev Distinct per action, so an approval can never be replayed as a
    /// cancellation or vice versa. Both digests bind this chain and this log.
    bytes32 public constant DOMAIN_INTENT_APPROVE = keccak256("FINAL_INTENT_APPROVE_v01");
    bytes32 public constant DOMAIN_INTENT_CANCEL = keccak256("FINAL_INTENT_CANCEL_v01");

    // ------------------------------------------------------ tree-7 mirror

    /// @dev The ring key's domain. Named in `FinalStateTrees`' key.* family
    /// because that is the space it lives in; computed HERE because the log is
    /// the writer and the backend mirrors this function, not the tree.
    bytes32 public constant DOMAIN_INTENT_KEY = keccak256("FinalStateTrees.key.intent.v01");
    /// @dev The status leaf's domain.
    bytes32 public constant DOMAIN_INTENT_STATUS_LEAF = keccak256("FINAL_INTENT_STATUS_LEAF_v01");
    /// @notice Ring size: keys recycle at `CAPACITY`, so the tree can never
    /// fill however long the chain runs. Pinned equal to
    /// `FinalStateTrees.CAPACITY` by test.
    uint64 public constant INTENT_SLOT_RING = uint64(1) << 20;

    /// @notice The tree this log writes. Restated from `FinalStateTrees`
    /// because a contract-type constant is not reachable here; pinned equal to
    /// `trees.TREE_INTENTS()` by test.
    uint8 public constant TREE_INTENTS_ID = 7;
    /// @notice The branch the intent ring lives in — `FinalStateTrees.BRANCH_MAIN`,
    ///         pinned by test. Branch 0 of tree 7 is the log's configuration.
    uint8 public constant BRANCH_MAIN_ID = 1;

    /// @notice Tree-7 status values.
    uint8 public constant STATUS_POSTED = 1;
    uint8 public constant STATUS_APPROVED = 2;
    uint8 public constant STATUS_CONSUMED = 3;
    uint8 public constant STATUS_CANCELLED = 4;

    /// @dev Registrar-quorum actions, verified by the registry with this log as
    /// the verifying contract.
    bytes32 public constant ACTION_SET_CONSUMER = keccak256("FINAL_INTENT_LOG_SET_CONSUMER_v01");
    bytes32 public constant ACTION_SEED_SEQUENCE = keccak256("FINAL_INTENT_LOG_SEED_SEQUENCE_v01");
    bytes32 public constant ACTION_SET_BOND_POLICY = keccak256("FINAL_INTENT_LOG_SET_BOND_POLICY_v01");

    /**
     * @notice The longest an intent may stay consumable past its earliest
     *         execution moment.
     *
     * @dev A cap rather than an exact value because every lane shares this log,
     *      and an uncapped deadline would let a posting sit consumable forever —
     *      which is a replay window dressed as a long-lived intent. An immediate
     *      intent (`executeNotBefore == 0`) gets exactly this from `now`; a
     *      scheduled one gets it from its own start.
     */
    /// @dev MILLISECONDS, like every duration on this chain. Its predecessor
    ///      was once 48 hours read against a millisecond clock — 48 SECONDS —
    ///      so a header whose deadline a client computed from wall time was
    ///      refused before it could ever be posted.
    uint64 public constant EXECUTION_WINDOW = 48 hours * FinalChainTime.MS_PER_SECOND;

    /// @notice How far ahead `executeNotBefore` may sit. The scheduling
    /// horizon: chosen at signing, because the deadline is inside the signed
    /// intent and cannot be extended afterwards.
    uint64 public constant MAX_SCHEDULE_HORIZON = 30 days * FinalChainTime.MS_PER_SECOND;

    /// @notice How far ahead of `executeNotBefore` a scheduled posting may be
    ///         CONSUMED when its target chain has no lead of its own (W1, ruled
    ///         2026-09-03: 90 seconds, per target chain). Admission — the
    ///         co-signer round and the append that consumes the leaf — runs
    ///         before T, so the fleet can compose and broadcast the wrapper to
    ///         land in the first target-chain block at or after T rather than
    ///         minutes late. The holder's veto (`cancel`) closes at consume,
    ///         i.e. no earlier than T − lead: the lead is the last call for a
    ///         cancel. An immediate posting (`executeNotBefore == 0`) has no
    ///         lead. Per chain: a row in tree 7's CONFIGURATION branch
    ///         (`FinalStateTrees.setConfig`, key `configKey(CONFIG_SCHEDULE_LEAD_MS,
    ///         targetChainRef)`) overrides this default (`scheduleLeadMsOf`),
    ///         explicit zero included — the first tenant of the config branch.
    uint64 public constant DEFAULT_SCHEDULE_LEAD_MS = 90 * FinalChainTime.MS_PER_SECOND;

    /// @notice The config-row NAME of a target chain's schedule lead, in
    ///         milliseconds: `trees.configKey(CONFIG_SCHEDULE_LEAD_MS, targetChainRef)`
    ///         → one word holding the lead. Written under the configuration
    ///         authority (bootstrap admin, then the registrar quorum) — an
    ///         operational parameter, not a trust boundary — and PROVABLE like
    ///         every other row of the plane, which a table here was not.
    bytes32 public constant CONFIG_SCHEDULE_LEAD_MS = keccak256("FinalIntentLog.config.scheduleLeadMs.v01");

    struct Posted {
        /// @dev Zero means never posted. Non-zero and `consumedAt == 0` means open.
        uint64 postedAt;
        /// @dev From the header, in MILLISECONDS. Before this, `consume` refuses.
        uint64 executeNotBefore;
        /// @dev From the header, in MILLISECONDS. After this, `consume` refuses.
        uint64 deadline;
        /// @dev Block timestamp of the anchoring append. Non-zero means spent.
        uint64 consumedAt;
        /// @dev `keccak256(body)` — the leaf↔body binding. With the reveal
        ///      schema gone it is checked OFF-chain: the execution chain's
        ///      public calldata against this word.
        bytes32 bodyCommitment;
        /// @dev `keccak256` of the per-intent approval public key; zero iff
        ///      AUTO_APPROVE. Names a key, never an account.
        bytes32 approvalKeyCommit;
        /// @dev From the header; kept for the tree-7 status leaf.
        bytes32 targetChainRef;
        /// @dev When `approve` verified. Non-zero means approved.
        uint64 approvedAt;
        /// @dev When `cancel` verified. Non-zero means dead: `consume` refuses.
        uint64 cancelledAt;
        /// @dev Posting sequence — the tree-7 ring position (`seq mod 2^20`).
        uint64 seq;
        /// @dev Header flags, verbatim.
        uint8 flags;
    }

    /// @dev The bytes, kept apart from `Posted` on purpose. `consume` runs on
    ///      the anchoring path and reads only the packed record; it must never
    ///      pay to walk 16 KB it does not look at.
    struct Envelope {
        /// @dev Canonical header, exactly as posted.
        bytes header;
        /// @dev The sealed body. This contract cannot read it and never tries.
        bytes ciphertext;
        /// @dev The content key and the account's KEM public keys, sealed to
        ///      the executor set. Delivered by `approve` — so before approval
        ///      the executor holds ciphertext it cannot open — or present from
        ///      posting on an AUTO_APPROVE intent. Opaque here: this contract
        ///      stores it and never parses it.
        bytes executorSection;
    }

    /// @notice Keyed by the intent LEAF, not by an envelope id.
    ///
    /// @dev The leaf is what `FinalBundleLog` folds and what the execution
    ///      chain's gateway recomputes, so keying on it makes the presence check
    ///      exact. It also makes duplicate protection land on the right thing: a
    ///      second header carrying a leaf already posted is the same intent
    ///      offered twice, and is refused.
    mapping(bytes32 leaf => Posted) public postedOf;

    /// @notice The full envelope, retrievable for as long as the chain exists.
    mapping(bytes32 leaf => Envelope) private _envelopeOf;

    /// @notice Postings ever made. The next intent takes this as its `seq`.
    uint64 public postSeq;
    /// @notice The leaf posted at sequence `seq` — the enumeration the backend
    ///         walks (`postSeq` is the count) instead of a `getLogs` range.
    mapping(uint64 => bytes32) public leafAt;

    // ─────────────────────────── the per-vertex bond ───────────────────────────
    //
    // **Encrypting the account removes attribution, and attribution is what
    // rate-limiting runs on.** Posting is permissionless at a 1 wei base fee and
    // state spam does not age out the way calldata spam does, so the thing
    // holding it off today is the default-deny allowlist on this chain's RPC —
    // a perimeter, and precisely the one that has to come down for users to post
    // their own intents. A bond charges the spammer regardless of identity,
    // needs no new cryptography, and is the natural unit for operators paid per
    // unit of work.
    //
    // Refunded on valid execution, forfeited on a vertex that was never
    // anchored. Both are PULL: `consume` runs inside the anchoring append and
    // must not be able to fail, or be delayed, because of where a refund was
    // going.
    //
    // **Not in the header.** The header's `<authenticator>` field stays a
    // free-form reference for off-chain accounting and is deliberately not the
    // bond itself. Moving the bond into the header would be an encoding change
    // rippling through every reader and the cross-repo offset parity, and a
    // bond is not worth one when native value keyed by leaf says the same
    // thing.
    //
    // **Zero is the shipped state**, which is today's behaviour exactly. Arming
    // it is the step that must precede opening the RPC, not follow it.

    struct Bond {
        /// @dev Who paid, and who a refund goes back to. Not the intent's
        /// account — that is encrypted and this contract cannot know it — which
        /// is the whole reason a bond works here and a per-account quota does
        /// not.
        address payer;
        /// @dev Wei paid. Stored rather than re-read from `bondWei`, because the
        /// rate can move between posting and settlement and a refund of
        /// something other than what was paid is a fee nobody agreed to.
        /// `uint88` covers 309 million ether and packs the struct into one slot.
        uint88 amount;
        /// @dev Paid out or forfeited. Set before the transfer.
        bool settled;
    }

    /// @notice The bond posted with each intent, if any.
    mapping(bytes32 leaf => Bond) public bondOf;

    /// @notice What `post` requires. Zero disables the bond entirely.
    uint256 public bondWei;

    /// @notice Where a forfeited bond goes.
    /// @dev Zero means BURN — the value stays in this contract and nothing can
    /// move it. That is the safe default rather than an oversight: a forfeit
    /// destination is a revenue stream, and one set by accident is worse than
    /// one that does not exist. Arming a bond without naming a destination is
    /// refused, so this can never be reached by forgetting.
    address public bondForfeitTo;

    /// @notice The bond rate, or its destination, changed.
    event BondPolicySet(uint256 bondWei, address forfeitTo);
    /// @notice A bond was posted alongside an intent.
    event BondPosted(bytes32 indexed leaf, address indexed payer, uint256 amount);
    /// @notice A bond was returned after the intent was anchored.
    event BondRefunded(bytes32 indexed leaf, address indexed payer, uint256 amount);
    /// @notice A bond was forfeited: the intent expired without being anchored.
    event BondForfeited(bytes32 indexed leaf, address indexed payer, uint256 amount);

    error BondMismatch(uint256 required, uint256 supplied);
    error NoBond(bytes32 leaf);
    error BondAlreadySettled(bytes32 leaf);
    error BondNotRefundable(bytes32 leaf);
    error BondNotForfeitable(bytes32 leaf, uint64 deadline);
    error BondTransferFailed(address to, uint256 amount);
    error BondNeedsAForfeitDestination();

    /// @notice The one contract allowed to consume. Set once.
    address public consumer;

    /// @notice Discovery only. The envelope itself is in state — read it with
    ///         `envelopeOf`, which needs no `getLogs` range.
    event IntentPosted(
        bytes32 indexed leaf,
        bytes32 indexed targetChainRef,
        uint64 executeNotBefore,
        uint64 deadline,
        uint8 flags,
        uint256 headerBytes,
        uint256 ciphertextBytes
    );
    /// @notice The user read the posted intent back and approved it. Carries
    ///         the executor section's size — the bytes that let the executor
    ///         decrypt from here on.
    event IntentApproved(bytes32 indexed leaf, uint256 executorSectionBytes);
    /// @notice The approval key revoked the intent. Terminal.
    event IntentCancelled(bytes32 indexed leaf);
    /// @notice Consumed by an anchoring append.
    event IntentConsumed(bytes32 indexed leaf, address indexed by);
    event ConsumerSet(address indexed consumer);
    /// @notice A target chain's schedule lead was configured (W1).

    error HeaderTooShort(uint256 length);
    error UnsupportedHeaderVersion(uint8 version);
    error DeadlinePassed(uint64 deadline, uint64 nowMs);
    error DeadlineTooFar(uint64 deadline, uint64 limit);
    error ZeroLeaf();
    error AlreadyPosted(bytes32 leaf);
    error NotPosted(bytes32 leaf);
    error AlreadyConsumed(bytes32 leaf, uint64 at);
    error Expired(bytes32 leaf, uint64 deadline);
    /// @notice Header flag bits beyond the ones this version defines.
    error UnsupportedFlags(uint8 flags);
    /// @notice An AUTO_APPROVE header must commit to no approval key.
    error AutoApproveTakesNoCommitment(bytes32 approvalKeyCommit);
    /// @notice An approval-gated header must commit to one.
    error ApprovalKeyCommitRequired();
    /// @notice `executeNotBefore` sits beyond the scheduling horizon.
    error ScheduleTooFar(uint64 executeNotBefore, uint64 limit);
    /// @notice The presented key does not hash to the header's commitment.
    error WrongApprovalKey(bytes32 expected, bytes32 got);
    /// @notice The signature did not verify under the committed key.
    error ApprovalSignatureInvalid(bytes32 leaf);
    error AlreadyApproved(bytes32 leaf, uint64 at);
    /// @notice AUTO_APPROVE intents commit to no key: nothing to approve or
    /// cancel per-intent. Their veto is the account-wide freeze.
    error NoApprovalKey(bytes32 leaf);
    error IntentIsCancelled(bytes32 leaf, uint64 at);
    /// @notice `executeNotBefore` has not arrived. Scheduling is a chain rule.
    error TooEarly(bytes32 leaf, uint64 executeNotBefore, uint64 nowMs);
    /// @notice Neither approved nor AUTO_APPROVE: the user has not read it back.
    error NotApproved(bytes32 leaf);
    error NotConsumer(address caller);
    error ConsumerUnset();
    error ConsumerAlreadySet(address current);
    error ZeroConsumer();
    error NotAuthorized(address caller);

    /**
     * @dev No precompile of its own to call — this contract verifies no
     *      signature and reads no PQ key, it hashes and compares. The probe is
     *      still here because the claim "these deploy to Final Chain and nowhere
     *      else" should be enforced rather than documented, and a posting log on
     *      an execution chain would be a place intents could be posted that no
     *      anchor will ever read.
     */
    constructor(FinalIdentityRegistry registry_, FinalStateTrees trees_) {
        FinalChainPrecompiles.assertAvailable();
        registry = registry_;
        trees = trees_;
    }

    /// @notice A fresh log took over the previous log's sequence.
    event SequenceSeeded(uint64 postSeq);
    /// @notice The sequence can be seeded only into a log that holds nothing.
    error NotFresh();

    /**
     * @notice Take over the previous log's `postSeq`, so every walker's cursor
     *         (`postSeq` is the count; the fleet walks it, never a `getLogs`
     *         range) stays ahead of nothing and behind everything new. Slots
     *         below the seed are the old log's. NO-WIPE redeploy, ruled
     *         2026-09-03. Configuration authority; only while empty.
     */
    function seedSequence(
        uint64 postSeq_,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SEED_SEQUENCE, keccak256(abi.encode(postSeq_)), anchorBlock, approvals
        );
        if (postSeq != 0) revert NotFresh();
        postSeq = postSeq_;
        emit SequenceSeeded(postSeq_);
    }

    /**
     * @dev The configuration gate: the registry's bootstrap admin alone while
     * its window is open, the sealed `ROLE_REGISTRAR` quorum afterwards — the
     * same window and quorum the registry and the trees use. `approvals` is
     * empty during bootstrap.
     */
    function _requireConfigurationAuthority(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) private {
        if (!registry.bootstrapSealed() && msg.sender == registry.bootstrapAdmin()) return;
        registry.requireRegistrarQuorum(actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /**
     * @notice Name the contract allowed to consume postings.
     *
     * @dev One-way and one-shot, like `FinalStateTrees.treeWriter`. Deployment
     *      is circular — the bundle log needs this address in its constructor —
     *      so this is set afterwards, and it can never be moved: a consumer that
     *      could be repointed would be a consumer that could be replaced with
     *      one that does not check.
     *
     *      Authorized, and it has to be. Left open it would be a front-run away
     *      from permanent: an attacker naming their own contract first would
     *      brick the PQ lane with no recovery, because one-shot cuts both ways.
     */
    function setConsumer(
        address newConsumer,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_CONSUMER, keccak256(abi.encode(newConsumer)), anchorBlock, approvals
        );
        if (consumer != address(0)) revert ConsumerAlreadySet(consumer);
        if (newConsumer == address(0)) revert ZeroConsumer();
        consumer = newConsumer;
        emit ConsumerSet(newConsumer);
    }

    /// @notice The lead a scheduled posting for `targetChainRef` gets: the
    ///         config-branch row when one exists (explicit zero included),
    ///         else `DEFAULT_SCHEDULE_LEAD_MS`. Capped at the schedule horizon:
    ///         a longer lead would make every scheduled posting consumable at
    ///         post time. The fleet reads this so the watch and the contract
    ///         agree on when an attempt may begin.
    function scheduleLeadMsOf(bytes32 targetChainRef) public view returns (uint64) {
        (bytes32 value, bool present) =
            trees.configValue(TREE_INTENTS_ID, trees.configKey(CONFIG_SCHEDULE_LEAD_MS, targetChainRef));
        if (!present) return DEFAULT_SCHEDULE_LEAD_MS;
        uint256 lead = uint256(value);
        return lead > MAX_SCHEDULE_HORIZON ? MAX_SCHEDULE_HORIZON : uint64(lead);
    }

    /**
     * @notice Post an intent. Permissionless.
     *
     * @dev Permissionless on purpose. Gating posting on an identity would
     *      reintroduce exactly the attribution the encryption removes, so what
     *      bounds a spammer is the BOND rather than a quota: `msg.value` must
     *      equal `bondWei`, and it comes back only if the intent is anchored.
     *
     *      While `bondWei` is zero — the shipped state — this is what it has
     *      always been, and `msg.value` must then be zero too. Accepting a
     *      payment the contract has no rule for would be accepting value it
     *      cannot refund.
     *
     * @param header Canonical envelope header. Parsed, never stored.
     * @param ciphertext The sealed body. Emitted, never stored, never read.
     * @return leaf The intent leaf this posting is keyed on.
     */
    function post(bytes calldata header, bytes calldata ciphertext)
        external
        payable
        returns (bytes32 leaf)
    {
        if (header.length < HEADER_MIN_BYTES) revert HeaderTooShort(header.length);
        uint8 version = uint8(header[0]);
        if (version < HEADER_VERSION || version > HEADER_VERSION_MAX) {
            revert UnsupportedHeaderVersion(version);
        }
        uint8 flags = uint8(header[OFF_FLAGS]);
        if (flags & ~FLAG_AUTO_APPROVE != 0) revert UnsupportedFlags(flags);

        bytes32 targetChainRef = bytes32(header[OFF_TARGET_CHAIN_REF:OFF_TARGET_CHAIN_REF + 32]);
        uint64 executeNotBefore =
            uint64(bytes8(header[OFF_EXECUTE_NOT_BEFORE:OFF_EXECUTE_NOT_BEFORE + 8]));
        uint64 deadline = uint64(bytes8(header[OFF_DEADLINE:OFF_DEADLINE + 8]));
        bytes32 bodyCommitment = bytes32(header[OFF_BODY_COMMITMENT:OFF_BODY_COMMITMENT + 32]);
        leaf = bytes32(header[OFF_INTENT_LEAF:OFF_INTENT_LEAF + 32]);
        bytes32 approvalKeyCommit =
            bytes32(header[OFF_APPROVAL_KEY_COMMIT:OFF_APPROVAL_KEY_COMMIT + 32]);

        // The commitment and the flag are one decision stated twice, so the two
        // must agree: an auto intent with a commitment would look cancellable
        // and not be gated, and a gated one without a commitment could never be
        // approved by anyone — dead on arrival, wearing a live intent's shape.
        bool auto_ = flags & FLAG_AUTO_APPROVE != 0;
        if (auto_ && approvalKeyCommit != bytes32(0)) {
            revert AutoApproveTakesNoCommitment(approvalKeyCommit);
        }
        if (!auto_ && approvalKeyCommit == bytes32(0)) revert ApprovalKeyCommitRequired();

        if (leaf == bytes32(0)) revert ZeroLeaf();
        if (postedOf[leaf].postedAt != 0) revert AlreadyPosted(leaf);

        // Scheduling is a contract rule. An immediate intent gets the window
        // from now; a scheduled one gets it from its own start, and the start
        // itself is bounded by the horizon — chosen at signing, because the
        // deadline is inside the signed intent and cannot be extended after.
        uint64 nowMs = FinalChainTime.nowMs();
        if (deadline <= nowMs) revert DeadlinePassed(deadline, nowMs);
        if (executeNotBefore == 0) {
            uint64 limit = nowMs + EXECUTION_WINDOW;
            if (deadline > limit) revert DeadlineTooFar(deadline, limit);
        } else {
            uint64 horizon = nowMs + MAX_SCHEDULE_HORIZON;
            if (executeNotBefore > horizon) revert ScheduleTooFar(executeNotBefore, horizon);
            uint64 limit = executeNotBefore + EXECUTION_WINDOW;
            if (deadline > limit) revert DeadlineTooFar(deadline, limit);
        }

        // The bond scales with lifetime. A month-long posting occupies state
        // and attention a 48-hour one does not, and a flat bond makes
        // long-lived spam the cheap kind. Ceiling division, so a single extra
        // millisecond of a new window costs a whole unit.
        uint256 required = bondWei;
        if (required != 0) {
            required = required
                * ((uint256(deadline) - nowMs + EXECUTION_WINDOW - 1) / EXECUTION_WINDOW);
        }
        if (required > type(uint88).max) revert BondMismatch(type(uint88).max, required);
        if (msg.value != required) revert BondMismatch(required, msg.value);

        uint64 seq = postSeq;
        postSeq = seq + 1;
        leafAt[seq] = leaf;

        postedOf[leaf] = Posted({
            postedAt: nowMs,
            executeNotBefore: executeNotBefore,
            deadline: deadline,
            consumedAt: 0,
            bodyCommitment: bodyCommitment,
            approvalKeyCommit: approvalKeyCommit,
            targetChainRef: targetChainRef,
            approvedAt: 0,
            cancelledAt: 0,
            seq: seq,
            flags: flags
        });
        _envelopeOf[leaf] = Envelope({header: header, ciphertext: ciphertext, executorSection: ""});

        if (required != 0) {
            bondOf[leaf] = Bond({payer: msg.sender, amount: uint88(required), settled: false});
            emit BondPosted(leaf, msg.sender, required);
        }

        _writeStatus(leaf, postedOf[leaf], STATUS_POSTED);
        emit IntentPosted(
            leaf, targetChainRef, executeNotBefore, deadline, flags, header.length, ciphertext.length
        );
    }

    /**
     * @notice Set the bond rate and where a forfeit goes.
     *
     * @dev Same gate as `setConsumer`, and unlike it this is NOT one-shot: a
     * bond is a price and a price that could never move would be a parameter
     * chosen once, before the traffic it is meant to bound existed.
     *
     * Arming a non-zero bond without a forfeit destination is refused. Zero
     * means burn, which is a real choice — but it has to be made rather than
     * arrived at by leaving a field unset, because the difference is an
     * accumulating balance nobody can ever move.
     *
     * Changing the rate does not touch bonds already posted. Each stores what
     * was actually paid, so a rate rise cannot retroactively underpay a refund
     * and a cut cannot leave one over-funded.
     */
    function setBondPolicy(
        uint256 newBondWei,
        address forfeitTo,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_BOND_POLICY, keccak256(abi.encode(newBondWei, forfeitTo)), anchorBlock, approvals
        );
        if (newBondWei != 0 && forfeitTo == address(0)) {
            // Only when ARMING. Setting the rate back to zero with no
            // destination is disarming, which needs no destination.
            revert BondNeedsAForfeitDestination();
        }
        if (newBondWei > type(uint88).max) revert BondMismatch(type(uint88).max, newBondWei);
        bondWei = newBondWei;
        bondForfeitTo = forfeitTo;
        emit BondPolicySet(newBondWei, forfeitTo);
    }

    /**
     * @notice Return a bond whose intent was anchored.
     *
     * @dev Permissionless to CALL and fixed in destination: the money goes to
     * the recorded payer whoever asks. That is what lets a relayer sweep on a
     * user's behalf without being able to redirect anything.
     *
     * A pull rather than a push inside `consume`. `consume` runs in the
     * anchoring append, and a refund there would put an arbitrary payer's
     * `receive` on the path of every bundle — one contract that reverts, or
     * burns the gas, and the anchor fails for reasons that have nothing to do
     * with the bundle.
     *
     * `consumedAt != 0` is the whole test. Consumption means `FinalBundleLog`
     * folded this leaf into an anchored bundle, which is exactly "valid
     * execution" — there is no later outcome for the chain to wait on, because
     * from here the execution chains authorize against the anchor.
     */
    function claimBond(bytes32 leaf) external {
        Bond storage b = bondOf[leaf];
        if (b.payer == address(0)) revert NoBond(leaf);
        if (b.settled) revert BondAlreadySettled(leaf);
        if (postedOf[leaf].consumedAt == 0) revert BondNotRefundable(leaf);

        b.settled = true;
        uint256 amount = b.amount;
        address payer = b.payer;
        emit BondRefunded(leaf, payer, amount);
        _send(payer, amount);
    }

    /**
     * @notice Forfeit the bond on an intent that expired without being anchored.
     *
     * @dev Permissionless, and gated on the DEADLINE rather than on a judgement.
     * An intent past its deadline can never be consumed — `consume` refuses one
     * — so "expired and unconsumed" is a terminal, objective state, and anyone
     * may say so.
     *
     * That is the half of the bond that actually bounds spam. A refund on
     * success only makes an honest posting free; the cost of a vertex that goes
     * nowhere is what a spammer pays.
     */
    function forfeitBond(bytes32 leaf) external {
        Bond storage b = bondOf[leaf];
        if (b.payer == address(0)) revert NoBond(leaf);
        if (b.settled) revert BondAlreadySettled(leaf);
        Posted storage p = postedOf[leaf];
        if (p.consumedAt != 0) revert BondNotForfeitable(leaf, p.deadline);
        if (FinalChainTime.nowMs() <= p.deadline) revert BondNotForfeitable(leaf, p.deadline);

        b.settled = true;
        uint256 amount = b.amount;
        address to = bondForfeitTo;
        emit BondForfeited(leaf, b.payer, amount);
        // A zero destination BURNS: the value stays here and nothing can move
        // it. Deliberate, and the reason `setBondPolicy` refuses to arm a bond
        // without a destination unless somebody chose this one.
        if (to != address(0)) _send(to, amount);
    }

    /// @dev Checks-effects-interactions is done by the caller — `settled` is set
    /// before this runs — so a reentrant call finds the bond already spent.
    ///
    /// Full gas rather than a stipend, because a payer may legitimately be a
    /// contract. The consequence to accept: a payer whose `receive` reverts
    /// cannot be refunded and the bond is stuck. That is their own contract's
    /// behaviour, and the alternative — swallowing the failure — would mark the
    /// bond settled while the money stayed here.
    function _send(address to, uint256 amount) private {
        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert BondTransferFailed(to, amount);
    }

    /**
     * @notice Approve a posted intent: the read-back that authorizes execution.
     *
     * @dev Anyone may SUBMIT this — the authority is the signature and the
     *      sender only pays gas, the same standing the guardian lanes give
     *      their relay. The key arrives in calldata and proves something only
     *      because it is checked against the commitment the header made at
     *      posting: storage-anchored evidence, exactly the rule `FinalPqQuorum`
     *      states for quorum keys.
     *
     *      The digest binds this chain, this log, the leaf AND the executor
     *      section, so an approval cannot be replayed here or elsewhere, and
     *      the section it delivered cannot be swapped for another under the
     *      same signature.
     *
     * @param publicKey The per-intent ML-DSA-87 public key (2592 B).
     * @param signature Over the 32-byte approval digest, verbatim (4627 B).
     * @param executorSection The content key and the account's KEM public
     *        keys, sealed to the executor set. Stored beside the envelope;
     *        this contract never parses it.
     */
    function approve(
        bytes32 leaf,
        bytes calldata publicKey,
        bytes calldata signature,
        bytes calldata executorSection
    ) external {
        Posted storage p = _requireActionable(leaf);
        if (p.approvedAt != 0) revert AlreadyApproved(leaf, p.approvedAt);
        uint64 nowMs = FinalChainTime.nowMs();
        if (nowMs > p.deadline) revert Expired(leaf, p.deadline);

        bytes32 digest = keccak256(
            abi.encode(
                DOMAIN_INTENT_APPROVE, block.chainid, address(this), leaf, keccak256(executorSection)
            )
        );
        _requireApprovalKey(p, publicKey, signature, digest, leaf);

        p.approvedAt = nowMs;
        _envelopeOf[leaf].executorSection = executorSection;
        _writeStatus(leaf, p, STATUS_APPROVED);
        emit IntentApproved(leaf, executorSection.length);
    }

    /**
     * @notice Revoke a posted intent. Terminal, and allowed AFTER approval:
     *         a user may change their mind at any point before execution.
     *
     * @dev No deadline check — cancelling an expired intent is harmless and
     *      refusing it would fail a retry for nothing. Only consumption closes
     *      the door, because consumption is execution.
     */
    function cancel(bytes32 leaf, bytes calldata publicKey, bytes calldata signature) external {
        Posted storage p = _requireActionable(leaf);

        bytes32 digest =
            keccak256(abi.encode(DOMAIN_INTENT_CANCEL, block.chainid, address(this), leaf));
        _requireApprovalKey(p, publicKey, signature, digest, leaf);

        p.cancelledAt = FinalChainTime.nowMs();
        _writeStatus(leaf, p, STATUS_CANCELLED);
        emit IntentCancelled(leaf);
    }

    /// @dev Posted, not consumed, not cancelled — the states in which the
    /// approval key still has anything to say.
    function _requireActionable(bytes32 leaf) private view returns (Posted storage p) {
        p = postedOf[leaf];
        if (p.postedAt == 0) revert NotPosted(leaf);
        if (p.consumedAt != 0) revert AlreadyConsumed(leaf, p.consumedAt);
        if (p.cancelledAt != 0) revert IntentIsCancelled(leaf, p.cancelledAt);
    }

    /// @dev The committed key, and a valid ML-DSA-87 signature under it over
    /// the 32-byte digest verbatim — the same convention every quorum approval
    /// follows. An AUTO_APPROVE posting committed to nothing and has nothing
    /// to approve or cancel; its veto is the account-wide freeze.
    function _requireApprovalKey(
        Posted storage p,
        bytes calldata publicKey,
        bytes calldata signature,
        bytes32 digest,
        bytes32 leaf
    ) private view {
        bytes32 commit = p.approvalKeyCommit;
        if (commit == bytes32(0)) revert NoApprovalKey(leaf);
        bytes32 got = keccak256(publicKey);
        if (got != commit) revert WrongApprovalKey(commit, got);
        if (!FinalChainPrecompiles.verifyMlDsa87(publicKey, abi.encodePacked(digest), signature)) {
            revert ApprovalSignatureInvalid(leaf);
        }
    }

    // ------------------------------------------------------ tree-7 mirror

    /// @notice The tree-7 key for posting sequence `seq` — a RING, so slots
    ///         recycle at `INTENT_SLOT_RING` and the tree can never fill.
    /// @dev Mirrored by the backend byte for byte; pinned cross-repo.
    function intentSlotKey(uint64 seq) public pure returns (bytes32) {
        return keccak256(
            abi.encodePacked(DOMAIN_INTENT_KEY, bytes32(uint256(seq % INTENT_SLOT_RING)))
        );
    }

    /// @notice The tree-7 status leaf. Everything in it is public log state —
    ///         nothing account-linked, so the tree adds searchability without
    ///         touching unlinkability. Expiry is derived from `deadline` by the
    ///         reader, never written.
    function intentStatusLeaf(
        bytes32 intentLeaf,
        uint8 status,
        uint64 postedAt,
        uint64 executeNotBefore,
        uint64 deadline,
        bytes32 targetChainRef,
        bytes32 bodyCommitment,
        uint64 seq
    ) public pure returns (bytes32) {
        return keccak256(
            bytes.concat(
                DOMAIN_INTENT_STATUS_LEAF,
                abi.encode(
                    intentLeaf,
                    status,
                    postedAt,
                    executeNotBefore,
                    deadline,
                    targetChainRef,
                    bodyCommitment,
                    seq
                )
            )
        );
    }

    /// @dev One transition, one write. The log is `treeWriter[7]`, and the
    /// tree-1 argument applies verbatim: everything this mirrors was already
    /// verified here, so no quorum belongs on top.
    function _writeStatus(bytes32 leaf, Posted storage p, uint8 status) private {
        bytes32[] memory keys = new bytes32[](1);
        bytes32[] memory statusLeaves = new bytes32[](1);
        keys[0] = intentSlotKey(p.seq);
        statusLeaves[0] = intentStatusLeaf(
            leaf,
            status,
            p.postedAt,
            p.executeNotBefore,
            p.deadline,
            p.targetChainRef,
            p.bodyCommitment,
            p.seq
        );
        trees.setLeavesAsWriter(TREE_INTENTS_ID, BRANCH_MAIN_ID, keys, statusLeaves);
    }

    /**
     * @notice Consume a posted intent as part of an anchoring append.
     *
     * @dev Called by `FinalBundleLog` in the same transaction as the append, so
     *      there is no window in which a bundle is anchored and its intents are
     *      not yet spent.
     *
     *      Consumption is permanent and is the replay gate. The execution chain
     *      keeps its own consumed-seqId set, which covers a bundle being
     *      re-executed; this covers an intent being re-anchored into a second
     *      bundle, which that set does not see.
     */
    function consume(bytes32 leaf) external {
        // Checked before the comparison, not folded into it. An unset consumer
        // is `address(0)`, and `msg.sender != consumer` would then be FALSE for
        // a caller of `address(0)` — so an unwired log would consume for the one
        // caller nobody can be, which is the kind of "unreachable" that stops
        // being unreachable the moment something else changes.
        address c = consumer;
        if (c == address(0)) revert ConsumerUnset();
        if (msg.sender != c) revert NotConsumer(msg.sender);
        Posted storage p = postedOf[leaf];
        if (p.postedAt == 0) revert NotPosted(leaf);
        if (p.consumedAt != 0) revert AlreadyConsumed(leaf, p.consumedAt);
        if (p.cancelledAt != 0) revert IntentIsCancelled(leaf, p.cancelledAt);
        uint64 nowMs = FinalChainTime.nowMs();
        if (nowMs > p.deadline) revert Expired(leaf, p.deadline);
        // Scheduling is enforced HERE, which is what makes the funding and fee
        // re-checks execution-time checks for free: admission cannot happen
        // before the moment the intent named — less the lead, so the wrapper
        // that follows admission can land AT that moment rather than minutes
        // after it (`SCHEDULE_LEAD_MS`).
        if (nowMs + _scheduleLeadMs(p) < p.executeNotBefore) {
            revert TooEarly(leaf, p.executeNotBefore, nowMs);
        }
        // The read-back gate. Neither approved nor auto means the user has not
        // seen on chain what is about to execute.
        if (p.approvedAt == 0 && p.flags & FLAG_AUTO_APPROVE == 0) revert NotApproved(leaf);
        p.consumedAt = nowMs;
        _writeStatus(leaf, p, STATUS_CONSUMED);
        emit IntentConsumed(leaf, msg.sender);
    }

    /// @notice The stored envelope. One `eth_call`, no range, any age.
    ///
    /// @dev A getter rather than a public mapping so the three parts come back
    ///      in one call — a caller pairing a header from one read with a
    ///      ciphertext from another would be pairing across a posting that
    ///      landed between them.
    function envelopeOf(bytes32 leaf)
        external
        view
        returns (bytes memory header, bytes memory ciphertext, bytes memory executorSection)
    {
        Envelope storage e = _envelopeOf[leaf];
        return (e.header, e.ciphertext, e.executorSection);
    }

    /// @notice Is `leaf` posted, unspent, uncancelled and unexpired right now?
    function isOpen(bytes32 leaf) external view returns (bool) {
        Posted storage p = postedOf[leaf];
        return p.postedAt != 0 && p.consumedAt == 0 && p.cancelledAt == 0
            && FinalChainTime.nowMs() <= p.deadline;
    }

    /// @notice Would `consume` accept `leaf` right now? Open, due, and either
    ///         approved or AUTO_APPROVE — the work-queue predicate, so a solver
    ///         asks the same question the contract answers.
    function isConsumable(bytes32 leaf) external view returns (bool) {
        Posted storage p = postedOf[leaf];
        uint64 nowMs = FinalChainTime.nowMs();
        return p.postedAt != 0 && p.consumedAt == 0 && p.cancelledAt == 0 && nowMs <= p.deadline
            && nowMs + _scheduleLeadMs(p) >= p.executeNotBefore
            && (p.approvedAt != 0 || p.flags & FLAG_AUTO_APPROVE != 0);
    }

    /// @dev The lead `consume` grants a posting: its target chain's
    ///      (`scheduleLeadMsOf`) for a scheduled one, nothing for an immediate
    ///      one (there is no moment to lead).
    function _scheduleLeadMs(Posted storage p) private view returns (uint64) {
        return p.executeNotBefore == 0 ? 0 : scheduleLeadMsOf(p.targetChainRef);
    }
}

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

contracts/finalchain/FinalStateTrees.sol

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

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

/// @notice The one question `syncIdentities` asks the asset registry.
/// @dev An interface rather than an import of `FinalAssetRegistry`, which
///      imports this file: the registry is tree 6's writer and holds the trees
///      as an immutable, so the dependency runs that way and this is the one
///      read that runs the other.
interface IChainSource {
    function enabledChainRefs() external view returns (bytes32[] memory);
}

/// @notice The one question {FinalStateTrees.syncSlotKeyLeaves} asks the
///         slot-key registry: the leaf value for one member's slot — the
///         registry's own verdict, zero when the slot holds nothing usable.
interface ISlotKeySource {
    function slotKeyLeafOf(address member, uint64 slotIndex) external view returns (bytes32);
}

/// @notice The one question {FinalStateTrees.syncEndpointLeaves} asks the
///         endpoint registry: the leaf value for one tunnel endpoint — the
///         registry's own verdict (certificate hash, status, expiry, region),
///         zero when nothing is registered under the id.
interface IEndpointSource {
    function endpointLeafOf(bytes32 endpointId) external view returns (bytes32);
}

/**
 * @title FinalStateTrees
 * @notice The eight trees. Final Chain's state plane, and the source of truth
 *         every other chain projects from.
 *
 * @dev One tree per domain, because they change at unrelated cadences and a
 * combined tree invalidates every outstanding proof on every tick:
 *
 * | # | tree | holds | cadence |
 * |---|---|---|---|
 * | 1 | accounts | every Final Wallet's public state | per rotation / creation |
 * | 2 | phi | the PHI record: per (wallet, chain) balances, the lock, exposures | per publisher round |
 * | 3 | vasset | issued vAsset supply and backing, per (asset, chain) | per settlement |
 * | 4 | oracle | published prices and their inputs | ~10 s; 1 s for morph and fee assets |
 * | 5 | settlement | chain and asset registry roots | rarely |
 * | 6 | allowlist | assets, chains, policy, price sources, DEX deployments | rarely |
 * | 7 | intents | intent status, ring-keyed over the posting sequence | per posting |
 * | 8 | identity | the wallet-creation admission set, projected from the registry | per identity mutation |
 *
 * ## The hash shape is not a choice
 *
 * Leaves hash as `keccak256(0x00 ‖ leaf)` and internal nodes as
 * `keccak256(0x01 ‖ lo ‖ hi)` with the pair sorted. That is
 * `FinalMerkle.verifyTaggedSortedProof`, verbatim, which is what
 * `FinalWalletFactory.syncAccountState` and `FinalSettlement` already run on
 * every supported chain. A proof produced here is consumed there with no
 * translation and no contract change, and tree 1's leaf preimage is exactly
 * `FinalWalletFactory.accountStateLeafHash` — same fields, same order, the
 * `deployedChains` table `abi.encode`d like every other field.
 *
 * Getting this wrong is not a compile error anywhere. It is a root every chain
 * silently rejects, with nothing pointing at the cause.
 *
 * ## Positional slots under a sorted-pair tree
 *
 * Sorted pairs make a proof position-agnostic, which is why it carries no
 * direction bits. That does not stop the TREE from being positional, and here
 * it is: every key gets a permanent slot, so a single leaf update is `DEPTH`
 * hashes instead of a rebuild over every leaf. The verifier neither knows nor
 * needs to know that a slot exists.
 *
 * ## Branches (2026-09-04)
 *
 * The slot space of every tree is cut into `BRANCH_COUNT` branches by the top
 * `BRANCH_BITS` of the slot: a branch is a subtree with a permanent place, its
 * root is one internal node, and a leaf's path to the tree root passes through
 * it. Branches hold what belongs to the same domain but not to the same rows
 * — branch 0 is the owning service's CONFIGURATION on every tree, tree 8 adds
 * the owner → wallets index and the co-signers' slot keys beside the admission
 * set — and they are chosen over more trees because a branch shares its
 * tree's authority doors and writer, while a tree would need its own. A leaf
 * proves against its branch root with `BRANCH_DEPTH` siblings, against the
 * tree root with `DEPTH`, against the round root with `ROUND_DEPTH`: one path,
 * cut at three heights, one verifier.
 *
 * ## Rounds, and why the live roots are not the product
 *
 * `setLeaves` moves a tree. It does not publish one. A consumer that fetched
 * eight roots one at a time would get a price proof from one moment and a
 * roster proof from another, and something delisted in between would still
 * verify.
 *
 * `publishRound` snapshots all eight together, and folds them into ONE round
 * root — the tree roots as the level-`DEPTH` nodes of a depth-`ROUND_DEPTH`
 * tree, tree `t` at position `t` — so a single word commits to the whole
 * plane and any leaf in it proves against that word with four more siblings.
 * A round is the unit a consumer pins, and it is the only thing this contract
 * promises is contemporaneous. The execution chains keep anchoring per-tree
 * roots (identity, account state, registry roots): those must move at their
 * own cadence, not at the oracle's.
 */
contract FinalStateTrees {
    // ---------------------------------------------------------------- trees

    /// @notice Every Final Wallet's public state. The source of truth other
    /// chains copy through `syncAccountState`.
    uint8 public constant TREE_ACCOUNTS = 1;
    /// @notice The PHI record, per `(wallet, chain)`: balances, the lock, its
    /// terms, the exposures carved from it and the accrual between reconciliations.
    uint8 public constant TREE_PHI = 2;
    /// @notice vAsset supply and backing.
    uint8 public constant TREE_VASSET = 3;
    /// @notice Oracle prices and their inputs.
    uint8 public constant TREE_ORACLE = 4;
    /// @notice Settlement chain and asset registry roots.
    uint8 public constant TREE_SETTLEMENT = 5;
    /// @notice Which assets and chains are supported.
    uint8 public constant TREE_ALLOWLIST = 6;
    /// @notice Intent status, keyed by a RING over the posting sequence.
    /// @dev The search structure beside `FinalBundleLog`'s permanent record.
    /// Written only by `FinalIntentLog` through `treeWriter[7]` — the tree-1
    /// argument verbatim: the log verified the bond, the commitment, the
    /// approval and the consume itself, and a service quorum on top would be a
    /// censorship point over posting. Slots are permanent and intents are
    /// unbounded flow, so the log recycles keys modulo `CAPACITY`: the tree is
    /// an index with a ~1M-posting retention window, never the record.
    uint8 public constant TREE_INTENTS = 7;
    /// @notice The wallet-creation admission set — the identity leaves
    /// (`keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)`) every execution
    /// chain's gateway verifies certificates against.
    /// @dev The root the gateways anchor as `currentIdentityRoot` — a
    /// CONTINUOUS root over this tree replaces the cold-set snapshot the
    /// retired `publishIdentityRoot.cjs` ceremony folded off-chain. Two
    /// feeders, one per identity plane, and NO quorum door for either:
    ///
    /// - SERVICE identities: {syncIdentityLeaves}, the permissionless
    ///   projection of `FinalIdentityRegistry`'s own verdict — the registry
    ///   calls it same-tx on every identity mutation, and anyone may call it
    ///   to retire a leaf whose standing lapsed by TIME (expiry moves no
    ///   registry storage, so only a projection pass can zero it).
    /// - USER identities: `treeWriter[8]` — `FinalAccountLedger`, which
    ///   computes the leaf from the genesis certificate fields it verified
    ///   under its opener quorum and writes it once at `openAccount`. A user
    ///   admission leaf is permanent by construction: the certificate IS the
    ///   address, rotation never changes it, and a post-rotation creation on
    ///   a new chain reads PUBLISHED account state (WAL-02), never the
    ///   certificate's genesis keys.
    ///
    /// A quorum of service signatures must not be able to state an identity
    /// neither ruler decided, so `setLeaves` refuses this tree outright.
    uint8 public constant TREE_IDENTITY = 8;
    /// @notice Count, for iteration. Trees are 1-indexed; 0 is not a tree.
    uint8 public constant TREE_COUNT = 8;

    /// @notice 2^24 slots per tree, laid out as 16 BRANCHES of 2^20: the top
    /// `BRANCH_BITS` of a slot name the branch, the rest its position in it.
    /// A million rows per branch is far past where this design gets replaced
    /// by Final Chain proper. Raising any of this later is a migration, not a
    /// parameter change: the depth is in every root.
    uint256 public constant DEPTH = 24;
    uint256 public constant BRANCH_BITS = 4;
    uint8 public constant BRANCH_COUNT = 16;
    /// @notice Height of a branch: a leaf proves against its branch root with
    /// this many siblings.
    uint256 public constant BRANCH_DEPTH = DEPTH - BRANCH_BITS;
    /// @notice Slots per branch.
    uint256 public constant BRANCH_CAPACITY = 1 << BRANCH_DEPTH;
    /// @notice Slots per tree, all branches together.
    uint256 public constant CAPACITY = 1 << DEPTH;
    /// @notice The round root is a tree over the tree roots — position `t`
    /// holds tree `t`'s root, positions 0 and 9..15 the empty tree — folded
    /// with the same node hash. It is literally the root of a depth-28 tree
    /// whose level-24 nodes are the eight tree roots, which is what lets one
    /// path prove a leaf against it.
    uint256 public constant FOREST_BITS = 4;
    uint256 public constant ROUND_DEPTH = DEPTH + FOREST_BITS;

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

    /// @dev Must equal `FinalWalletFactory.DOMAIN_ACCOUNT_STATE_LEAF`. Pinned
    /// by the cross-repo parity test; a field reordered on one side and not the
    /// other is a root every chain rejects.
    ///
    /// `v02`: `deployedChains` became the `(chainRef, account)` table. A v01
    /// leaf and a v02 leaf never share a domain, so a proof built against the
    /// retired plane cannot verify against this one by accident.
    bytes32 public constant DOMAIN_ACCOUNT_STATE_LEAF =
        keccak256("FINAL_ACCOUNT_STATE_LEAF_v02");

    bytes32 private constant ACTION_SET_LEAVES = keccak256("FinalStateTrees.setLeaves.v01");
    /// @dev Registrar-quorum actions, verified by the registry with this
    /// contract as the verifying contract. See `FinalIdentityRegistry.requireRegistrarQuorum`.
    bytes32 public constant ACTION_CONFIGURE_TREE = keccak256("FINAL_STATE_TREES_CONFIGURE_TREE_v01");
    bytes32 public constant ACTION_SET_TREE_WRITER = keccak256("FINAL_STATE_TREES_SET_TREE_WRITER_v01");
    bytes32 public constant ACTION_SET_CHAIN_SOURCE = keccak256("FINAL_STATE_TREES_SET_CHAIN_SOURCE_v01");
    bytes32 public constant ACTION_SET_SLOT_KEY_SOURCE = keccak256("FINAL_STATE_TREES_SET_SLOT_KEY_SOURCE_v01");
    bytes32 public constant ACTION_SET_ENDPOINT_SOURCE = keccak256("FINAL_STATE_TREES_SET_ENDPOINT_SOURCE_v01");
    bytes32 public constant ACTION_SEED_COUNTERS = keccak256("FINAL_STATE_TREES_SEED_COUNTERS_v01");
    bytes32 public constant ACTION_SET_TYPED_WRITER = keccak256("FINAL_STATE_TREES_SET_TYPED_WRITER_v01");
    bytes32 public constant ACTION_SET_CONFIG = keccak256("FINAL_STATE_TREES_SET_CONFIG_v01");

    /// @dev Key domains. Both are full-width hashes rather than the packed
    /// address they came from, which matters: an address key occupies only the
    /// low 160 bits, so a hashed key colliding with one needs ~2^96 work rather
    /// than a full collision. That is expensive but not comfortable, and the
    /// consequence would be a service identity landing in a wallet's slot.
    bytes32 private constant DOMAIN_ACCOUNT_KEY = keccak256("FinalStateTrees.key.account.v01");
    bytes32 private constant DOMAIN_IDENTITY_TREE_KEY = keccak256("FinalStateTrees.key.identity.v01");
    /// @dev Tree 8, branches 2 and 3, and branch 0 of every tree. Each is its
    ///      own domain so a key can never land in another branch's slot by
    ///      construction — `_set` refuses a key whose slot sits in a different
    ///      branch, and the domain is what makes that refusal unreachable.
    bytes32 private constant DOMAIN_OWNER_INDEX_KEY = keccak256("FinalStateTrees.key.ownerIndex.v01");
    bytes32 private constant DOMAIN_SLOT_KEY = keccak256("FinalStateTrees.key.slotKey.v01");
    bytes32 private constant DOMAIN_ENDPOINT_KEY = keccak256("FinalStateTrees.key.endpoint.v01");
    bytes32 private constant DOMAIN_CONFIG_KEY = keccak256("FinalStateTrees.key.config.v01");

    /// @notice Leaf domains for the owner index (tree 8, branch 2) and for
    /// configuration rows (branch 0 of every tree). The config leaf binds the
    /// tree too, so the same row in two trees is two different leaves.
    bytes32 public constant DOMAIN_OWNER_INDEX_LEAF = keccak256("FINAL_OWNER_INDEX_LEAF_v01");
    bytes32 public constant DOMAIN_CONFIG_LEAF = keccak256("FINAL_CONFIG_LEAF_v01");

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

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

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

    /// Raw (untagged) leaf value by tree and slot.
    mapping(uint8 => mapping(uint256 => bytes32)) private _leaf;
    /// Internal nodes, levels 1..DEPTH. Level 0 is derived from `_leaf`.
    mapping(uint8 => mapping(uint256 => mapping(uint256 => bytes32))) private _node;
    /// Empty-subtree hash per level, computed once at construction — up to
    /// the round root's height, since the forest's empty positions are empty
    /// trees.
    bytes32[ROUND_DEPTH + 1] private _zero;

    /// Permanent slot for a key, 1-based so 0 means unassigned. The slot's top
    /// `BRANCH_BITS` are the branch the key lives in.
    mapping(uint8 => mapping(bytes32 => uint256)) private _slotPlusOne;
    /// The key a slot was handed to — the reverse of `_slotPlusOne`, so any
    /// branch enumerates on chain (`keyAt` over `0 .. branchSlotsUsed`) with
    /// no log window. One extra word per NEW key, never per update.
    mapping(uint8 => mapping(uint256 => bytes32)) private _keyAt;
    /// @notice Slots handed out per tree, all branches together.
    mapping(uint8 => uint256) public slotsUsed;
    /// Slots handed out per branch — the next position in it.
    mapping(uint8 => mapping(uint8 => uint256)) private _branchSlotsUsed;
    /// The VALUE behind a configuration row (branch 0), by tree and key.
    mapping(uint8 => mapping(bytes32 => bytes32)) private _configValue;

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

    /// @notice A contemporaneous snapshot of all eight roots, and the one
    /// round root that folds them.
    struct Round {
        bytes32[TREE_COUNT + 1] roots;
        bytes32 roundRoot;
        uint64 blockNumber;
        uint64 timestamp;
    }

    /// @notice Published rounds, 1-indexed. Round 0 is "nothing published".
    mapping(uint64 => Round) private _rounds;
    /// @notice Highest published round.
    uint64 public round;
    /// Tree versions as of the last published round.
    mapping(uint8 => uint64) private _publishedVersion;

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

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

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

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

    event LeavesSet(uint8 indexed treeId, uint256 count, bytes32 newRoot, uint64 treeVersion);
    event RoundPublished(uint64 indexed round, uint64 blockNumber, uint64 timestamp);
    event TreeConfigured(uint8 indexed treeId, uint256 writerRole, uint256 threshold);
    event TreeWriterSet(uint8 indexed treeId, address writer);
    event ChainSourceSet(address source);
    event SlotKeySourceSet(address source);
    event EndpointSourceSet(address source);
    /// @notice A fresh plane took over the previous plane's counters.
    event CountersSeeded(uint64 round, uint64[] versions);
    event TypedWriterSet(address writer);
    event ConfigSet(uint8 indexed treeId, bytes32 indexed key, bytes32 value);

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

    error UnknownTree(uint8 treeId);
    error LengthMismatch(uint256 keys, uint256 leaves);
    error BranchFull(uint8 treeId, uint8 branch);
    error UnknownBranch(uint8 branch);
    /// @notice A key already holds a slot in another branch of this tree.
    error BranchMismatch(uint8 treeId, bytes32 key, uint8 have, uint8 want);
    /// @notice Branch 0 is written by `setConfig` alone.
    error ConfigBranchReserved(uint8 treeId);
    error SlotKeySourceUnset();
    error EndpointSourceUnset();
    /// @notice Counters can be seeded only into a plane that has published nothing.
    error NotFresh();
    error VersionCountMismatch(uint256 given);
    error TreeNotConfigured(uint8 treeId);
    error NothingToPublish();
    error UnknownKey(uint8 treeId, bytes32 key);
    error NotAuthorized(address caller);
    error NoRounds();
    error ThresholdUnreachable(uint8 treeId, uint256 live, uint256 required);
    /// @notice Trees 7 and 8 take no quorum writes — only their writer
    /// contract (and, for tree 8, the registry projection).
    error WriterOnlyTree(uint8 treeId);
    /// @notice `setLeaves` was called on a tree that has a typed writer.
    /// @dev Trees 2, 3 and 4 keep the leaf's preimage beside its hash so a
    ///      consumer can read the VALUE. An untyped write sets the hash and
    ///      cannot set the preimage — the pair would disagree, and the stored
    ///      value would look authoritative while committing to nothing. The
    ///      typed entrypoint is not a convenience over this one; it is the
    ///      only door.
    error TypedTreeOnly(uint8 treeId);
    /// @notice A `deployedChains` row names the zero chain or the zero account,
    ///         or repeats a chain. A table with either proves nothing about
    ///         where the account exists.
    error InvalidChainAccount(bytes32 chainRef, bytes32 account);

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

    /**
     * @param registry_ The identity registry. Every signer, key and role is
     *        resolved through it.
     * @dev The empty-subtree table is built here rather than as constants
     * because it depends on the tagging, and a constant table that drifted from
     * the tagging would produce roots nothing can verify — silently, since both
     * sides would still be self-consistent.
     */
    constructor(FinalIdentityRegistry registry_) {
        registry = registry_;

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

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

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

    /**
     * @dev The configuration gate: the registry's bootstrap admin alone while
     * its window is open, the sealed `ROLE_REGISTRAR` quorum afterwards. The
     * same window the registry uses, for the same reason — every roster has to
     * be installed by someone before it can install itself — and the same
     * quorum, because a threshold is membership by another name: whoever can
     * set K to one owns the tree.
     */
    function _requireConfigurationAuthority(
        bytes32 actionDomain,
        bytes32 payloadDigest,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) private {
        if (!registry.bootstrapSealed() && msg.sender == registry.bootstrapAdmin()) return;
        registry.requireRegistrarQuorum(actionDomain, payloadDigest, anchorBlock, approvals);
    }

    /**
     * @notice Set which role may write a tree and how many approvals it needs.
     * @param k Approvals a write needs; `0` leaves the tree unconfigured.
     * @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
     * @param approvals The sealed registrar quorum. Empty during bootstrap.
     */
    function configureTree(
        uint8 treeId,
        uint256 role,
        uint256 k,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _assertTree(treeId);
        _requireConfigurationAuthority(
            ACTION_CONFIGURE_TREE, keccak256(abi.encode(treeId, role, k)), anchorBlock, approvals
        );
        // Refuse a threshold nobody can meet. Register the members first; that
        // ordering is the point, not an inconvenience. A 4-of-5 configured
        // against three registered co-signers is a tree that reverts on every
        // write, and the revert names the threshold rather than the roster.
        if (k != 0) {
            uint256 live = registry.liveMemberCount(role);
            if (live < k) revert ThresholdUnreachable(treeId, live, k);
        }
        writerRole[treeId] = role;
        threshold[treeId] = k;
        emit TreeConfigured(treeId, role, k);
    }

    /**
     * @notice Point a tree at the contract allowed to write it directly.
     * @dev Same gate as `configureTree`, for the same reason. Setting it to the
     * zero address removes the path entirely and leaves the tree quorum-only.
     */
    function setTreeWriter(
        uint8 treeId,
        address writer,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _assertTree(treeId);
        _requireConfigurationAuthority(
            ACTION_SET_TREE_WRITER, keccak256(abi.encode(treeId, writer)), anchorBlock, approvals
        );
        treeWriter[treeId] = writer;
        emit TreeWriterSet(treeId, writer);
    }

    /**
     * @notice Point `syncIdentities` at the contract that knows the chain set.
     * @dev Same gate as `setTreeWriter`. Zero removes the source, after which
     * service leaves carry an empty `deployedChains` table.
     */
    function setChainSource(
        address source,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_CHAIN_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
        );
        chainSource = source;
        emit ChainSourceSet(source);
    }

    /// @notice Point tree 8's branch 3 at the slot-key registry it projects.
    function setSlotKeySource(
        address source,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_SLOT_KEY_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
        );
        slotKeySource = source;
        emit SlotKeySourceSet(source);
    }

    /// @notice Point tree 8's branch 4 at the endpoint registry it projects.
    function setEndpointSource(
        address source,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_ENDPOINT_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
        );
        endpointSource = source;
        emit EndpointSourceSet(source);
    }

    /**
     * @notice Take over the previous plane's counters — one `treeVersion` per
     *         tree (index = treeId, 0 unused) and the published `round` — so a
     *         redeploy is monotonic for every consumer that compares them
     *         (rings, explorers, the round feed). NO-WIPE redeploy, ruled
     *         2026-09-03. Past rounds' roots stay on the old plane:
     *         `roundRootAt` below the seed answers zero.
     * @dev Configuration authority (bootstrap admin before the seal, registrar
     *      quorum after), and only while this plane has published nothing.
     */
    function seedCounters(
        uint64[] calldata versions,
        uint64 round_,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SEED_COUNTERS, keccak256(abi.encode(versions, round_)), anchorBlock, approvals
        );
        if (versions.length != TREE_COUNT + 1) revert VersionCountMismatch(versions.length);
        if (round != 0) revert NotFresh();
        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            if (treeVersion[t] != 0) revert NotFresh();
        }
        for (uint8 t = 1; t <= TREE_COUNT; t++) {
            treeVersion[t] = versions[t];
        }
        round = round_;
        emit CountersSeeded(round_, versions);
    }

    /// @notice Install the records contract that writes the typed trees.
    function setTypedWriter(
        address writer,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        _requireConfigurationAuthority(
            ACTION_SET_TYPED_WRITER, keccak256(abi.encode(writer)), anchorBlock, approvals
        );
        typedWriter = writer;
        emit TypedWriterSet(writer);
    }

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

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

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

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

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

        _bump(treeId, keys.length);
    }

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

    /// @notice `FinalWalletFactory.AccountStateLeaf`, field for field.
    struct AccountStateLeaf {
        address wallet;
        bytes32 liveAccess;
        bytes32 liveTransaction;
        bytes32 recoveryAccess;
        bytes32 recoveryTransaction;
        /// @dev Active-stage encapsulation commitment and its pre-committed
        /// successor. Field-for-field with `FinalWalletFactory.AccountStateLeaf`;
        /// a field added on one side and not the other is a root every execution
        /// chain rejects, with nothing pointing at the cause.
        bytes32 liveKem;
        bytes32 recoveryKem;
        address owner;
        bool pqEnabled;
        bool frozen;
        /// @dev The chains this account exists on, and its account on each —
        /// including chains whose accounts are not EVM addresses. Decided HERE
        /// (set by the holder through the ledger) and enforced there: an
        /// execution chain refuses to create the account unless the table has a
        /// row for it, and a settlement toward a chain with no row is refused at
        /// the source. This is what a zero beneficiary resolves through; it
        /// replaced a bitmask over registry slots that could only say "may
        /// exist", never "as what".
        ChainAccount[] deployedChains;
        /// @dev Per-chain dormancy verdict, one bit per asset-registry chain
        /// slot. Keeps the slot space the bitmask had.
        uint32 dormantChains;
        uint64 version;
    }

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

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

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

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

        _bump(TREE_ACCOUNTS, leaves.length);
    }

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

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

    /// @notice The leaf hash `FinalWalletFactory.accountStateLeafHash` computes.
    /// @dev Identical `abi.encode`, identical field order, identical domain.
    /// Pinned against the factory by test. `deployedChains` rides through
    /// `abi.encode` like every other field — head offset, then length and
    /// rows — so the table is committed whole and in order.
    function accountStateLeafHash(AccountStateLeaf memory leaf) public pure returns (bytes32) {
        _assertChainAccounts(leaf.deployedChains);
        return keccak256(
            abi.encode(
                DOMAIN_ACCOUNT_STATE_LEAF,
                leaf.wallet,
                leaf.liveAccess,
                leaf.liveTransaction,
                leaf.recoveryAccess,
                leaf.recoveryTransaction,
                leaf.liveKem,
                leaf.recoveryKem,
                leaf.owner,
                leaf.pqEnabled,
                leaf.frozen,
                leaf.deployedChains,
                leaf.dormantChains,
                leaf.version
            )
        );
    }

    /// @dev A well-formed table: no zero chain, no zero account, no chain twice.
    ///      Checked where the leaf is hashed so no door — quorum, writer
    ///      contract, identity projection — can publish a table a resolver
    ///      would read two ways.
    function _assertChainAccounts(ChainAccount[] memory rows) private pure {
        for (uint256 i = 0; i < rows.length; i++) {
            if (rows[i].chainRef == bytes32(0) || rows[i].account == bytes32(0)) {
                revert InvalidChainAccount(rows[i].chainRef, rows[i].account);
            }
            for (uint256 j = 0; j < i; j++) {
                if (rows[j].chainRef == rows[i].chainRef) {
                    revert InvalidChainAccount(rows[i].chainRef, rows[i].account);
                }
            }
        }
    }

    /// @notice The account `wallet`'s published table names on `chainRef`, or
    ///         zero if it has no row there.
    /// @dev A convenience over `accountStateLeafHash`'s input for readers on
    /// this chain; execution chains answer the same question from their synced
    /// record (`FinalWalletFactory.addressOn`).
    function accountOn(AccountStateLeaf memory leaf, bytes32 chainRef) public pure returns (bytes32) {
        for (uint256 i = 0; i < leaf.deployedChains.length; i++) {
            if (leaf.deployedChains[i].chainRef == chainRef) return leaf.deployedChains[i].account;
        }
        return bytes32(0);
    }

    /**
     * @notice The typed trees' write door — `FinalStateRecords` alone.
     * @dev The quorum, the nonce and the write, shared by every typed record.
     * The records contract computed the keys and hashes from the structs it
     * stores; this contract admits nobody else to trees 2, 3 and 4
     * (`setLeaves` refuses them), so the value there can never drift from
     * the commitment here.
     *
     * The digest is byte-identical to `setLeaves`' over the same keys and
     * hashes, deliberately: the typed entrypoints choose the PREIMAGE, not the
     * authorization. A member recomputes one digest whichever door the batch
     * came through, and there is no second approval shape to get wrong.
     */
    function writeTyped(
        uint8 treeId,
        bytes32[] memory keys,
        bytes32[] memory hashes,
        uint64 anchorBlock,
        FinalPqQuorum.Approval[] calldata approvals
    ) external {
        if (msg.sender != typedWriter) revert NotAuthorized(msg.sender);
        uint256 k = threshold[treeId];
        if (k == 0) revert TreeNotConfigured(treeId);

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

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

        _bump(treeId, keys.length);
    }

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

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

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

    /// @notice Every root from one round. Index by the `TREE_*` constants;
    /// index 0 is unused.
    function rootsAt(uint64 which) external view returns (bytes32[TREE_COUNT + 1] memory) {
        return _rounds[which].roots;
    }

    /// @notice One tree's root at one round.
    function rootAt(uint64 which, uint8 treeId) external view returns (bytes32) {
        _assertTree(treeId);
        return _rounds[which].roots[treeId];
    }

    /// @notice The one word that commits to every tree at one round.
    function roundRootAt(uint64 which) external view returns (bytes32) {
        return _rounds[which].roundRoot;
    }

    /**
     * @notice The `FOREST_BITS` siblings that take a tree's root at one round
     *         up to that round's root — appended to `proofFor`, they make a
     *         leaf provable against `roundRootAt(which)` by the same verifier.
     */
    function roundProofFor(uint64 which, uint8 treeId) external view returns (bytes32[] memory path) {
        _assertTree(treeId);
        if (which == 0 || which > round) revert NoRounds();
        bytes32[] memory level = _forestLeaves(_rounds[which].roots);
        path = new bytes32[](FOREST_BITS);
        uint256 idx = treeId;
        uint256 n = level.length;
        for (uint256 l = 0; l < FOREST_BITS; l++) {
            path[l] = level[idx ^ 1];
            n >>= 1;
            for (uint256 i = 0; i < n; i++) {
                level[i] = _pair(level[2 * i], level[2 * i + 1]);
            }
            idx >>= 1;
        }
    }

    /// @notice The latest round's roots, with the block it was taken at.
    function latestRound()
        external
        view
        returns (uint64 which, bytes32[TREE_COUNT + 1] memory roots, uint64 blockNumber, uint64 timestamp)
    {
        which = round;
        if (which == 0) revert NoRounds();
        Round storage r = _rounds[which];
        return (which, r.roots, r.blockNumber, r.timestamp);
    }

    /// @notice The raw leaf stored for a key, and whether it has a slot.
    function leafOf(uint8 treeId, bytes32 key) external view returns (bytes32 leaf, bool present) {
        uint256 s = _slotPlusOne[treeId][key];
        if (s == 0) return (bytes32(0), false);
        return (_leaf[treeId][s - 1], true);
    }

    /// @notice The permanent slot for a key. Reverts if it has none. The
    /// slot's top `BRANCH_BITS` are its branch.
    function slotOf(uint8 treeId, bytes32 key) public view returns (uint256) {
        uint256 s = _slotPlusOne[treeId][key];
        if (s == 0) revert UnknownKey(treeId, key);
        return s - 1;
    }

    /// @notice The key a slot was handed to, or zero if it is still free —
    /// the enumeration every branch offers: slots `branch << BRANCH_DEPTH`
    /// through `+ branchSlotsUsed(treeId, branch) - 1`.
    function keyAt(uint8 treeId, uint256 slot) external view returns (bytes32) {
        return _keyAt[treeId][slot];
    }

    /// @notice Slots handed out in one branch.
    function branchSlotsUsed(uint8 treeId, uint8 branch) external view returns (uint256) {
        return _branchSlotsUsed[treeId][branch];
    }

    /// @notice One branch's root: the level-`BRANCH_DEPTH` node at its position.
    function branchRoot(uint8 treeId, uint8 branch) external view returns (bytes32) {
        _assertTree(treeId);
        _assertBranch(branch);
        return _nodeAt(treeId, BRANCH_DEPTH, branch);
    }

    /// @notice The first `BRANCH_DEPTH` siblings of `proofFor` — a proof
    /// against the leaf's branch root rather than the tree root.
    function branchProofFor(uint8 treeId, bytes32 key) external view returns (bytes32[] memory) {
        _assertTree(treeId);
        return _path(treeId, slotOf(treeId, key), BRANCH_DEPTH);
    }

    /// @notice A configuration row's value, and whether the row exists.
    function configValue(uint8 treeId, bytes32 key) external view returns (bytes32 value, bool present) {
        present = _slotPlusOne[treeId][key] != 0;
        value = _configValue[treeId][key];
    }

    /// @notice The branch-0 key of a configuration row: a name the owning
    /// service defines, and a sub-key (a chain reference, an asset, zero).
    function configKey(bytes32 name, bytes32 sub) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_CONFIG_KEY, name, sub));
    }

    /// @notice The leaf a configuration row hashes to.
    function configLeafHash(uint8 treeId, bytes32 key, bytes32 value) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_CONFIG_LEAF, treeId, key, value));
    }

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

    /// @notice The owner-index leaf: a commitment to the ledger's ordered
    /// `walletsByOwner(owner)`.
    function ownerIndexLeafHash(address owner, address[] memory wallets) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_OWNER_INDEX_LEAF, owner, wallets));
    }

    /// @notice The tree-8 branch-3 key of one member's slot — a ring position.
    function slotKeyFor(address member, uint64 slotIndex) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_SLOT_KEY, member, slotIndex % SLOT_KEY_RING));
    }

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

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

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

    /**
     * @notice The sibling path for a key, ready for
     *         `FinalMerkle.verifyTaggedSortedProof` on any chain.
     * @dev A view, so the backend fetches a proof with one `eth_call` instead of
     * rebuilding the tree off chain. Rebuilding is where a divergence between
     * what the chain holds and what a service believes it holds would come
     * from, and this removes the second implementation entirely.
     */
    function proofFor(uint8 treeId, bytes32 key) external view returns (bytes32[] memory) {
        _assertTree(treeId);
        return _path(treeId, slotOf(treeId, key), DEPTH);
    }

    /// @notice The empty-subtree hash at a level. Level `DEPTH` is the root of
    /// a tree with nothing in it.
    function emptyRoot(uint256 level) external view returns (bytes32) {
        return _zero[level];
    }

    /// @notice The tree-1 key a wallet occupies.
    function accountKeyFor(address wallet) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_ACCOUNT_KEY, wallet));
    }

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

    /// @notice The tree-8 slot key an identity occupies.
    function identityKeyFor(address account) public pure returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_IDENTITY_TREE_KEY, account));
    }

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

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

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

    /// @dev The enabled chain references `chainSource` knows, or none if it is
    ///      unset. Read through the narrow interface so this contract need not
    ///      import the registry that imports it.
    function _enabledChainRefs() private view returns (bytes32[] memory) {
        address source = chainSource;
        if (source == address(0)) return new bytes32[](0);
        return IChainSource(source).enabledChainRefs();
    }

    function _assertTree(uint8 treeId) private pure {
        if (treeId == 0 || treeId > TREE_COUNT) revert UnknownTree(treeId);
    }

    function _assertBranch(uint8 branch) private pure {
        if (branch >= BRANCH_COUNT) revert UnknownBranch(branch);
    }

    /// @dev A branch a quorum or a writer may write: any but the config branch.
    function _assertDataBranch(uint8 treeId, uint8 branch) private pure {
        _assertBranch(branch);
        if (branch == BRANCH_CONFIG) revert ConfigBranchReserved(treeId);
    }

    /// @dev Version + event, the tail of every write door.
    function _bump(uint8 treeId, uint256 count) private {
        uint64 v = treeVersion[treeId] + 1;
        treeVersion[treeId] = v;
        emit LeavesSet(treeId, count, liveRoot[treeId], v);
    }

    /// @dev `keccak256(0x01 ‖ lo ‖ hi)`, the pair sorted — the one node hash.
    function _pair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        (bytes32 lo, bytes32 hi) = a < b ? (a, b) : (b, a);
        return keccak256(abi.encodePacked(bytes1(0x01), lo, hi));
    }

    /// @dev The sibling path from a slot up `height` levels.
    function _path(uint8 treeId, uint256 idx, uint256 height) private view returns (bytes32[] memory path) {
        path = new bytes32[](height);
        for (uint256 l = 0; l < height; l++) {
            path[l] = _nodeAt(treeId, l, idx ^ 1);
            idx >>= 1;
        }
    }

    /// @dev The forest's leaves: the tree roots at their positions, the
    ///      empty tree at the rest.
    function _forestLeaves(bytes32[TREE_COUNT + 1] memory roots) private view returns (bytes32[] memory level) {
        level = new bytes32[](1 << FOREST_BITS);
        for (uint256 p = 0; p < level.length; p++) {
            level[p] = (p >= 1 && p <= TREE_COUNT) ? roots[p] : _zero[DEPTH];
        }
    }

    /// @dev Fold a power-of-two level to its root, in place.
    function _foldForest(bytes32[] memory level) private pure returns (bytes32) {
        for (uint256 n = level.length; n > 1; n >>= 1) {
            for (uint256 i = 0; i < n / 2; i++) {
                level[i] = _pair(level[2 * i], level[2 * i + 1]);
            }
        }
        return level[0];
    }

    function _set(uint8 treeId, uint8 branch, bytes32 key, bytes32 leaf) private {
        uint256 s = _slotPlusOne[treeId][key];
        uint256 idx;
        if (s == 0) {
            uint256 used = _branchSlotsUsed[treeId][branch];
            if (used >= BRANCH_CAPACITY) revert BranchFull(treeId, branch);
            idx = (uint256(branch) << BRANCH_DEPTH) | used;
            _branchSlotsUsed[treeId][branch] = used + 1;
            slotsUsed[treeId] += 1;
            _slotPlusOne[treeId][key] = idx + 1;
            _keyAt[treeId][idx] = key;
        } else {
            idx = s - 1;
            uint8 have = uint8(idx >> BRANCH_DEPTH);
            if (have != branch) revert BranchMismatch(treeId, key, have, branch);
        }

        _leaf[treeId][idx] = leaf;

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

    /// @dev Level 0 is derived from the leaf store rather than duplicated into
    /// `_node`, so there is one place a leaf lives and no way for the two to
    /// disagree. Unset positions fall through to the empty-subtree hash.
    function _nodeAt(uint8 treeId, uint256 level, uint256 index) private view returns (bytes32) {
        if (level == 0) {
            return keccak256(abi.encodePacked(bytes1(0x00), _leaf[treeId][index]));
        }
        bytes32 v = _node[treeId][level][index];
        return v == bytes32(0) ? _zero[level] : v;
    }
}

abi

[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "registry_",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      },
      {
        "name": "trees_",
        "type": "address",
        "internalType": "contract FinalStateTrees"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "ACTION_SEED_SEQUENCE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SET_BOND_POLICY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "ACTION_SET_CONSUMER",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "BRANCH_MAIN_ID",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "CONFIG_SCHEDULE_LEAD_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DEFAULT_SCHEDULE_LEAD_MS",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_INTENT_APPROVE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_INTENT_CANCEL",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_INTENT_KEY",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "DOMAIN_INTENT_STATUS_LEAF",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "EXECUTION_WINDOW",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "FLAG_AUTO_APPROVE",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "HEADER_VERSION",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "HEADER_VERSION_MAX",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "INTENT_SLOT_RING",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "MAX_SCHEDULE_HORIZON",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "STATUS_APPROVED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "STATUS_CANCELLED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "STATUS_CONSUMED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "STATUS_POSTED",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "TREE_INTENTS_ID",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "approve",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "publicKey",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "signature",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "executorSection",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "bondForfeitTo",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "bondOf",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "payer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint88",
        "internalType": "uint88"
      },
      {
        "name": "settled",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "bondWei",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "cancel",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "publicKey",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "signature",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "claimBond",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "consume",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "consumer",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "envelopeOf",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "header",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "ciphertext",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "executorSection",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "forfeitBond",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "intentSlotKey",
    "inputs": [
      {
        "name": "seq",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "intentStatusLeaf",
    "inputs": [
      {
        "name": "intentLeaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "status",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "postedAt",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "executeNotBefore",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "deadline",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "targetChainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "bodyCommitment",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "seq",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "type": "function",
    "name": "isConsumable",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "isOpen",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "leafAt",
    "inputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "post",
    "inputs": [
      {
        "name": "header",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "ciphertext",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "payable"
  },
  {
    "type": "function",
    "name": "postSeq",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "postedOf",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "postedAt",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "executeNotBefore",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "deadline",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "consumedAt",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "bodyCommitment",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "approvalKeyCommit",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "targetChainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "approvedAt",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "cancelledAt",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "seq",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "flags",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "registry",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalIdentityRegistry"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "scheduleLeadMsOf",
    "inputs": [
      {
        "name": "targetChainRef",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "function",
    "name": "seedSequence",
    "inputs": [
      {
        "name": "postSeq_",
        "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": "setBondPolicy",
    "inputs": [
      {
        "name": "newBondWei",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "forfeitTo",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "setConsumer",
    "inputs": [
      {
        "name": "newConsumer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "anchorBlock",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "approvals",
        "type": "tuple[]",
        "internalType": "struct FinalPqQuorum.Approval[]",
        "components": [
          {
            "name": "signer",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "algorithm",
            "type": "uint8",
            "internalType": "uint8"
          },
          {
            "name": "signature",
            "type": "bytes",
            "internalType": "bytes"
          },
          {
            "name": "seal",
            "type": "bytes",
            "internalType": "bytes"
          }
        ]
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "function",
    "name": "trees",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract FinalStateTrees"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "event",
    "name": "BondForfeited",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "payer",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "BondPolicySet",
    "inputs": [
      {
        "name": "bondWei",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "forfeitTo",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "BondPosted",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "payer",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "BondRefunded",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "payer",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "ConsumerSet",
    "inputs": [
      {
        "name": "consumer",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "IntentApproved",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "executorSectionBytes",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "IntentCancelled",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "IntentConsumed",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "by",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "IntentPosted",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "targetChainRef",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "executeNotBefore",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "deadline",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "flags",
        "type": "uint8",
        "indexed": false,
        "internalType": "uint8"
      },
      {
        "name": "headerBytes",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "ciphertextBytes",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "event",
    "name": "SequenceSeeded",
    "inputs": [
      {
        "name": "postSeq",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "type": "error",
    "name": "AlreadyApproved",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "at",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "AlreadyConsumed",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "at",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "AlreadyPosted",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "ApprovalKeyCommitRequired",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ApprovalSignatureInvalid",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "AutoApproveTakesNoCommitment",
    "inputs": [
      {
        "name": "approvalKeyCommit",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "BondAlreadySettled",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "BondMismatch",
    "inputs": [
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "supplied",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "BondNeedsAForfeitDestination",
    "inputs": []
  },
  {
    "type": "error",
    "name": "BondNotForfeitable",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "deadline",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "BondNotRefundable",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "BondTransferFailed",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "ConsumerAlreadySet",
    "inputs": [
      {
        "name": "current",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "ConsumerUnset",
    "inputs": []
  },
  {
    "type": "error",
    "name": "DeadlinePassed",
    "inputs": [
      {
        "name": "deadline",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "nowMs",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "DeadlineTooFar",
    "inputs": [
      {
        "name": "deadline",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "limit",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "Expired",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "deadline",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "HeaderTooShort",
    "inputs": [
      {
        "name": "length",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "type": "error",
    "name": "IntentIsCancelled",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "at",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "NoApprovalKey",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "NoBond",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotApproved",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotAuthorized",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotConsumer",
    "inputs": [
      {
        "name": "caller",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "NotFresh",
    "inputs": []
  },
  {
    "type": "error",
    "name": "NotPosted",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "PrecompileUnavailable",
    "inputs": [
      {
        "name": "precompile",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "type": "error",
    "name": "ScheduleTooFar",
    "inputs": [
      {
        "name": "executeNotBefore",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "limit",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "TooEarly",
    "inputs": [
      {
        "name": "leaf",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "executeNotBefore",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "nowMs",
        "type": "uint64",
        "internalType": "uint64"
      }
    ]
  },
  {
    "type": "error",
    "name": "UnsupportedFlags",
    "inputs": [
      {
        "name": "flags",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "UnsupportedHeaderVersion",
    "inputs": [
      {
        "name": "version",
        "type": "uint8",
        "internalType": "uint8"
      }
    ]
  },
  {
    "type": "error",
    "name": "WrongApprovalKey",
    "inputs": [
      {
        "name": "expected",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "got",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "type": "error",
    "name": "ZeroConsumer",
    "inputs": []
  },
  {
    "type": "error",
    "name": "ZeroLeaf",
    "inputs": []
  }
]

read contract

bytecode · 12,995 bytes

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

disassembly (first 4,000 ops)

pcopoperand
0000PUSH10x80
0002PUSH10x40
0004MSTORE
0005PUSH10x04
0007CALLDATASIZE
0008LT
0009ISZERO
000aPUSH20x0011
000dJUMPI
000ePUSH0
000fDUP1
0010REVERT
0011JUMPDEST
0012PUSH0
0013PUSH0
0014CALLDATALOAD
0015PUSH10xe0
0017SHR
0018DUP1
0019PUSH40x0618c724
001eEQ
001fPUSH20x23b7
0022JUMPI
0023DUP1
0024PUSH40x072e6d6d
0029EQ
002aPUSH20x074b
002dJUMPI
002eDUP1
002fPUSH40x0fb585ba
0034EQ
0035PUSH20x2368
0038JUMPI
0039DUP1
003aPUSH40x116c1af0
003fEQ
0040PUSH20x22a0
0043JUMPI
0044DUP1
0045PUSH40x178bcc93
004aEQ
004bPUSH20x225c
004eJUMPI
004fDUP1
0050PUSH40x2972cd99
0055EQ
0056PUSH20x1e77
0059JUMPI
005aDUP1
005bPUSH40x30a0d08f
0060EQ
0061PUSH20x1d92
0064JUMPI
0065DUP1
0066PUSH40x352e4660
006bEQ
006cPUSH20x1d12
006fJUMPI
0070DUP1
0071PUSH40x38fcba77
0076EQ
0077PUSH20x0dde
007aJUMPI
007bDUP1
007cPUSH40x3e65164a
0081EQ
0082PUSH20x1cf2
0085JUMPI
0086DUP1
0087PUSH40x480e7ffa
008cEQ
008dPUSH20x1cb7
0090JUMPI
0091DUP1
0092PUSH40x4f26b64b
0097EQ
0098PUSH20x1c7c
009bJUMPI
009cDUP1
009dPUSH40x4f65a32e
00a2EQ
00a3PUSH20x1c41
00a6JUMPI
00a7DUP1
00a8PUSH40x51c043a7
00adEQ
00aePUSH20x1af4
00b1JUMPI
00b2DUP1
00b3PUSH40x581582c7
00b8EQ
00b9PUSH20x1ad6
00bcJUMPI
00bdDUP1
00bePUSH40x5a4e5a15
00c3EQ
00c4PUSH20x1aba
00c7JUMPI
00c8DUP1
00c9PUSH40x5c0b2330
00ceEQ
00cfPUSH20x1a3e
00d2JUMPI
00d3DUP1
00d4PUSH40x6ee7ab1f
00d9EQ
00daPUSH20x0de3
00ddJUMPI
00deDUP1
00dfPUSH40x72efd405
00e4EQ
00e5PUSH20x0efa
00e8JUMPI
00e9DUP1
00eaPUSH40x7b103999
00efEQ
00f0PUSH20x0eb5
00f3JUMPI
00f4DUP1
00f5PUSH40x7b8ec02b
00faEQ
00fbPUSH20x0e7a
00feJUMPI
00ffDUP1
0100PUSH40x7c35be7a
0105EQ
0106PUSH20x0de8
0109JUMPI
010aDUP1
010bPUSH40x7cb33ac0
0110EQ
0111PUSH20x0de3
0114JUMPI
0115DUP1
0116PUSH40x9b76f6bb
011bEQ
011cPUSH20x0dde
011fJUMPI
0120DUP1
0121PUSH40x9ca3efea
0126EQ
0127PUSH20x0cda
012aJUMPI
012bDUP1
012cPUSH40xaf6f8c1b
0131EQ
0132PUSH20x09d6
0135JUMPI
0136DUP1
0137PUSH40xb0843d5d
013cEQ
013dPUSH20x0779
0140JUMPI
0141DUP1
0142PUSH40xb4fd7296
0147EQ
0148PUSH20x0750
014bJUMPI
014cDUP1
014dPUSH40xb996de66
0152EQ
0153PUSH20x074b
0156JUMPI
0157DUP1
0158PUSH40xb9983677
015dEQ
015ePUSH20x0724
0161JUMPI
0162DUP1
0163PUSH40xbecec190
0168EQ
0169PUSH20x0704
016cJUMPI
016dDUP1
016ePUSH40xc21a3fa2
0173EQ
0174PUSH20x05ca
0177JUMPI
0178DUP1
0179PUSH40xd085c61a
017eEQ
017fPUSH20x0592
0182JUMPI
0183DUP1
0184PUSH40xd11b2015
0189EQ
018aPUSH20x0566
018dJUMPI
018eDUP1
018fPUSH40xd2a26fb0
0194EQ
0195PUSH20x052b
0198JUMPI
0199DUP1
019aPUSH40xd61ec1cd
019fEQ
01a0PUSH20x050f
01a3JUMPI
01a4DUP1
01a5PUSH40xdd759453
01aaEQ
01abPUSH20x04d4
01aeJUMPI
01afDUP1
01b0PUSH40xe01696b1
01b5EQ
01b6PUSH20x04b5
01b9JUMPI
01baDUP1
01bbPUSH40xed8da001
01c0EQ
01c1PUSH20x047a
01c4JUMPI
01c5DUP1
01c6PUSH40xf5760fd1
01cbEQ
01ccPUSH20x045a
01cfJUMPI
01d0DUP1
01d1PUSH40xf63c7bfb
01d6EQ
01d7PUSH20x042a
01daJUMPI
01dbDUP1
01dcPUSH40xf68365b0
01e1EQ
01e2PUSH20x0401
01e5JUMPI
01e6DUP1
01e7PUSH40xff08fc00
01ecEQ
01edPUSH20x032b
01f0JUMPI
01f1DUP1
01f2PUSH40xff582a04
01f7EQ
01f8PUSH20x02f0
01fbJUMPI
01fcPUSH40xffc7733c
0201EQ
0202PUSH20x0209
0205JUMPI
0206PUSH0
0207DUP1
0208REVERT
0209JUMPDEST
020aCALLVALUE
020bPUSH20x02ed
020eJUMPI
020fPUSH10x20
0211CALLDATASIZE
0212PUSH10x03
0214NOT
0215ADD
0216SLT
0217PUSH20x02ed
021aJUMPI
021bPUSH10x04
021dCALLDATALOAD
021eSWAP1
021fDUP2
0220DUP2
0221MSTORE
0222PUSH10x04
0224PUSH10x20
0226MSTORE
0227PUSH10x40
0229DUP2
022aKECCAK256
022bSWAP2
022cDUP3
022dSLOAD
022ePUSH10x01
0230DUP1
0231PUSH10xa0
0233SHL
0234SUB
0235DUP2
0236AND
0237SWAP1
0238DUP2
0239ISZERO
023aPUSH20x02d9
023dJUMPI
023eDUP1
023fPUSH10xf8
0241SHR
0242PUSH20x02c5
0245JUMPI
0246DUP3
0247DUP5
0248MSTORE
0249DUP4
024aPUSH10x20
024cMSTORE
024dPUSH10x40
024fDUP5
0250KECCAK256
0251SLOAD
0252PUSH10xc0
0254SHR
0255ISZERO
0256PUSH20x02b1
0259JUMPI
025aPUSH10x01
025cPUSH10x01
025ePUSH10x58
0260SHL
0261SUB
0262PUSH20x02ae
0265SWAP5
0266SWAP6
0267DUP4
0268SWAP3
0269PUSH10x01
026bPUSH10xf8
026dSHL
026eSWAP1
026fPUSH10x01
0271DUP1
0272PUSH10xf8
0274SHL
0275SUB
0276AND
0277OR
0278DUP1
0279SWAP2
027aSSTORE
027bPUSH10xa0
027dSHR
027eAND
027fSWAP3
0280PUSH320x7514b10bc04eb31bd8aa5b8639cb6c21e464040da82dd4e84534cd91f49ac675
02a1PUSH10x20
02a3PUSH10x40
02a5MLOAD
02a6DUP7
02a7DUP2
02a8MSTORE
02a9LOG3
02aaPUSH20x319b
02adJUMP
02aeJUMPDEST
02afDUP1
02b0RETURN
02b1JUMPDEST
02b2PUSH40x33cb2a17
02b7PUSH10xe2
02b9SHL
02baDUP5
02bbMSTORE
02bcPUSH10x04
02beDUP4
02bfSWAP1
02c0MSTORE
02c1PUSH10x24
02c3DUP5
02c4REVERT
02c5JUMPDEST
02c6PUSH40xf920a8ad
02cbPUSH10xe0
02cdSHL
02ceDUP5
02cfMSTORE
02d0PUSH10x04
02d2DUP4
02d3SWAP1
02d4MSTORE
02d5PUSH10x24
02d7DUP5
02d8REVERT
02d9JUMPDEST
02daPUSH40x03c231f1
02dfPUSH10xe2
02e1SHL
02e2DUP5
02e3MSTORE
02e4PUSH10x04
02e6DUP4
02e7SWAP1
02e8MSTORE
02e9PUSH10x24
02ebDUP5
02ecREVERT
02edJUMPDEST
02eeDUP1
02efREVERT
02f0JUMPDEST
02f1POP
02f2CALLVALUE
02f3PUSH20x02ed
02f6JUMPI
02f7DUP1
02f8PUSH10x03
02faNOT
02fbCALLDATASIZE
02fcADD
02fdSLT
02fePUSH20x02ed
0301JUMPI
0302PUSH10x20
0304PUSH10x40
0306MLOAD
0307PUSH320x39efb8a3ddb9a6b5fbbc1efde87859ad508e4acf5b6c2a87ce3a298ccc3c5a00
0328DUP2
0329MSTORE
032aRETURN
032bJUMPDEST
032cPOP
032dCALLVALUE
032ePUSH20x02ed
0331JUMPI
0332PUSH10x60
0334CALLDATASIZE
0335PUSH10x03
0337NOT
0338ADD
0339SLT
033aPUSH20x02ed
033dJUMPI
033ePUSH20x0345
0341PUSH20x245a
0344JUMP
0345JUMPDEST
0346PUSH20x034d
0349PUSH20x242e
034cJUMP
034dJUMPDEST
034ePUSH10x44
0350CALLDATALOAD
0351SWAP1
0352PUSH10x01
0354PUSH10x01
0356PUSH10x40
0358SHL
0359SUB
035aDUP3
035bGT
035cPUSH20x03fd
035fJUMPI
0360PUSH20x0370
0363PUSH20x039e
0366SWAP3
0367CALLDATASIZE
0368SWAP1
0369PUSH10x04
036bADD
036cPUSH20x2470
036fJUMP
0370JUMPDEST
0371SWAP2
0372PUSH10x40
0374MLOAD
0375SWAP5
0376PUSH10x01
0378PUSH10x01
037aPUSH10x40
037cSHL
037dSUB
037ePUSH10x20
0380DUP8
0381ADD
0382SWAP2
0383AND
0384SWAP6
0385DUP7
0386DUP3
0387MSTORE
0388PUSH10x20
038aDUP2
038bMSTORE
038cPUSH20x0396
038fPUSH10x40
0391DUP3
0392PUSH20x2508
0395JUMP
0396JUMPDEST
0397MLOAD
0398SWAP1
0399KECCAK256
039aPUSH20x2f6d
039dJUMP
039eJUMPDEST
039fPUSH10x02
03a1SLOAD
03a2PUSH10x01
03a4PUSH10x01
03a6PUSH10x40
03a8SHL
03a9SUB
03aaDUP2
03abAND
03acPUSH20x03ee
03afJUMPI
03b0PUSH80xffffffffffffffff
03b9NOT
03baAND
03bbDUP2
03bcOR
03bdPUSH10x02
03bfSSTORE
03c0PUSH10x40
03c2MLOAD
03c3SWAP1
03c4DUP2
03c5MSTORE
03c6PUSH320x0b2384dfec7e6ac4cabe32366a33ef1072d076ac41659321348732ffe00b3181
03e7SWAP1
03e8PUSH10x20
03eaSWAP1
03ebLOG1
03ecDUP1
03edRETURN
03eeJUMPDEST
03efPUSH40xdc63d81f
03f4PUSH10xe0
03f6SHL
03f7DUP4
03f8MSTORE
03f9PUSH10x04
03fbDUP4
03fcREVERT
03fdJUMPDEST
03feDUP4
03ffDUP1
0400REVERT
0401JUMPDEST
0402POP
0403CALLVALUE
0404PUSH20x02ed
0407JUMPI
0408DUP1
0409PUSH10x03
040bNOT
040cCALLDATASIZE
040dADD
040eSLT
040fPUSH20x02ed
0412JUMPI
0413PUSH10x06
0415SLOAD
0416PUSH10x40
0418MLOAD
0419PUSH10x01
041bPUSH10x01
041dPUSH10xa0
041fSHL
0420SUB
0421SWAP1
0422SWAP2
0423AND
0424DUP2
0425MSTORE
0426PUSH10x20
0428SWAP1
0429RETURN
042aJUMPDEST
042bPOP
042cCALLVALUE
042dPUSH20x02ed
0430JUMPI
0431PUSH10x20
0433CALLDATASIZE
0434PUSH10x03
0436NOT
0437ADD
0438SLT
0439PUSH20x02ed
043cJUMPI
043dPUSH10x20
043fPUSH20x0449
0442PUSH10x04
0444CALLDATALOAD
0445PUSH20x27e3
0448JUMP
0449JUMPDEST
044aPUSH10x01
044cPUSH10x01
044ePUSH10x40
0450SHL
0451SUB
0452PUSH10x40
0454MLOAD
0455SWAP2
0456AND
0457DUP2
0458MSTORE
0459RETURN
045aJUMPDEST
045bPOP
045cCALLVALUE
045dPUSH20x02ed
0460JUMPI
0461DUP1
0462PUSH10x03
0464NOT
0465CALLDATASIZE
0466ADD
0467SLT
0468PUSH20x02ed
046bJUMPI
046cPOP
046dPUSH10x20
046fPUSH30x100000
0473PUSH10x40
0475MLOAD
0476SWAP1
0477DUP2
0478MSTORE
0479RETURN
047aJUMPDEST
047bPOP
047cCALLVALUE
047dPUSH20x02ed
0480JUMPI
0481DUP1
0482PUSH10x03
0484NOT
0485CALLDATASIZE
0486ADD
0487SLT
0488PUSH20x02ed
048bJUMPI
048cPUSH10x20
048ePUSH10x40
0490MLOAD
0491PUSH320x2efb98c58c1606c2b7524fdfbd55ecb71e0ebbedaef85fb5ceca0b8f4e66b0d9
04b2DUP2
04b3MSTORE
04b4RETURN
04b5JUMPDEST
04b6POP
04b7CALLVALUE
04b8PUSH20x02ed
04bbJUMPI
04bcDUP1
04bdPUSH10x03
04bfNOT
04c0CALLDATASIZE
04c1ADD
04c2SLT
04c3PUSH20x02ed
04c6JUMPI
04c7PUSH10x40
04c9MLOAD
04caPUSH30x015f90
04ceDUP2
04cfMSTORE
04d0PUSH10x20
04d2SWAP1
04d3RETURN
04d4JUMPDEST
04d5POP
04d6CALLVALUE
04d7PUSH20x02ed
04daJUMPI
04dbDUP1
04dcPUSH10x03
04deNOT
04dfCALLDATASIZE
04e0ADD
04e1SLT
04e2PUSH20x02ed
04e5JUMPI
04e6PUSH10x20
04e8PUSH10x40
04eaMLOAD
04ebPUSH320x25d67559fc7236429288de0997218a361d6df4de81f842df2e5db075e8643eaa
050cDUP2
050dMSTORE
050eRETURN
050fJUMPDEST
0510POP
0511CALLVALUE
0512PUSH20x02ed
0515JUMPI
0516DUP1
0517PUSH10x03
0519NOT
051aCALLDATASIZE
051bADD
051cSLT
051dPUSH20x02ed
0520JUMPI
0521PUSH10x20
0523PUSH10x40
0525MLOAD
0526PUSH10x07
0528DUP2
0529MSTORE
052aRETURN
052bJUMPDEST
052cPOP
052dCALLVALUE
052ePUSH20x02ed
0531JUMPI
0532DUP1
0533PUSH10x03
0535NOT
0536CALLDATASIZE
0537ADD
0538SLT
0539PUSH20x02ed
053cJUMPI
053dPUSH10x20
053fPUSH10x40
0541MLOAD
0542PUSH320x8613751e8e5a860774fbee33f44b7afcd801513ac5715713f0d65d5fe9355465
0563DUP2
0564MSTORE
0565RETURN
0566JUMPDEST
0567POP
0568CALLVALUE
0569PUSH20x02ed
056cJUMPI
056dPUSH10x20
056fCALLDATASIZE
0570PUSH10x03
0572NOT
0573ADD
0574SLT
0575PUSH20x02ed
0578JUMPI
0579PUSH10x20
057bPUSH20x058a
057ePUSH20x0585
0581PUSH20x245a
0584JUMP
0585JUMPDEST
0586PUSH20x2792
0589JUMP
058aJUMPDEST
058bPUSH10x40
058dMLOAD
058eSWAP1
058fDUP2
0590MSTORE
0591RETURN
0592JUMPDEST
0593POP
0594CALLVALUE
0595PUSH20x02ed
0598JUMPI
0599PUSH10x20
059bCALLDATASIZE
059cPUSH10x03
059eNOT
059fADD
05a0SLT
05a1PUSH20x02ed
05a4JUMPI
05a5PUSH10x40
05a7PUSH10x20
05a9SWAP2
05aaPUSH10x01
05acPUSH10x01
05aePUSH10x40
05b0SHL
05b1SUB
05b2PUSH20x05b9
05b5PUSH20x245a
05b8JUMP
05b9JUMPDEST
05baAND
05bbDUP2
05bcMSTORE
05bdPUSH10x03
05bfDUP4
05c0MSTORE
05c1KECCAK256
05c2SLOAD
05c3PUSH10x40
05c5MLOAD
05c6SWAP1
05c7DUP2
05c8MSTORE
05c9RETURN
05caJUMPDEST
05cbPOP
05ccCALLVALUE
05cdPUSH20x02ed
05d0JUMPI
05d1PUSH10x80
05d3CALLDATASIZE
05d4PUSH10x03
05d6NOT
05d7ADD
05d8SLT
05d9PUSH20x02ed
05dcJUMPI
05ddPUSH10x04
05dfCALLDATALOAD
05e0SWAP1
05e1PUSH10x24
05e3CALLDATALOAD
05e4PUSH10x01
05e6PUSH10x01
05e8PUSH10xa0
05eaSHL
05ebSUB
05ecDUP2
05edAND
05eeDUP1
05efDUP3
05f0SUB
05f1PUSH20x0700
05f4JUMPI
05f5PUSH20x05fc
05f8PUSH20x2444
05fbJUMP
05fcJUMPDEST
05fdPUSH10x64
05ffCALLDATALOAD
0600PUSH10x01
0602PUSH10x01
0604PUSH10x40
0606SHL
0607SUB
0608DUP2
0609GT
060aPUSH20x06fc
060dJUMPI
060eSWAP1
060fPUSH20x061f
0612PUSH20x0651
0615SWAP3
0616CALLDATASIZE
0617SWAP1
0618PUSH10x04
061aADD
061bPUSH20x2470
061eJUMP
061fJUMPDEST
0620PUSH10x40
0622DUP1
0623MLOAD
0624PUSH10x20
0626DUP2
0627ADD
0628DUP11
0629DUP2
062aMSTORE
062bPUSH10x01
062dPUSH10x01
062fPUSH10xa0
0631SHL
0632SUB
0633DUP10
0634AND
0635DUP3
0636DUP5
0637ADD
0638MSTORE
0639SWAP2
063aDUP2
063bMSTORE
063cSWAP2
063dSWAP4
063eSWAP2
063fPUSH20x0649
0642PUSH10x60
0644DUP3
0645PUSH20x2508
0648JUMP
0649JUMPDEST
064aMLOAD
064bSWAP1
064cKECCAK256
064dPUSH20x2d6e
0650JUMP
0651JUMPDEST
0652DUP4
0653ISZERO
0654ISZERO
0655DUP1
0656PUSH20x06f4
0659JUMPI
065aJUMPDEST
065bPUSH20x06e5
065eJUMPI
065fPUSH10x01
0661PUSH10x01
0663PUSH10x58
0665SHL
0666SUB
0667DUP5
0668GT
0669PUSH20x06c6
066cJUMPI
066dPUSH10x05
066fDUP5
0670SWAP1
0671SSTORE
0672PUSH10x06
0674DUP1
0675SLOAD
0676PUSH10x01
0678PUSH10x01
067aPUSH10xa0
067cSHL
067dSUB
067eNOT
067fAND
0680SWAP2
0681SWAP1
0682SWAP2
0683OR
0684SWAP1
0685SSTORE
0686PUSH10x40
0688DUP1
0689MLOAD
068aSWAP4
068bDUP5
068cMSTORE
068dPUSH10x01
068fPUSH10x01
0691PUSH10xa0
0693SHL
0694SUB
0695SWAP2
0696SWAP1
0697SWAP2
0698AND
0699PUSH10x20
069bDUP5
069cADD
069dMSTORE
069eSWAP1
069fSWAP2
06a0PUSH320xcb31391f189faeae4cf8cec8849f690db565c748ce3da5987fd102f1bdeda60b
06c1SWAP2
06c2SWAP1
06c3LOG1
06c4DUP1
06c5RETURN
06c6JUMPDEST
06c7PUSH40x4ba85a6b
06ccPUSH10xe1
06ceSHL
06cfDUP4
06d0MSTORE
06d1PUSH10x01
06d3PUSH10x01
06d5PUSH10x58
06d7SHL
06d8SUB
06d9PUSH10x04
06dbMSTORE
06dcPUSH10x24
06deDUP5
06dfSWAP1
06e0MSTORE
06e1PUSH10x44
06e3DUP4
06e4REVERT
06e5JUMPDEST
06e6PUSH40x18f99d43
06ebPUSH10xe3
06edSHL
06eeDUP4
06efMSTORE
06f0PUSH10x04
06f2DUP4
06f3REVERT
06f4JUMPDEST
06f5POP
06f6DUP1
06f7ISZERO
06f8PUSH20x065a
06fbJUMP
06fcJUMPDEST
06fdDUP5
06feDUP1
06ffREVERT
0700JUMPDEST
0701DUP3
0702DUP1
0703REVERT
0704JUMPDEST
0705POP
0706CALLVALUE
0707PUSH20x02ed
070aJUMPI
070bDUP1
070cPUSH10x03
070eNOT
070fCALLDATASIZE
0710ADD
0711SLT
0712PUSH20x02ed
0715JUMPI
0716PUSH10x40
0718MLOAD
0719PUSH40x9a7ec800
071eDUP2
071fMSTORE
0720PUSH10x20
0722SWAP1
0723RETURN
0724JUMPDEST
0725POP
0726CALLVALUE
0727PUSH20x02ed
072aJUMPI
072bDUP1
072cPUSH10x03
072eNOT
072fCALLDATASIZE
0730ADD
0731SLT
0732PUSH20x02ed
0735JUMPI
0736PUSH10x20
0738PUSH10x01
073aPUSH10x01
073cPUSH10x40
073eSHL
073fSUB
0740PUSH10x02
0742SLOAD
0743AND
0744PUSH10x40
0746MLOAD
0747SWAP1
0748DUP2
0749MSTORE
074aRETURN
074bJUMPDEST
074cPUSH20x23d2
074fJUMP
0750JUMPDEST
0751POP
0752CALLVALUE
0753PUSH20x02ed
0756JUMPI
0757DUP1
0758PUSH10x03
075aNOT
075bCALLDATASIZE
075cADD
075dSLT
075ePUSH20x02ed
0761JUMPI
0762PUSH10x07
0764SLOAD
0765PUSH10x40
0767MLOAD
0768PUSH10x01
076aPUSH10x01
076cPUSH10xa0
076eSHL
076fSUB
0770SWAP1
0771SWAP2
0772AND
0773DUP2
0774MSTORE
0775PUSH10x20
0777SWAP1
0778RETURN
0779JUMPDEST
077aPOP
077bCALLVALUE
077cPUSH20x02ed
077fJUMPI
0780PUSH10x60
0782CALLDATASIZE
0783PUSH10x03
0785NOT
0786ADD
0787SLT
0788PUSH20x02ed
078bJUMPI
078cPUSH10x04
078eCALLDATALOAD
078fDUP2
0790PUSH10x24
0792CALLDATALOAD
0793PUSH10x01
0795PUSH10x01
0797PUSH10x40
0799SHL
079aSUB
079bDUP2
079cGT
079dPUSH20x09c9
07a0JUMPI
07a1PUSH20x07ae
07a4SWAP1
07a5CALLDATASIZE
07a6SWAP1
07a7PUSH10x04
07a9ADD
07aaPUSH20x23ed
07adJUMP
07aeJUMPDEST
07afSWAP1
07b0PUSH10x44
07b2CALLDATALOAD
07b3PUSH10x01
07b5PUSH10x01
07b7PUSH10x40
07b9SHL
07baSUB
07bbDUP2
07bcGT
07bdPUSH20x03fd
07c0JUMPI
07c1PUSH20x0834
07c4SWAP2
07c5PUSH20x07d3
07c8DUP7
07c9SWAP3
07caCALLDATASIZE
07cbSWAP1
07ccPUSH10x04
07ceADD
07cfPUSH20x23ed
07d2JUMP
07d3JUMPDEST
07d4SWAP1
07d5PUSH20x07dd
07d8DUP5
07d9PUSH20x293e
07dcJUMP
07ddJUMPDEST
07deSWAP6
07dfPUSH10x40
07e1MLOAD
07e2PUSH10x20
07e4DUP2
07e5ADD
07e6SWAP1
07e7PUSH320xcc7c08481f764947d6a6fa10f1913f9c489bcbe1ea586c5d4aca3bb0d12f45f6
0808DUP3
0809MSTORE
080aCHAINID
080bPUSH10x40
080dDUP3
080eADD
080fMSTORE
0810ADDRESS
0811PUSH10x60
0813DUP3
0814ADD
0815MSTORE
0816DUP7
0817PUSH10x80
0819DUP3
081aADD
081bMSTORE
081cPUSH10x80
081eDUP2
081fMSTORE
0820PUSH20x082a
0823PUSH10xa0
0825DUP3
0826PUSH20x2508
0829JUMP
082aJUMPDEST
082bMLOAD
082cSWAP1
082dKECCAK256
082eSWAP4
082fDUP8
0830PUSH20x29be
0833JUMP
0834JUMPDEST
0835PUSH10x04
0837DUP2
0838ADD
0839DUP1
083aSLOAD
083bPUSH160xffffffffffffffff0000000000000000
084cNOT
084dAND
084eTIMESTAMP
084fPUSH10x40
0851SHL
0852PUSH80xffffffffffffffff
085bPUSH10x40
085dSHL
085eAND
085fOR
0860DUP2
0861SSTORE
0862SWAP1
0863PUSH10x40
0865SWAP1
0866PUSH20x08f1
0869DUP3
086aMLOAD
086bSWAP2
086cPUSH20x0875
086fDUP5
0870DUP5
0871PUSH20x2508
0874JUMP
0875JUMPDEST
0876PUSH10x01
0878DUP4
0879MSTORE
087aPUSH10x01
087cPUSH10x01
087ePUSH10x40
0880SHL
0881SUB
0882PUSH10x1f
0884NOT
0885DUP6
0886ADD
0887SWAP6
0888DUP7
0889CALLDATASIZE
088aPUSH10x20
088cDUP8
088dADD
088eCALLDATACOPY
088fDUP6
0890MLOAD
0891SWAP7
0892PUSH20x089b
0895DUP8
0896DUP10
0897PUSH20x2508
089aJUMP
089bJUMPDEST
089cPUSH10x01
089eDUP9
089fMSTORE
08a0CALLDATASIZE
08a1PUSH10x20
08a3DUP10
08a4ADD
08a5CALLDATACOPY
08a6SLOAD
08a7PUSH10x80
08a9SHR
08aaAND
08abSWAP1
08acPUSH20x08b4
08afDUP3
08b0PUSH20x2792
08b3JUMP
08b4JUMPDEST
08b5PUSH20x08bd
08b8DUP6
08b9PUSH20x2a67
08bcJUMP
08bdJUMPDEST
08beMSTORE
08bfDUP1
08c0SLOAD
08c1SWAP1
08c2PUSH10x01
08c4PUSH10x03
08c6DUP3
08c7ADD
08c8SLOAD
08c9SWAP2
08caADD
08cbSLOAD
08ccSWAP2
08cdPUSH10x01
08cfPUSH10x01
08d1PUSH10x40
08d3SHL
08d4SUB
08d5DUP2
08d6PUSH10x80
08d8SHR
08d9AND
08daSWAP1
08dbPUSH10x01
08ddPUSH10x01
08dfPUSH10x40
08e1SHL
08e2SUB
08e3DUP1
08e4DUP3
08e5DUP11
08e6SHR
08e7AND
08e8SWAP2
08e9AND
08eaPUSH10x04
08ecDUP13
08edPUSH20x26b0
08f0JUMP
08f1JUMPDEST
08f2PUSH20x08fa
08f5DUP5
08f6PUSH20x2a67
08f9JUMP
08faJUMPDEST
08fbMSTORE
08fcPUSH320x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8
091dPUSH10x01
091fPUSH10x01
0921PUSH10xa0
0923SHL
0924SUB
0925AND
0926SWAP3
0927DUP4
0928EXTCODESIZE
0929ISZERO
092aPUSH20x06fc
092dJUMPI
092ePUSH20x097b
0931SWAP4
0932PUSH20x0969
0935DUP7
0936DUP1
0937SWAP5
0938DUP7
0939MLOAD
093aSWAP8
093bDUP9
093cSWAP6
093dDUP7
093eSWAP5
093fDUP6
0940SWAP4
0941PUSH40xabf1570d
0946PUSH10xe0
0948SHL
0949DUP6
094aMSTORE
094bPUSH10x07
094dPUSH10x04
094fDUP7
0950ADD
0951MSTORE
0952PUSH10x01
0954PUSH10x24
0956DUP7
0957ADD
0958MSTORE
0959PUSH10x80
095bPUSH10x44
095dDUP7
095eADD
095fMSTORE
0960PUSH10x84
0962DUP6
0963ADD
0964SWAP1
0965PUSH20x2a88
0968JUMP
0969JUMPDEST
096aDUP4
096bDUP2
096cSUB
096dPUSH10x03
096fNOT
0970ADD
0971PUSH10x64
0973DUP6
0974ADD
0975MSTORE
0976SWAP1
0977PUSH20x2a88
097aJUMP
097bJUMPDEST
097cSUB
097dSWAP3
097eGAS
097fCALL
0980SWAP1
0981DUP2
0982ISZERO
0983PUSH20x09cd
0986JUMPI
0987POP
0988PUSH20x09b4
098bJUMPI
098cJUMPDEST
098dPOP
098eDUP1
098fPUSH320xc08eb64db16a39d2848960af04e3f16fb404d9d436a9f0e9d7d0d4854715c9dc
09b0SWAP2
09b1LOG2
09b2DUP1
09b3RETURN
09b4JUMPDEST
09b5DUP2
09b6PUSH20x09be
09b9SWAP2
09baPUSH20x2508
09bdJUMP
09beJUMPDEST
09bfPUSH20x09c9
09c2JUMPI
09c3DUP2
09c4PUSH0
09c5PUSH20x098c
09c8JUMP
09c9JUMPDEST
09caPOP
09cbDUP1
09ccREVERT
09cdJUMPDEST
09ceMLOAD
09cfRETURNDATASIZE
09d0DUP5
09d1DUP3
09d2RETURNDATACOPY
09d3RETURNDATASIZE
09d4SWAP1
09d5REVERT
09d6JUMPDEST
09d7POP
09d8CALLVALUE
09d9PUSH20x02ed
09dcJUMPI
09ddPUSH10x20
09dfCALLDATASIZE
09e0PUSH10x03
09e2NOT
09e3ADD
09e4SLT
09e5PUSH20x02ed
09e8JUMPI
09e9PUSH10x07
09ebSLOAD
09ecPUSH10x04
09eeCALLDATALOAD
09efSWAP1
09f0PUSH10x01
09f2PUSH10x01
09f4PUSH10xa0
09f6SHL
09f7SUB
09f8AND
09f9DUP1
09faISZERO
09fbPUSH20x0ccb
09feJUMPI
09ffCALLER
0a00SUB
0a01PUSH20x0cb8
0a04JUMPI
0a05DUP1
0a06DUP3
0a07MSTORE
0a08DUP2
0a09PUSH10x20
0a0bMSTORE
0a0cPUSH10x40
0a0eDUP3
0a0fKECCAK256
0a10DUP1
0a11SLOAD
0a12SWAP1
0a13PUSH10x01
0a15PUSH10x01
0a17PUSH10x40
0a19SHL
0a1aSUB
0a1bDUP3
0a1cAND
0a1dISZERO
0a1ePUSH20x0ca4
0a21JUMPI
0a22DUP2
0a23PUSH10xc0
0a25SHR
0a26DUP1
0a27PUSH20x0c8d
0a2aJUMPI
0a2bPOP
0a2cPUSH10x04
0a2eDUP2
0a2fADD
0a30SWAP2
0a31DUP3
0a32SLOAD
0a33PUSH10x01
0a35PUSH10x01
0a37PUSH10x40
0a39SHL
0a3aSUB
0a3bDUP2
0a3cPUSH10x40
0a3eSHR
0a3fAND
0a40DUP1
0a41PUSH20x0c76
0a44JUMPI
0a45POP
0a46PUSH10x01
0a48PUSH10x01
0a4aPUSH10x40
0a4cSHL
0a4dSUB
0a4eTIMESTAMP
0a4fAND
0a50SWAP2
0a51PUSH10x01
0a53PUSH10x01
0a55PUSH10x40
0a57SHL
0a58SUB
0a59DUP2
0a5aPUSH10x80
0a5cSHR
0a5dAND
0a5eDUP1
0a5fDUP5
0a60GT
0a61PUSH20x0c5f
0a64JUMPI
0a65POP
0a66PUSH10x01
0a68PUSH10x01
0a6aPUSH10x40
0a6cSHL
0a6dSUB
0a6eSWAP1
0a6fDUP2
0a70PUSH20x0a81
0a73PUSH20x0a7b
0a76DUP8
0a77PUSH20x31d2
0a7aJUMP
0a7bJUMPDEST
0a7cDUP7
0a7dPUSH20x275e
0a80JUMP
0a81JUMPDEST
0a82SWAP2
0a83PUSH10x40
0a85SHR
0a86AND
0a87SWAP2
0a88DUP3
0a89SWAP2
0a8aAND
0a8bLT
0a8cPUSH20x0c43
0a8fJUMPI
0a90POP
0a91PUSH10x01
0a93PUSH10x01
0a95PUSH10x40
0a97SHL
0a98SUB
0a99DUP2
0a9aAND
0a9bISZERO
0a9cSWAP1
0a9dDUP2
0a9ePUSH20x0c34
0aa1JUMPI
0aa2JUMPDEST
0aa3POP
0aa4PUSH20x0c20
0aa7JUMPI
0aa8DUP2
0aa9SLOAD
0aaaPUSH10x01
0aacPUSH10x01
0aaePUSH10xc0
0ab0SHL
0ab1SUB
0ab2AND
0ab3PUSH10xc0
0ab5SWAP2
0ab6SWAP1
0ab7SWAP2
0ab8SHL
0ab9PUSH10x01
0abbPUSH10x01
0abdPUSH10xc0
0abfSHL
0ac0SUB
0ac1NOT
0ac2AND
0ac3OR
0ac4DUP2
0ac5SSTORE
0ac6DUP4
0ac7SWAP2
0ac8SWAP1
0ac9PUSH10x40
0acbSWAP1
0accPUSH20x0b57
0acfDUP3
0ad0MLOAD
0ad1SWAP2
0ad2PUSH20x0adb
0ad5DUP5
0ad6DUP5
0ad7PUSH20x2508
0adaJUMP
0adbJUMPDEST
0adcPUSH10x01
0adeDUP4
0adfMSTORE
0ae0PUSH10x01
0ae2PUSH10x01
0ae4PUSH10x40
0ae6SHL
0ae7SUB
0ae8PUSH10x1f
0aeaNOT
0aebDUP6
0aecADD
0aedSWAP6
0aeeDUP7
0aefCALLDATASIZE
0af0PUSH10x20
0af2DUP8
0af3ADD
0af4CALLDATACOPY
0af5DUP6
0af6MLOAD
0af7SWAP7
0af8PUSH20x0b01
0afbDUP8
0afcDUP10
0afdPUSH20x2508
0b00JUMP
0b01JUMPDEST
0b02PUSH10x01
0b04DUP9
0b05MSTORE
0b06CALLDATASIZE
0b07PUSH10x20
0b09DUP10
0b0aADD
0b0bCALLDATACOPY
0b0cSLOAD
0b0dPUSH10x80
0b0fSHR
0b10AND
0b11SWAP1
0b12PUSH20x0b1a
0b15DUP3
0b16PUSH20x2792
0b19JUMP
0b1aJUMPDEST
0b1bPUSH20x0b23
0b1eDUP6
0b1fPUSH20x2a67
0b22JUMP
0b23JUMPDEST
0b24MSTORE
0b25DUP1
0b26SLOAD
0b27SWAP1
0b28PUSH10x01
0b2aPUSH10x03
0b2cDUP3
0b2dADD
0b2eSLOAD
0b2fSWAP2
0b30ADD
0b31SLOAD
0b32SWAP2
0b33PUSH10x01
0b35PUSH10x01
0b37PUSH10x40
0b39SHL
0b3aSUB
0b3bDUP2
0b3cPUSH10x80
0b3eSHR
0b3fAND
0b40SWAP1
0b41PUSH10x01
0b43PUSH10x01
0b45PUSH10x40
0b47SHL
0b48SUB
0b49DUP1
0b4aDUP3
0b4bDUP11
0b4cSHR
0b4dAND
0b4eSWAP2
0b4fAND
0b50PUSH10x03
0b52DUP13
0b53PUSH20x26b0
0b56JUMP
0b57JUMPDEST
0b58PUSH20x0b60
0b5bDUP5
0b5cPUSH20x2a67
0b5fJUMP
0b60JUMPDEST
0b61MSTORE
0b62PUSH320x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8
0b83PUSH10x01
0b85PUSH10x01
0b87PUSH10xa0
0b89SHL
0b8aSUB
0b8bAND
0b8cSWAP3
0b8dDUP4
0b8eEXTCODESIZE
0b8fISZERO
0b90PUSH20x06fc
0b93JUMPI
0b94PUSH20x0bcf
0b97SWAP4
0b98PUSH20x0969
0b9bDUP7
0b9cDUP1
0b9dSWAP5
0b9eDUP7
0b9fMLOAD
0ba0SWAP8
0ba1DUP9
0ba2SWAP6
0ba3DUP7
0ba4SWAP5
0ba5DUP6
0ba6SWAP4
0ba7PUSH40xabf1570d
0bacPUSH10xe0
0baeSHL
0bafDUP6
0bb0MSTORE
0bb1PUSH10x07
0bb3PUSH10x04
0bb5DUP7
0bb6ADD
0bb7MSTORE
0bb8PUSH10x01
0bbaPUSH10x24
0bbcDUP7
0bbdADD
0bbeMSTORE
0bbfPUSH10x80
0bc1PUSH10x44
0bc3DUP7
0bc4ADD
0bc5MSTORE
0bc6PUSH10x84
0bc8DUP6
0bc9ADD
0bcaSWAP1
0bcbPUSH20x2a88
0bceJUMP
0bcfJUMPDEST
0bd0SUB
0bd1SWAP3
0bd2GAS
0bd3CALL
0bd4SWAP1
0bd5DUP2
0bd6ISZERO
0bd7PUSH20x09cd
0bdaJUMPI
0bdbPOP
0bdcPUSH20x0c0b
0bdfJUMPI
0be0JUMPDEST
0be1POP
0be2POP
0be3CALLER
0be4SWAP1
0be5PUSH320x1408bd9a6e6ac1e782a8780a8d173b72dff516f8deb1cf14209f043d74d7f884
0c06DUP4
0c07DUP1
0c08LOG3
0c09DUP1
0c0aRETURN
0c0bJUMPDEST
0c0cDUP2
0c0dPUSH20x0c15
0c10SWAP2
0c11PUSH20x2508
0c14JUMP
0c15JUMPDEST
0c16PUSH20x09c9
0c19JUMPI
0c1aDUP2
0c1bPUSH0
0c1cPUSH20x0be0
0c1fJUMP
0c20JUMPDEST
0c21PUSH40xc5d89109
0c26PUSH10xe0
0c28SHL
0c29DUP6
0c2aMSTORE
0c2bPUSH10x04
0c2dDUP5
0c2eSWAP1
0c2fMSTORE
0c30PUSH10x24
0c32DUP6
0c33REVERT
0c34JUMPDEST
0c35PUSH10x01
0c37SWAP2
0c38POP
0c39PUSH10xc0
0c3bSHR
0c3cAND
0c3dISZERO
0c3ePUSH0
0c3fPUSH20x0aa2
0c42JUMP
0c43JUMPDEST
0c44PUSH40x28ae40d3
0c49PUSH10xe1
0c4bSHL
0c4cDUP8
0c4dMSTORE
0c4ePUSH10x04
0c50DUP7
0c51SWAP1
0c52MSTORE
0c53PUSH10x24
0c55MSTORE
0c56PUSH10x44
0c58DUP3
0c59SWAP1
0c5aMSTORE
0c5bPUSH10x64
0c5dDUP7
0c5eREVERT
0c5fJUMPDEST
0c60PUSH40x4d13561d
0c65PUSH10xe0
0c67SHL
0c68DUP9
0c69MSTORE
0c6aPUSH10x04
0c6cDUP8
0c6dSWAP1
0c6eMSTORE
0c6fPUSH10x24
0c71MSTORE
0c72PUSH10x44
0c74DUP8
0c75REVERT
0c76JUMPDEST
0c77PUSH40x37e3280d
0c7cPUSH10xe2
0c7eSHL
0c7fDUP8
0c80MSTORE
0c81PUSH10x04
0c83DUP7
0c84SWAP1
0c85MSTORE
0c86PUSH10x24
0c88MSTORE
0c89PUSH10x44
0c8bDUP7
0c8cREVERT
0c8dJUMPDEST
0c8ePUSH40x86de1953
0c93PUSH10xe0
0c95SHL
0c96DUP6
0c97MSTORE
0c98PUSH10x04
0c9aDUP5
0c9bSWAP1
0c9cMSTORE
0c9dPUSH10x24
0c9fMSTORE
0ca0PUSH10x44
0ca2DUP5
0ca3REVERT
0ca4JUMPDEST
0ca5PUSH40x54880ffd
0caaPUSH10xe1
0cacSHL
0cadDUP5
0caeMSTORE
0cafPUSH10x04
0cb1DUP4
0cb2SWAP1
0cb3MSTORE
0cb4PUSH10x24
0cb6DUP5
0cb7REVERT
0cb8JUMPDEST
0cb9PUSH40x16f28f79
0cbePUSH10xe2
0cc0SHL
0cc1DUP3
0cc2MSTORE
0cc3CALLER
0cc4PUSH10x04
0cc6MSTORE
0cc7PUSH10x24
0cc9DUP3
0ccaREVERT
0ccbJUMPDEST
0cccPUSH40xedc1a9bb
0cd1PUSH10xe0
0cd3SHL
0cd4DUP4
0cd5MSTORE
0cd6PUSH10x04
0cd8DUP4
0cd9REVERT
0cdaJUMPDEST
0cdbPOP
0cdcCALLVALUE
0cddPUSH20x02ed
0ce0JUMPI
0ce1PUSH10x20
0ce3CALLDATASIZE
0ce4PUSH10x03
0ce6NOT
0ce7ADD
0ce8SLT
0ce9PUSH20x02ed
0cecJUMPI
0cedPUSH10x40
0cefPUSH10x20
0cf1SWAP2
0cf2PUSH10x04
0cf4CALLDATALOAD
0cf5DUP2
0cf6MSTORE
0cf7DUP1
0cf8DUP4
0cf9MSTORE
0cfaKECCAK256
0cfbPUSH10x01
0cfdPUSH10x01
0cffPUSH10x40
0d01SHL
0d02SUB
0d03TIMESTAMP
0d04AND
0d05SWAP1
0d06DUP1
0d07SLOAD
0d08PUSH10x01
0d0aPUSH10x01
0d0cPUSH10x40
0d0eSHL
0d0fSUB
0d10DUP2
0d11AND
0d12ISZERO
0d13ISZERO
0d14SWAP3
0d15DUP4
0d16PUSH20x0dd1
0d19JUMPI
0d1aJUMPDEST
0d1bDUP4
0d1cPUSH20x0db8
0d1fJUMPI
0d20JUMPDEST
0d21DUP4
0d22PUSH20x0da0
0d25JUMPI
0d26JUMPDEST
0d27DUP4
0d28PUSH20x0d6e
0d2bJUMPI
0d2cJUMPDEST
0d2dPOP
0d2ePOP
0d2fDUP2
0d30PUSH20x0d3f
0d33JUMPI
0d34JUMPDEST
0d35POP
0d36PUSH10x40
0d38MLOAD
0d39SWAP1
0d3aISZERO
0d3bISZERO
0d3cDUP2
0d3dMSTORE
0d3eRETURN
0d3fJUMPDEST
0d40PUSH10x04
0d42ADD
0d43SLOAD
0d44PUSH10x01
0d46PUSH10x01
0d48PUSH10x40
0d4aSHL
0d4bSUB
0d4cDUP2
0d4dAND
0d4eISZERO
0d4fDUP1
0d50ISZERO
0d51SWAP3
0d52POP
0d53PUSH20x0d5e
0d56JUMPI
0d57JUMPDEST
0d58POP
0d59PUSH0
0d5aPUSH20x0d34
0d5dJUMP
0d5eJUMPDEST
0d5fPUSH10x01
0d61SWAP2
0d62POP
0d63PUSH10xc0
0d65SHR
0d66AND
0d67ISZERO
0d68ISZERO
0d69PUSH0
0d6aPUSH20x0d57
0d6dJUMP
0d6eJUMPDEST
0d6fDUP3
0d70SWAP4
0d71POP
0d72PUSH20x0d8f
0d75PUSH10x01
0d77PUSH10x01
0d79PUSH10x40
0d7bSHL
0d7cSUB
0d7dSWAP3
0d7eSWAP4
0d7fSWAP2
0d80PUSH20x0d89
0d83DUP5
0d84SWAP4
0d85PUSH20x31d2
0d88JUMP
0d89JUMPDEST
0d8aSWAP1
0d8bPUSH20x275e
0d8eJUMP
0d8fJUMPDEST
0d90SWAP3
0d91PUSH10x40
0d93SHR
0d94AND
0d95SWAP2
0d96AND
0d97LT
0d98ISZERO
0d99SWAP1
0d9aPUSH0
0d9bDUP1
0d9cPUSH20x0d2c
0d9fJUMP
0da0JUMPDEST
0da1SWAP3
0da2POP
0da3PUSH10x01
0da5PUSH10x01
0da7PUSH10x40
0da9SHL
0daaSUB
0dabDUP2
0dacPUSH10x80
0daeSHR
0dafAND
0db0DUP4
0db1GT
0db2ISZERO
0db3SWAP3
0db4PUSH20x0d26
0db7JUMP
0db8JUMPDEST
0db9PUSH10x04
0dbbDUP4
0dbcADD
0dbdSLOAD
0dbePUSH10x40
0dc0SHR
0dc1PUSH10x01
0dc3PUSH10x01
0dc5PUSH10x40
0dc7SHL
0dc8SUB
0dc9AND
0dcaISZERO
0dcbSWAP4
0dccPOP
0dcdPUSH20x0d20
0dd0JUMP
0dd1JUMPDEST
0dd2SWAP3
0dd3POP
0dd4DUP1
0dd5PUSH10xc0
0dd7SHR
0dd8ISZERO
0dd9SWAP3
0ddaPUSH20x0d1a
0dddJUMP
0ddeJUMPDEST
0ddfPUSH20x23b7
0de2JUMP
0de3JUMPDEST
0de4PUSH20x24d2
0de7JUMP
0de8JUMPDEST
0de9POP
0deaCALLVALUE
0debPUSH20x02ed
0deeJUMPI
0defPUSH10x20
0df1CALLDATASIZE
0df2PUSH10x03
0df4NOT
0df5ADD
0df6SLT
0df7PUSH20x02ed
0dfaJUMPI
0dfbPUSH10x40
0dfdPUSH10x20
0dffSWAP2
0e00PUSH10x04
0e02CALLDATALOAD
0e03DUP2
0e04MSTORE
0e05DUP1
0e06DUP4
0e07MSTORE
0e08KECCAK256
0e09DUP1
0e0aSLOAD
0e0bPUSH10x01
0e0dPUSH10x01
0e0fPUSH10x40
0e11SHL
0e12SUB
0e13DUP2
0e14AND
0e15ISZERO
0e16ISZERO
0e17SWAP2
0e18DUP3
0e19PUSH20x0e6d
0e1cJUMPI
0e1dJUMPDEST
0e1eDUP3
0e1fPUSH20x0e54
0e22JUMPI
0e23JUMPDEST
0e24POP
0e25DUP2
0e26PUSH20x0e34
0e29JUMPI
0e2aPOP
0e2bPUSH10x40
0e2dMLOAD
0e2eSWAP1
0e2fISZERO
0e30ISZERO
0e31DUP2
0e32MSTORE
0e33RETURN
0e34JUMPDEST
0e35PUSH10x01
0e37PUSH10x01
0e39PUSH10x40
0e3bSHL
0e3cSUB
0e3dSWAP2
0e3ePOP
0e3fPUSH10x80
0e41SHR
0e42AND
0e43PUSH10x01
0e45PUSH10x01
0e47PUSH10x40
0e49SHL
0e4aSUB
0e4bTIMESTAMP
0e4cAND
0e4dGT
0e4eISZERO
0e4fPUSH0
0e50PUSH20x0d34
0e53JUMP
0e54JUMPDEST
0e55PUSH10x04
0e57ADD
0e58SLOAD
0e59PUSH10x40
0e5bSHR
0e5cPUSH10x01
0e5ePUSH10x01
0e60PUSH10x40
0e62SHL
0e63SUB
0e64AND
0e65ISZERO
0e66SWAP2
0e67POP
0e68PUSH0
0e69PUSH20x0e23
0e6cJUMP
0e6dJUMPDEST
0e6eSWAP2
0e6fPOP
0e70DUP1
0e71PUSH10xc0
0e73SHR
0e74ISZERO
0e75SWAP2
0e76PUSH20x0e1d
0e79JUMP
0e7aJUMPDEST
0e7bPOP
0e7cCALLVALUE
0e7dPUSH20x02ed
0e80JUMPI
0e81DUP1
0e82PUSH10x03
0e84NOT
0e85CALLDATASIZE
0e86ADD
0e87SLT
0e88PUSH20x02ed
0e8bJUMPI
0e8cPUSH10x20
0e8ePUSH10x40
0e90MLOAD
0e91PUSH320xcc7c08481f764947d6a6fa10f1913f9c489bcbe1ea586c5d4aca3bb0d12f45f6
0eb2DUP2
0eb3MSTORE
0eb4RETURN
0eb5JUMPDEST
0eb6POP
0eb7CALLVALUE
0eb8PUSH20x02ed
0ebbJUMPI
0ebcDUP1
0ebdPUSH10x03
0ebfNOT
0ec0CALLDATASIZE
0ec1ADD
0ec2SLT
0ec3PUSH20x02ed
0ec6JUMPI
0ec7PUSH10x40
0ec9MLOAD
0ecaPUSH320x0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d540
0eebPUSH10x01
0eedPUSH10x01
0eefPUSH10xa0
0ef1SHL
0ef2SUB
0ef3AND
0ef4DUP2
0ef5MSTORE
0ef6PUSH10x20
0ef8SWAP1
0ef9RETURN
0efaJUMPDEST
0efbPOP
0efcPUSH10x40
0efeCALLDATASIZE
0effPUSH10x03
0f01NOT
0f02ADD
0f03SLT
0f04PUSH20x02ed
0f07JUMPI
0f08PUSH10x04
0f0aCALLDATALOAD
0f0bPUSH10x01
0f0dPUSH10x01
0f0fPUSH10x40
0f11SHL
0f12SUB
0f13DUP2
0f14GT
0f15PUSH20x09c9
0f18JUMPI
0f19PUSH20x0f26
0f1cSWAP1
0f1dCALLDATASIZE
0f1eSWAP1
0f1fPUSH10x04
0f21ADD
0f22PUSH20x23ed
0f25JUMP
0f26JUMPDEST
0f27SWAP1
0f28PUSH10x24
0f2aCALLDATALOAD
0f2bPUSH10x01
0f2dPUSH10x01
0f2fPUSH10x40
0f31SHL
0f32SUB
0f33DUP2
0f34GT
0f35PUSH20x03fd
0f38JUMPI
0f39PUSH20x0f46
0f3cSWAP1
0f3dCALLDATASIZE
0f3eSWAP1
0f3fPUSH10x04
0f41ADD
0f42PUSH20x23ed
0f45JUMP
0f46JUMPDEST
0f47PUSH10xb6
0f49DUP5
0f4aSWAP3
0f4bSWAP5
0f4cLT
0f4dPUSH20x1a2a
0f50JUMPI
0f51DUP2
0f52ISZERO
0f53PUSH20x19fa
0f56JUMPI
0f57DUP3
0f58CALLDATALOAD
0f59PUSH10xf8
0f5bSHR
0f5cPUSH10x02
0f5eDUP2
0f5fLT
0f60DUP1
0f61ISZERO
0f62PUSH20x1a20
0f65JUMPI
0f66JUMPDEST
0f67PUSH20x1a0e
0f6aJUMPI
0f6bPOP
0f6cDUP2
0f6dPUSH10x01
0f6fLT
0f70ISZERO
0f71PUSH20x19fa
0f74JUMPI
0f75PUSH10xfe
0f77PUSH10x01
0f79DUP5
0f7aADD
0f7bCALLDATALOAD
0f7cPUSH10xf8
0f7eSHR
0f7fAND
0f80PUSH20x19e0
0f83JUMPI
0f84DUP2
0f85PUSH10x22
0f87GT
0f88PUSH20x06fc
0f8bJUMPI
0f8cPUSH10x02
0f8eDUP4
0f8fADD
0f90CALLDATALOAD
0f91SWAP1
0f92DUP3
0f93PUSH10x2a
0f95GT
0f96PUSH20x19dc
0f99JUMPI
0f9aPUSH10x22
0f9cDUP5
0f9dADD
0f9eCALLDATALOAD
0f9fSWAP1
0fa0DUP4
0fa1PUSH10x32
0fa3GT
0fa4PUSH20x19d8
0fa7JUMPI
0fa8PUSH10x2a
0faaDUP6
0fabADD
0facCALLDATALOAD
0fadSWAP4
0faeDUP1
0fafPUSH10x52
0fb1GT
0fb2PUSH20x19d4
0fb5JUMPI
0fb6PUSH10x32
0fb8DUP7
0fb9ADD
0fbaCALLDATALOAD
0fbbSWAP6
0fbcDUP2
0fbdPUSH10x72
0fbfGT
0fc0PUSH20x19d0
0fc3JUMPI
0fc4PUSH10x52
0fc6DUP2
0fc7ADD
0fc8CALLDATALOAD
0fc9SWAP7
0fcaDUP3
0fcbPUSH10x92
0fcdGT
0fcePUSH20x19cc
0fd1JUMPI
0fd2PUSH10x72
0fd4DUP3
0fd5ADD
0fd6CALLDATALOAD
0fd7PUSH10x01
0fd9DUP1
0fdaDUP5
0fdbADD
0fdcCALLDATALOAD
0fddPUSH10xf8
0fdfSHR
0fe0AND
0fe1ISZERO
0fe2DUP1
0fe3ISZERO
0fe4DUP1
0fe5PUSH20x19c3
0fe8JUMPI
0fe9JUMPDEST
0feaPUSH20x19af
0fedJUMPI
0feeDUP1
0fefPUSH20x19a7
0ff2JUMPI
0ff3JUMPDEST
0ff4PUSH20x1998
0ff7JUMPI
0ff8DUP9
0ff9ISZERO
0ffaPUSH20x1989
0ffdJUMPI
0ffeDUP9
0fffDUP12
1000MSTORE
1001DUP11
1002PUSH10x20
1004MSTORE
1005PUSH10x01
1007PUSH10x01
1009PUSH10x40
100bSHL
100cSUB
100dPUSH10x40
100fDUP13
1010KECCAK256
1011SLOAD
1012AND
1013PUSH20x1975
1016JUMPI
1017TIMESTAMP
1018PUSH10x01
101aPUSH10x01
101cPUSH10x40
101eSHL
101fSUB
1020AND
1021PUSH10xc0
1023DUP10
1024SWAP1
1025SHR
1026GT
1027ISZERO
1028PUSH20x1951
102bJUMPI
102cPUSH10xc0
102eDUP7
102fSWAP1
1030SHR
1031PUSH20x18d6
1034JUMPI
1035PUSH10x01
1037PUSH10x01
1039PUSH10x40
103bSHL
103cSUB
103dPUSH20x104c
1040PUSH40x0a4cb800
1045DUP3
1046TIMESTAMP
1047AND
1048PUSH20x275e
104bJUMP
104cJUMPDEST
104dAND
104eDUP1
104fPUSH10x01
1051PUSH10x01
1053PUSH10x40
1055SHL
1056SUB
1057DUP11
1058PUSH10xc0
105aSHR
105bAND
105cGT
105dPUSH20x18bc
1060JUMPI
1061POP
1062JUMPDEST
1063PUSH10x05
1065SLOAD
1066SWAP2
1067DUP3
1068ISZERO
1069DUP1
106aISZERO
106bPUSH20x182e
106eJUMPI
106fJUMPDEST
1070POP
1071PUSH10x01
1073PUSH10x01
1075PUSH10x58
1077SHL
1078SUB
1079DUP4
107aGT
107bPUSH20x180f
107eJUMPI
107fDUP3
1080CALLVALUE
1081SUB
1082PUSH20x17f7
1085JUMPI
1086PUSH10x02
1088SLOAD
1089SWAP2
108aPUSH10x01
108cPUSH10x01
108ePUSH10x01
1090PUSH10x40
1092SHL
1093SUB
1094DUP5
1095AND
1096ADD
1097PUSH10x01
1099PUSH10x01
109bPUSH10x40
109dSHL
109eSUB
109fDUP2
10a0GT
10a1PUSH20x17e3
10a4JUMPI
10a5PUSH10x40
10a7DUP15
10a8PUSH10x01
10aaPUSH10x01
10acPUSH10x40
10aeSHL
10afSUB
10b0DUP15
10b1SWAP4
10b2AND
10b3PUSH10x01
10b5PUSH10x01
10b7PUSH10x40
10b9SHL
10baSUB
10bbNOT
10bcDUP8
10bdAND
10beOR
10bfPUSH10x02
10c1SSTORE
10c2PUSH10x01
10c4PUSH10x01
10c6PUSH10x40
10c8SHL
10c9SUB
10caDUP7
10cbAND
10ccDUP2
10cdMSTORE
10cePUSH10x03
10d0PUSH10x20
10d2MSTORE
10d3KECCAK256
10d4SSTORE
10d5PUSH10x40
10d7MLOAD
10d8SWAP2
10d9PUSH20x0160
10dcDUP4
10ddADD
10deDUP4
10dfDUP2
10e0LT
10e1PUSH10x01
10e3PUSH10x01
10e5PUSH10x40
10e7SHL
10e8SUB
10e9DUP3
10eaGT
10ebOR
10ecPUSH20x17cf
10efJUMPI
10f0SWAP3
10f1DUP11
10f2DUP16
10f3SWAP6
10f4SWAP4
10f5DUP15
10f6SWAP4
10f7DUP13
10f8DUP16
10f9DUP12
10faSWAP9
10fbPUSH10x40
10fdMSTORE
10feTIMESTAMP
10ffPUSH10x01
1101PUSH10x01
1103PUSH10x40
1105SHL
1106SUB
1107AND
1108DUP7
1109MSTORE
110aPUSH10x20
110cDUP7
110dADD
110eSWAP2
110fPUSH10xc0
1111SHR
1112PUSH10x01
1114PUSH10x01
1116PUSH10x40
1118SHL
1119SUB
111aAND
111bDUP3
111cMSTORE
111dPUSH10x40
111fDUP7
1120ADD
1121SWAP1
1122PUSH10xc0
1124SHR
1125PUSH10x01
1127PUSH10x01
1129PUSH10x40
112bSHL
112cSUB
112dAND
112eDUP2
112fMSTORE
1130PUSH10x60
1132DUP7
1133ADD
1134SWAP2
1135DUP11
1136DUP4
1137MSTORE
1138PUSH10x80
113aDUP8
113bADD
113cSWAP4
113dDUP5
113eMSTORE
113fPUSH10xa0
1141DUP8
1142ADD
1143SWAP5
1144DUP6
1145MSTORE
1146PUSH10xc0
1148DUP8
1149ADD
114aSWAP6
114bDUP7
114cMSTORE
114dPUSH10xe0
114fDUP8
1150ADD
1151SWAP11
1152DUP1
1153DUP13
1154MSTORE
1155PUSH20x0100
1158DUP9
1159ADD
115aSWAP9
115bDUP2
115cDUP11
115dMSTORE
115ePUSH20x0120
1161DUP10
1162ADD
1163SWAP11
1164PUSH10x01
1166PUSH10x01
1168PUSH10x40
116aSHL
116bSUB
116cAND
116dDUP12
116eMSTORE
116fPUSH20x0140
1172DUP10
1173ADD
1174SWAP12
1175PUSH10x01
1177ADD
1178CALLDATALOAD
1179PUSH10xf8
117bSHR
117cDUP13
117dMSTORE
117eDUP2
117fMSTORE
1180DUP1
1181PUSH10x20
1183MSTORE
1184PUSH10x40
1186SWAP1
1187KECCAK256
1188SWAP7
1189MLOAD
118aPUSH10x01
118cPUSH10x01
118ePUSH10x40
1190SHL
1191SUB
1192AND
1193PUSH10x01
1195PUSH10x01
1197PUSH10x40
1199SHL
119aSUB
119bAND
119cPUSH10x01
119ePUSH10x01
11a0PUSH10x40
11a2SHL
11a3SUB
11a4NOT
11a5DUP9
11a6SLOAD
11a7AND
11a8OR
11a9DUP8
11aaSSTORE
11abMLOAD
11acPUSH10x01
11aePUSH10x01
11b0PUSH10x40
11b2SHL
11b3SUB
11b4AND
11b5PUSH20x11e1
11b8SWAP1
11b9DUP8
11baSWAP1
11bbPUSH80xffffffffffffffff
11c4PUSH10x40
11c6SHL
11c7DUP3
11c8SLOAD
11c9SWAP2
11caPUSH10x40
11ccSHL
11cdAND
11ceSWAP1
11cfPUSH80xffffffffffffffff
11d8PUSH10x40
11daSHL
11dbNOT
11dcAND
11ddOR
11deSWAP1
11dfSSTORE
11e0JUMP
11e1JUMPDEST
11e2MLOAD
11e3DUP6
11e4SLOAD
11e5SWAP2
11e6MLOAD
11e7PUSH10x01
11e9PUSH10x01
11ebPUSH10xc0
11edSHL
11eeSUB
11efNOT
11f0PUSH10xc0
11f2SWAP2
11f3DUP3
11f4SHL
11f5AND
11f6PUSH160xffffffffffffffffffffffffffffffff
1207SWAP1
1208SWAP4
1209AND
120aPUSH80xffffffffffffffff
1213PUSH10x80
1215SHL
1216PUSH10x80
1218SWAP4
1219DUP5
121aSHL
121bDUP2
121cAND
121dSWAP2
121eSWAP1
121fSWAP2
1220OR
1221SWAP4
1222SWAP1
1223SWAP4
1224OR
1225DUP8
1226SSTORE
1227SWAP3
1228MLOAD
1229PUSH10x01
122bDUP8
122cADD
122dSSTORE
122eSWAP3
122fMLOAD
1230PUSH10x02
1232DUP7
1233ADD
1234SSTORE
1235SWAP3
1236MLOAD
1237PUSH10x03
1239DUP6
123aADD
123bSSTORE
123cSWAP7
123dMLOAD
123ePUSH10x04
1240SWAP1
1241SWAP4
1242ADD
1243DUP1
1244SLOAD
1245SWAP5
1246MLOAD
1247SWAP6
1248MLOAD
1249SWAP7
124aMLOAD
124bPUSH10xff
124dPUSH10xc0
124fSHL
1250SWAP9
1251SHL
1252SWAP8
1253SWAP1
1254SWAP8
1255AND
1256PUSH10x01
1258PUSH10x01
125aPUSH10xc8
125cSHL
125dSUB
125eNOT
125fSWAP1
1260SWAP5
1261AND
1262PUSH10x01
1264PUSH10x01
1266PUSH10x40
1268SHL
1269SUB
126aSWAP1
126bSWAP4
126cAND
126dSWAP3
126eSWAP1
126fSWAP3
1270OR
1271PUSH80xffffffffffffffff
127aPUSH10x40
127cSHL
127dPUSH10x40
127fSWAP6
1280DUP7
1281SHL
1282AND
1283OR
1284SWAP5
1285SWAP1
1286SWAP2
1287SHL
1288AND
1289SWAP3
128aSWAP1
128bSWAP3
128cOR
128dSWAP2
128eSWAP1
128fSWAP2
1290OR
1291SWAP1
1292SWAP2
1293SSTORE
1294MLOAD
1295SWAP9
1296PUSH20x129e
1299DUP11
129aPUSH20x24ed
129dJUMP
129eJUMPDEST
129fPUSH20x12a9
12a2CALLDATASIZE
12a3DUP6
12a4DUP6
12a5PUSH20x2544
12a8JUMP
12a9JUMPDEST
12aaDUP11
12abMSTORE
12acDUP5
12adCALLDATASIZE
12aeSWAP1
12afPUSH20x12b7
12b2SWAP3
12b3PUSH20x2544
12b6JUMP
12b7JUMPDEST
12b8PUSH10x20
12baDUP11
12bbADD
12bcMSTORE
12bdPUSH10x20
12bfSWAP9
12c0PUSH10x40
12c2MLOAD
12c3PUSH20x12cc
12c6DUP12
12c7DUP3
12c8PUSH20x2508
12cbJUMP
12ccJUMPDEST
12cdDUP12
12ceDUP2
12cfMSTORE
12d0PUSH10x40
12d2DUP3
12d3ADD
12d4MSTORE
12d5DUP9
12d6DUP12
12d7MSTORE
12d8PUSH10x01
12daDUP11
12dbMSTORE
12dcPUSH10x40
12deDUP12
12dfKECCAK256
12e0DUP2
12e1MLOAD
12e2DUP1
12e3MLOAD
12e4SWAP1
12e5PUSH10x01
12e7PUSH10x01
12e9PUSH10x40
12ebSHL
12ecSUB
12edDUP3
12eeGT
12efPUSH20x1756
12f2JUMPI
12f3SWAP1
12f4DUP13
12f5DUP15
12f6SWAP3
12f7PUSH20x130a
12faDUP4
12fbPUSH20x1304
12feDUP8
12ffSLOAD
1300PUSH20x257a
1303JUMP
1304JUMPDEST
1305DUP8
1306PUSH20x25b2
1309JUMP
130aJUMPDEST
130bDUP2
130cPUSH10x1f
130eDUP5
130fGT
1310PUSH10x01
1312EQ
1313PUSH20x176a
1316JUMPI
1317POP
1318PUSH20x1337
131bSWAP4
131cSWAP2
131dSWAP1
131eDUP4
131fPUSH20x1662
1322JUMPI
1323JUMPDEST
1324POP
1325POP
1326DUP2
1327PUSH10x01
1329SHL
132aSWAP2
132bPUSH0
132cNOT
132dSWAP1
132ePUSH10x03
1330SHL
1331SHR
1332NOT
1333AND
1334OR
1335SWAP1
1336JUMP
1337JUMPDEST
1338DUP2
1339SSTORE
133aJUMPDEST
133bPUSH10x20
133dDUP3
133eADD
133fMLOAD
1340DUP1
1341MLOAD
1342SWAP1
1343PUSH10x01
1345PUSH10x01
1347PUSH10x40
1349SHL
134aSUB
134bDUP3
134cGT
134dPUSH20x1756
1350JUMPI
1351DUP13
1352SWAP1
1353DUP15
1354PUSH20x136d
1357DUP5
1358PUSH20x1364
135bPUSH10x01
135dDUP9
135eADD
135fSLOAD
1360PUSH20x257a
1363JUMP
1364JUMPDEST
1365PUSH10x01
1367DUP9
1368ADD
1369PUSH20x25b2
136cJUMP
136dJUMPDEST
136eDUP3
136fPUSH10x1f
1371DUP6
1372GT
1373PUSH10x01
1375EQ
1376PUSH20x16e5
1379JUMPI
137aPOP
137bSWAP3
137cDUP1
137dPUSH10x40
137fSWAP6
1380SWAP4
1381PUSH20x13a0
1384SWAP4
1385PUSH10x02
1387SWAP7
1388SWAP3
1389PUSH20x1662
138cJUMPI
138dPOP
138ePOP
138fDUP2
1390PUSH10x01
1392SHL
1393SWAP2
1394PUSH0
1395NOT
1396SWAP1
1397PUSH10x03
1399SHL
139aSHR
139bNOT
139cAND
139dOR
139eSWAP1
139fJUMP
13a0JUMPDEST
13a1PUSH10x01
13a3DUP3
13a4ADD
13a5SSTORE
13a6JUMPDEST
13a7ADD
13a8SWAP2
13a9ADD
13aaMLOAD
13abDUP1
13acMLOAD
13adPUSH10x01
13afPUSH10x01
13b1PUSH10x40
13b3SHL
13b4SUB
13b5DUP2
13b6GT
13b7PUSH20x16d1
13baJUMPI
13bbPUSH20x13ce
13beDUP2
13bfPUSH20x13c8
13c2DUP6
13c3SLOAD
13c4PUSH20x257a
13c7JUMP
13c8JUMPDEST
13c9DUP6
13caPUSH20x25b2
13cdJUMP
13ceJUMPDEST
13cfDUP12
13d0DUP14
13d1PUSH10x1f
13d3DUP4
13d4GT
13d5PUSH10x01
13d7EQ
13d8PUSH20x166d
13dbJUMPI
13dcSWAP1
13ddPUSH20x13f9
13e0SWAP4
13e1DUP4
13e2PUSH20x1662
13e5JUMPI
13e6POP
13e7POP
13e8DUP2
13e9PUSH10x01
13ebSHL
13ecSWAP2
13edPUSH0
13eeNOT
13efSWAP1
13f0PUSH10x03
13f2SHL
13f3SHR
13f4NOT
13f5AND
13f6OR
13f7SWAP1
13f8JUMP
13f9JUMPDEST
13faSWAP1
13fbSSTORE
13fcJUMPDEST
13fdDUP1
13fePUSH20x15bb
1401JUMPI
1402JUMPDEST
1403POP
1404DUP7
1405DUP10
1406MSTORE
1407DUP9
1408DUP9
1409MSTORE
140aPUSH10x40
140cDUP10
140dKECCAK256
140eSWAP6
140fDUP10
1410PUSH10x40
1412SWAP8
1413DUP9
1414PUSH20x1499
1417DUP2
1418MLOAD
1419SWAP3
141aPUSH20x1423
141dDUP4
141eDUP6
141fPUSH20x2508
1422JUMP
1423JUMPDEST
1424PUSH10x01
1426DUP5
1427MSTORE
1428PUSH10x1f
142aNOT
142bDUP4
142cADD
142dDUP15
142eDUP2
142fCALLDATASIZE
1430DUP3
1431DUP9
1432ADD
1433CALLDATACOPY
1434PUSH20x143f
1437DUP6
1438MLOAD
1439SWAP6
143aDUP7
143bPUSH20x2508
143eJUMP
143fJUMPDEST
1440PUSH10x01
1442DUP6
1443MSTORE
1444CALLDATASIZE
1445SWAP1
1446DUP6
1447ADD
1448CALLDATACOPY
1449DUP13
144aPUSH10x01
144cPUSH10x01
144ePUSH10x40
1450SHL
1451SUB
1452PUSH10x04
1454DUP4
1455ADD
1456SLOAD
1457PUSH10x80
1459SHR
145aAND
145bSWAP2
145cPUSH20x1464
145fDUP4
1460PUSH20x2792
1463JUMP
1464JUMPDEST
1465PUSH20x146d
1468DUP8
1469PUSH20x2a67
146cJUMP
146dJUMPDEST
146eMSTORE
146fDUP14
1470DUP2
1471SLOAD
1472PUSH10x01
1474PUSH10x03
1476DUP5
1477ADD
1478SLOAD
1479SWAP4
147aADD
147bSLOAD
147cSWAP4
147dPUSH10x01
147fPUSH10x01
1481PUSH10x01
1483PUSH10x40
1485SHL
1486SUB
1487DUP1
1488DUP5
1489DUP2
148aDUP2
148bPUSH10x80
148dSHR
148eAND
148fSWAP7
1490SHR
1491AND
1492SWAP4
1493AND
1494SWAP2
1495PUSH20x26b0
1498JUMP
1499JUMPDEST
149aPUSH20x14a2
149dDUP3
149ePUSH20x2a67
14a1JUMP
14a2JUMPDEST
14a3MSTORE
14a4PUSH320x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8
14c5PUSH10x01
14c7PUSH10x01
14c9PUSH10xa0
14cbSHL
14ccSUB
14cdAND
14ceDUP1
14cfEXTCODESIZE
14d0ISZERO
14d1PUSH20x03fd
14d4JUMPI
14d5DUP11
14d6MLOAD
14d7PUSH40xabf1570d
14dcPUSH10xe0
14deSHL
14dfDUP2
14e0MSTORE
14e1PUSH10x07
14e3PUSH10x04
14e5DUP3
14e6ADD
14e7MSTORE
14e8PUSH10x01
14eaPUSH10x24
14ecDUP3
14edADD
14eeMSTORE
14efPUSH10x80
14f1PUSH10x44
14f3DUP3
14f4ADD
14f5MSTORE
14f6SWAP14
14f7DUP5
14f8SWAP4
14f9DUP16
14faSWAP4
14fbDUP5
14fcSWAP3
14fdSWAP2
14feDUP4
14ffSWAP2
1500SWAP1
1501PUSH20x150e
1504SWAP1
1505PUSH10x84
1507DUP5
1508ADD
1509SWAP1
150aPUSH20x2a88
150dJUMP
150eJUMPDEST
150fDUP3
1510DUP2
1511SUB
1512PUSH10x03
1514NOT
1515ADD
1516PUSH10x64
1518DUP5
1519ADD
151aMSTORE
151bPUSH20x1523
151eSWAP2
151fPUSH20x2a88
1522JUMP
1523JUMPDEST
1524SUB
1525SWAP3
1526GAS
1527CALL
1528SWAP11
1529DUP12
152aISZERO
152bPUSH20x15af
152eJUMPI
152fDUP10
1530SWAP11
1531SWAP12
1532SWAP10
1533SWAP8
1534SWAP9
1535SWAP10
1536PUSH20x158c
1539JUMPI
153aJUMPDEST
153bPOP
153cPOP
153dDUP8
153eMLOAD
153fPUSH10xc0
1541SWAP6
1542DUP7
1543SHR
1544DUP2
1545MSTORE
1546SWAP5
1547SHR
1548DUP10
1549DUP6
154aADD
154bMSTORE
154cPUSH10x01
154eADD
154fCALLDATALOAD
1550PUSH10xf8
1552SHR
1553DUP4
1554DUP8
1555ADD
1556MSTORE
1557PUSH10x60
1559DUP4
155aADD
155bMSTORE
155cPUSH10x80
155eDUP3
155fADD
1560MSTORE
1561PUSH320x24f7513addaa5e94cfcd9b75c20467f3408766ba4339ceb2e7f7670542bc0519
1582SWAP1
1583PUSH10xa0
1585SWAP1
1586LOG3
1587MLOAD
1588SWAP1
1589DUP2
158aMSTORE
158bRETURN
158cJUMPDEST
158dDUP2
158eDUP1
158fSWAP4
1590SWAP5
1591SWAP6
1592SWAP7
1593SWAP8
1594SWAP9
1595POP
1596PUSH20x159e
1599SWAP2
159aPUSH20x2508
159dJUMP
159eJUMPDEST
159fPUSH20x02ed
15a2JUMPI
15a3SWAP1
15a4DUP2
15a5DUP10
15a6SWAP7
15a7SWAP6
15a8SWAP5
15a9SWAP4
15aaSWAP3
15abPUSH20x153a
15aeJUMP
15afJUMPDEST
15b0POP
15b1DUP8
15b2MLOAD
15b3SWAP1
15b4RETURNDATASIZE
15b5SWAP1
15b6DUP3
15b7RETURNDATACOPY
15b8RETURNDATASIZE
15b9SWAP1
15baREVERT
15bbJUMPDEST
15bcPUSH10x40
15beMLOAD
15bfPUSH20x15c7
15c2DUP2
15c3PUSH20x24ed
15c6JUMP
15c7JUMPDEST
15c8CALLER
15c9DUP1
15caDUP3
15cbMSTORE
15ccPUSH10x01
15cePUSH10x01
15d0PUSH10x58
15d2SHL
15d3SUB
15d4DUP4
15d5AND
15d6DUP12
15d7DUP4
15d8ADD
15d9SWAP1
15daDUP2
15dbMSTORE
15dcPUSH10x40
15deDUP1
15dfDUP5
15e0ADD
15e1DUP15
15e2DUP2
15e3MSTORE
15e4DUP13
15e5DUP16
15e6MSTORE
15e7PUSH10x04
15e9DUP15
15eaMSTORE
15ebDUP2
15ecDUP16
15edKECCAK256
15eeSWAP5
15efMLOAD
15f0SWAP3
15f1MLOAD
15f2SWAP1
15f3MLOAD
15f4PUSH10x01
15f6PUSH10x01
15f8PUSH10xf8
15faSHL
15fbSUB
15fcNOT
15fdSWAP1
15feISZERO
15ffISZERO
1600PUSH10xf8
1602SHL
1603AND
1604PUSH10x01
1606PUSH10x01
1608PUSH10xa0
160aSHL
160bSUB
160cSWAP1
160dSWAP4
160eAND
160fPUSH110xffffffffffffffffffffff
161bPUSH10xa0
161dSHL
161ePUSH10xa0
1620SWAP3
1621SWAP1
1622SWAP3
1623SHL
1624SWAP2
1625SWAP1
1626SWAP2
1627AND
1628OR
1629SWAP2
162aSWAP1
162bSWAP2
162cOR
162dSWAP1
162eSWAP3
162fSSTORE
1630SWAP1
1631MLOAD
1632SWAP2
1633DUP3
1634MSTORE
1635SWAP1
1636DUP9
1637SWAP1
1638PUSH320xa7370d05155156bd5ea6efcbeb39806e27517011389f0fdecfc64cf5d00f405a
1659SWAP1
165aDUP12
165bSWAP1
165cLOG3
165dPUSH0
165ePUSH20x1402
1661JUMP
1662JUMPDEST
1663ADD
1664MLOAD
1665SWAP1
1666POP
1667PUSH0
1668DUP1
1669PUSH20x1323
166cJUMP
166dJUMPDEST
166eDUP5
166fDUP2
1670MSTORE
1671DUP14
1672DUP2
1673KECCAK256
1674SWAP3
1675SWAP4
1676SWAP1
1677PUSH10x1f
1679NOT
167aDUP6
167bAND
167cSWAP1
167dDUP16
167eJUMPDEST
167fDUP3
1680DUP3
1681LT
1682PUSH20x16ba
1685JUMPI
1686POP
1687POP
1688SWAP1
1689DUP5
168aPUSH10x01
168cSWAP6
168dSWAP5
168eSWAP4
168fSWAP3
1690LT
1691PUSH20x16a2
1694JUMPI
1695JUMPDEST
1696POP
1697POP
1698POP
1699DUP2
169aSHL
169bADD
169cSWAP1
169dSSTORE
169ePUSH20x13fc
16a1JUMP
16a2JUMPDEST
16a3ADD
16a4MLOAD
16a5PUSH0
16a6NOT
16a7PUSH10xf8
16a9DUP5
16aaPUSH10x03
16acSHL
16adAND
16aeSHR
16afNOT
16b0AND
16b1SWAP1
16b2SSTORE
16b3PUSH0
16b4DUP1
16b5DUP1
16b6PUSH20x1695
16b9JUMP
16baJUMPDEST
16bbPUSH10x01
16bdDUP6
16beSWAP7
16bfDUP3
16c0SWAP4
16c1SWAP7
16c2DUP7
16c3ADD
16c4MLOAD
16c5DUP2
16c6SSTORE
16c7ADD
16c8SWAP6
16c9ADD
16caSWAP4
16cbADD
16ccDUP16
16cdPUSH20x167e
16d0JUMP
16d1JUMPDEST
16d2PUSH40x4e487b71
16d7PUSH10xe0
16d9SHL
16daDUP14
16dbMSTORE
16dcPUSH10x41
16dePUSH10x04
16e0MSTORE
16e1PUSH10x24
16e3DUP14
16e4REVERT
16e5JUMPDEST
16e6PUSH10x01
16e8DUP7
16e9ADD
16eaDUP3
16ebMSTORE
16ecDUP1
16edDUP3
16eeKECCAK256
16efSWAP4
16f0SWAP3
16f1SWAP2
16f2SWAP1
16f3JUMPDEST
16f4PUSH10x1f
16f6NOT
16f7DUP7
16f8AND
16f9DUP3
16faLT
16fbPUSH20x173f
16feJUMPI
16ffPOP
1700POP
1701SWAP3
1702PUSH10x40
1704SWAP5
1705SWAP3
1706PUSH10x01
1708SWAP3
1709PUSH10x02
170bSWAP6
170cDUP4
170dPUSH10x1f
170fNOT
1710DUP2
1711AND
1712LT
1713PUSH20x1727
1716JUMPI
1717JUMPDEST
1718POP
1719POP
171aPOP
171bDUP2
171cSHL
171dADD
171ePUSH10x01
1720DUP3
1721ADD
1722SSTORE
1723PUSH20x13a6
1726JUMP
1727JUMPDEST
1728ADD
1729MLOAD
172aPUSH0
172bNOT
172cPUSH10xf8
172eDUP5
172fPUSH10x03
1731SHL
1732AND
1733SHR
1734NOT
1735AND
1736SWAP1
1737SSTORE
1738PUSH0
1739DUP1
173aDUP1
173bPUSH20x1717
173eJUMP
173fJUMPDEST
1740PUSH10x01
1742DUP5
1743SWAP6
1744DUP3
1745SWAP4
1746SWAP6
1747DUP6
1748ADD
1749MLOAD
174aDUP2
174bSSTORE
174cADD
174dSWAP5
174eADD
174fSWAP3
1750ADD
1751DUP16
1752PUSH20x16f3
1755JUMP
1756JUMPDEST
1757PUSH40x4e487b71
175cPUSH10xe0
175eSHL
175fDUP15
1760MSTORE
1761PUSH10x41
1763PUSH10x04
1765MSTORE
1766PUSH10x24
1768DUP15
1769REVERT
176aJUMPDEST
176bDUP6
176cDUP6
176dMSTORE
176eDUP1
176fDUP6
1770KECCAK256
1771SWAP3
1772SWAP2
1773SWAP1
1774JUMPDEST
1775PUSH10x1f
1777NOT
1778DUP6
1779AND
177aDUP7
177bLT
177cPUSH20x17b5
177fJUMPI
1780POP
1781PUSH10x01
1783SWAP5
1784POP
1785DUP4
1786PUSH10x1f
1788NOT
1789DUP2
178aAND
178bLT
178cPUSH20x179d
178fJUMPI
1790JUMPDEST
1791POP
1792POP
1793POP
1794DUP2
1795SHL
1796ADD
1797DUP2
1798SSTORE
1799PUSH20x133a
179cJUMP
179dJUMPDEST
179eADD
179fMLOAD
17a0PUSH0
17a1NOT
17a2PUSH10xf8
17a4DUP5
17a5PUSH10x03
17a7SHL
17a8AND
17a9SHR
17aaNOT
17abAND
17acSWAP1
17adSSTORE
17aePUSH0
17afDUP1
17b0DUP1
17b1PUSH20x1790
17b4JUMP
17b5JUMPDEST
17b6DUP3
17b7DUP3
17b8ADD
17b9MLOAD
17baDUP5
17bbSSTORE
17bcSWAP5
17bdDUP6
17beADD
17bfSWAP5
17c0PUSH10x01
17c2SWAP1
17c3SWAP4
17c4ADD
17c5SWAP3
17c6SWAP1
17c7SWAP2
17c8ADD
17c9SWAP1
17caDUP16
17cbPUSH20x1774
17ceJUMP
17cfJUMPDEST
17d0PUSH40x4e487b71
17d5PUSH10xe0
17d7SHL
17d8DUP16
17d9MSTORE
17daPUSH10x41
17dcPUSH10x04
17deMSTORE
17dfPUSH10x24
17e1DUP16
17e2REVERT
17e3JUMPDEST
17e4PUSH40x4e487b71
17e9PUSH10xe0
17ebSHL
17ecDUP15
17edMSTORE
17eePUSH10x11
17f0PUSH10x04
17f2MSTORE
17f3PUSH10x24
17f5DUP15
17f6REVERT
17f7JUMPDEST
17f8PUSH40x4ba85a6b
17fdPUSH10xe1
17ffSHL
1800DUP13
1801MSTORE
1802PUSH10x04
1804DUP4
1805SWAP1
1806MSTORE
1807CALLVALUE
1808PUSH10x24
180aMSTORE
180bPUSH10x44
180dDUP13
180eREVERT
180fJUMPDEST
1810PUSH40x4ba85a6b
1815PUSH10xe1
1817SHL
1818DUP13
1819MSTORE
181aPUSH10x01
181cPUSH10x01
181ePUSH10x58
1820SHL
1821SUB
1822PUSH10x04
1824MSTORE
1825PUSH10x24
1827DUP4
1828SWAP1
1829MSTORE
182aPUSH10x44
182cDUP13
182dREVERT
182eJUMPDEST
182fSWAP1
1830SWAP3
1831SWAP1
1832TIMESTAMP
1833PUSH10x01
1835PUSH10x01
1837PUSH10x40
1839SHL
183aSUB
183bAND
183cPUSH10xc0
183eDUP12
183fSWAP1
1840SHR
1841SWAP1
1842DUP2
1843SUB
1844SWAP1
1845DUP2
1846GT
1847PUSH20x17e3
184aJUMPI
184bPUSH40x0a4cb800
1850SWAP1
1851DUP2
1852DUP2
1853ADD
1854SWAP1
1855DUP2
1856LT
1857PUSH20x18a8
185aJUMPI
185bPUSH0
185cNOT
185dDUP2
185eADD
185fSWAP1
1860DUP2
1861GT
1862PUSH20x18a8
1865JUMPI
1866DUP2
1867ISZERO
1868PUSH20x1894
186bJUMPI
186cDIV
186dDUP1
186eDUP4
186fMUL
1870SWAP3
1871DUP4
1872DIV
1873EQ
1874OR
1875ISZERO
1876PUSH20x1880
1879JUMPI
187aSWAP2
187bPUSH0
187cPUSH20x106f
187fJUMP
1880JUMPDEST
1881PUSH40x4e487b71
1886PUSH10xe0
1888SHL
1889DUP13
188aMSTORE
188bPUSH10x11
188dPUSH10x04
188fMSTORE