不卖P R O O F  O F  H O D L

The rules that decide who gets paid

Published before launch. They do not change afterwards. Once the winners list is committed to the escrow there is no owner, no withdraw and no fix — so every rule is stated here in full, and nothing is decided later.

DRAFT. Two values below are still pending. This page is not final until both are filled and the hash is republished.

WHAT IS FROZEN

rules_hash0xe9a58b3f19edeeb2b26ac4f180941e5ab412c00406a75eb61e36190bc08a2230
team_list_hash0x5248f874ba36ef5d3e442c93b6caf780716949f12370e1e586e96888cbd810bf
escrow addressPENDING · not yet deployed
GAS_PER_CLAIM_WEI500,000,000,000,000 wei = 0.0005 BNB
gas price cap5 gwei — ops pushes only at or below
vest30 days from graduation
sweep target0x000…dEaD — burn
不卖 (BUMAI) — FROZEN PAYOUT SNAPSHOT. This script decides who gets paid. It is published BEFORE launch and does not change after. Once the Merkle root is committed to the escrow there is no owner, no withdraw and no fix, so every rule below is stated here in full and nothing is inferred at runtime. python snapshot.py --token 0x… --cutoff-block N > snapshot.json python snapshot.py --token 0x… --cutoff-block N --verify F # re-run and diff ═══════════════════════════════════════════════════════════════════════════════ THE RULES — all of them, in force, no exceptions applied later ═══════════════════════════════════════════════════════════════════════════════

THE RULES · R1 – R11

R1CUTOFF. THE CUTOFF IS THE ESCROW'S OWN, READ FROM THE ESCROW. Not our clock, not a

    number we choose, not an assumption.

    The escrow sets `cutoff` in start(), so the 30-day clock begins at graduation rather
    than at deploy. It reads ZERO until then. THREE states are therefore distinguished,
    and only the last proceeds:

        cutoff() == 0    NOT STARTED  -> refuse
        cutoff()  > now  RUNNING      -> refuse
        cutoff() <= now  ENDED        -> proceed

    Zero is in the past, so a two-state "has the cutoff passed?" test PASSES on a game
    that has not begun — it would snapshot an unstarted or mid-flight game and commit a
    root over it. `started()` is read as well and cross-checked against `cutoff() != 0`;
    if the two disagree the script refuses rather than choosing one.

    Once ENDED, the cutoff timestamp is resolved to the last block at or before it and
    the script REFUSES if --cutoff-block disagrees. The block is still passed explicitly
    so a run is reproducible from its arguments alone, but it is checked against the
    contract rather than trusted.

    The run also refuses unless the cutoff is at least 15 blocks deep
    (engine.CONFIRMATIONS = 15, about 7 seconds on BSC) — a reorgable cutoff would make
    the payout itself reorgable.

R2ACCRUAL. A wallet accrues token-seconds: balance x seconds held. Accrual runs from

    its first inbound transfer to the cutoff block's timestamp. This is the SAME code
    path as the live engine — `engine.apply()` is imported, not reimplemented, so the
    scoreboard people watched for 30 days and the payout cannot disagree.

R3DEATH - stated in full here, not by reference. Death is decided PER TRANSACTION on

    the net balance change, never per Transfer log. Post-graduation the token is
    fee-on-transfer, so one buy emits several Transfers (pool -> buyer for the tokens,
    buyer -> vault for the tax); judged per log, every buyer appears as `from` on its
    own purchase and dies instantly.

    For a wallet w in transaction T:

      counted_out(w,T)    = sum of outbound legs, EXCLUDING legs to a mechanical fee
                            sink (the tax vault or the token contract). A tax leg is
                            involuntary and is not a sale.
      offsettable_in(w,T) = sum of inbound legs whose sender is a POOL, the bonding
                            curve, or a mint (0x0). NOTHING ELSE offsets.
      dead(w,T)           <=> counted_out > offsettable_in

    Death is permanent: accrual is zeroed and never resumes, and later buys do not
    resurrect. Legs are grouped by (block, transactionIndex) and processed in
    (block, transactionIndex, logIndex) order, so the result is deterministic.

    Inbound from another WALLET does not offset an outbound. That is the residual case:
    A sells 1M to the pool and receives 1M from B in the same transaction. Its balance
    nets to zero, but it sold, and offsettable_in is 0 because B is not a pool - so A is
    dead. Netting on the raw balance delta would let any seller be rescued by an inbound
    leg they arranged themselves, which is trivially farmable.

    Worked cases:
      BUY            in 1M from pool, out 30k tax -> out 0,  offset 1M   ALIVE
      SELL           out 1M to pool (+ tax)       -> out 1M, offset 0    DEAD
      RECEIVE + tax  in 1M from EOA, out 30k tax  -> out 0               ALIVE
      SEND to friend out 1M to another wallet     -> out 1M, offset 0    DEAD
      sell + rebuy   out 1M pool, in 1M pool      -> 1M vs 1M, not >     ALIVE

    Implemented once in `engine.apply` and shared with the live scoreboard (R2). Every
    case above is asserted in test_apply.py.

R4ELIGIBILITY. Alive at the cutoff, with accrual > 0. An entry_rank is NOT required —

    how tokens were acquired does not change that they were held. Self-airdropping to
    farm extra wallets is not an exploit: the sending wallet's outbound kills it, so the
    tokens simply move, they do not multiply.

R5EXCLUSIONS. Two sets, and NOTHING is inferred from transaction history.

    (a) STRUCTURAL - addresses that are not holders by construction:
          the token contract - the tax vault (dividendContract()) - the tax processor
          - the flap bonding curve / Portal - the escrow - 0x0...0 - 0x...dEaD
          - PancakeSwap feeTo (a permanent sink that would otherwise read as the
            ultimate diamond hand)
          - every liquidity pool, DETECTED AS: PancakeSwap V2 getPair(token, quote), V3
            getPool(token, quote, fee) over fee tiers 100/500/2500/10000, for each quote
            in {WBNB, USDT, USDC, BUSD}; plus any counterparty in the token's own
            Transfer graph for which token.pools(addr) returns true. The method is
            stated because "every detected pool" is not reproducible without it.

    (b) TEAM - an EXPLICIT PUBLISHED LIST, fixed before launch, passed as --team-list and
        hashed into this output as team_list_hash. The file is part of the frozen rules.
        An empty list must be passed explicitly; there is no default.

    WHY THE TEAM IS NOT DEDUCED FROM THE CHAIN. A previous version excluded "every
    address the deployer sent BNB to before the cutoff". That is unimplementable as
    written. Etherscan caps every query at 10,000 rows, so a deployer with 48,018
    transactions before the cutoff yields a RECENCY WINDOW of roughly 11-24 days rather
    than a complete set - a bound the rule never stated and no auditor could reproduce.
    Two honest implementations of the same sentence found 8 addresses and 39.

    On the token used to validate this, both sets produced an identical root, because
    every deployer-funded wallet was already dead or had zero accrual. That is luck, not
    correctness: in a 30-day game a deployer-funded wallet that buys and HOLDS stays
    alive with accrual > 0 - precisely the case the clause exists to catch - and 8 vs 39
    is real money.

    The clause was never an anti-attack measure; it is a credibility measure. So it is
    now a commitment we NAME and publish, not a query nobody can reproduce.

R6AMOUNT, AND WHERE THE POT COMES FROM.

      gross_i = floor(POT_WEI * accrual_i / total_accrual)

    Integer arithmetic throughout - Python ints are arbitrary precision, so there is no
    float anywhere in the payout path. ROUNDING IS DOWN, ALWAYS, PER RECIPIENT.

    POT_WEI IS THE ESCROW'S ON-CHAIN BALANCE AT THE CUTOFF BLOCK, read from chain via
    --escrow. It is not the vault's inflow, not an estimate, and not a number we choose.
    claim() pays with Address.sendValue, so if the committed tree's total exceeded the
    escrow's real balance the LAST claimants would revert - holding a valid proof
    against an empty contract, with no owner and no fix. The script REFUSES TO RUN
    without --escrow for exactly that reason.

R7GAS DEDUCTION, AND THE FLOOR THAT FALLS OUT OF IT.

    THERE IS EXACTLY ONE THRESHOLD, AND IT IS THIS ONE. The earlier PAYOUT_FLOOR_WEI of
    1e15 was REMOVED, not kept alongside. It does not appear anywhere in this file or in
    the code, and no wallet is tested against it. If both were live a wallet would need
    gross > 0.0015 BNB to be paid, and the exclusion rate would rise further than any
    figure we publish. A grep for PAYOUT_FLOOR is the check.

      GAS_PER_CLAIM_WEI  a PUBLISHED CONSTANT in this file, not a live estimate — a
                         live one would make the leaves non-deterministic.
      gross_i            = floor(POT_WEI * accrual_i / total_accrual)      (R6)
      net_i              = gross_i - GAS_PER_CLAIM_WEI
      net_i <= 0         -> NO LEAF. This IS the floor, and it is mechanical: a wallet
                         is excluded exactly when its share does not cover the cost of
                         delivering it, never because we picked a threshold.
      leaf amount        = net_i

    Recipients bear their own gas through this deduction. "No fee" means no protocol
    fee; everyone in crypto pays gas and nobody calls that a fee.

    THE OPS LEAF. One additional leaf:

      address = OPS_WALLET, which PUSHES every claim
      amount  = GAS_PER_CLAIM_WEI * (number of winner leaves)

    It is the delivery budget, and it is in the published list like any other leaf so
    anyone can check it is exactly the sum of the deductions and not one wei more.
    Note the identity: sum(net_i) + ops = sum(gross_i) over winners. The ops leaf
    reclaims the deductions exactly; it cannot over-collect without the arithmetic
    visibly failing.

    WITHOUT THE OPS LEAF the deducted gas would be stranded: the escrow has no withdraw
    function, so it would sit there and burn at sweep while ops still paid for every
    claim out of pocket. The deduction would fund nothing.

    THE GAS PRICE CAP IS PART OF THE RULE. GAS_PER_CLAIM_WEI = 0.0005 BNB was set
    against a MEASURED 75,063 gas per claim - executed against the deployed escrow's own
    verified bytecode on a real EVM, not modelled - at a ceiling of 5 gwei, which is what
    BSC quoted when the constant was fixed. OPS COMMITS TO PUSHING CLAIMS ONLY AT OR
    BELOW 5 GWEI. Without that cap the constant is arbitrary; with it, it is checkable:
    75,063 x 5 gwei = 0.000375 BNB, and 0.0005 covers it with 33% headroom.

    UNSPENT GAS BUDGET IS BURNED. If delivery costs less than the budget, the remainder
    is burned and the burn transaction hash is published. There is no version of this
    where we profit from the estimate being generous. Note the asymmetry, which runs
    against instinct: overshooting costs RECIPIENTS (the surplus burns), undershooting
    costs OPS. The constant is therefore the smallest value that covers the measured
    cost at the capped price, not a comfortable round number.

R8RESIDUE. sum(all leaves) <= POT_WEI always. The difference — rounding dust plus the

    gross shares of wallets excluded by R7 — is never assigned to anyone. It stays in
    the escrow and BURNS. No one, including us, can retrieve it. The exact residue is
    reported in the output so it can be checked against the escrow after the last claim.

    Excluded shares are NOT redistributed. Redistributing would change every other
    payout, which changes who clears the deduction, which changes the set again — it
    does not converge cleanly and it is far harder to audit.

R9ORDERING AND INDEX. Leaves are sorted by address ascending, byte-wise. Addresses are

    unique, so no tie-break is ever needed and none is defined. `index` is the position
    in that sorted list, from 0. This ordering is a pure function of the eligible set.

R10MERKLE — OpenZeppelin StandardMerkleTree, matching the deployed BumaiPotEscrow.

      leaf = keccak256(keccak256(abi.encode(uint256 index, address account,
                                            uint256 amount)))

    DOUBLE-hashed, and abi.encode — every field padded to 32 bytes, 96 bytes in total —
    NOT abi.encodePacked. The double hash is what makes a leaf undistinguishable from an
    internal node: with single-hashed packed leaves a 64-byte internal node can be
    replayed as a leaf, which is the standard second-preimage attack on Merkle trees.

    Internal nodes are keccak256 of the two children CONCATENATED IN ASCENDING BYTE
    ORDER, so proofs carry no position bits. Leaves are SORTED BY HASH ASCENDING and
    placed in a complete binary tree of 2n-1 nodes, filled from the end in reverse
    (OpenZeppelin `makeMerkleTree`). Tree shape is part of the spec: the same leaves in
    a pair-in-order tree with odd-node promotion give a DIFFERENT ROOT.

    This is verified against the real @openzeppelin/merkle-tree library in
    test_merkle_oz.py — root, every leaf hash, and every proof. It is deliberately NOT
    verified only against our own hasher: an earlier version of this file was
    self-consistent, agreed with itself perfectly, and would have made every single
    claim revert with InvalidProof because it matched nothing the escrow computes.

R11DETERMINISM. Same token + same cutoff block => byte-identical output. No local

    state is read: not the engine's state file, not its rank ledger, nothing but chain
    data. `--verify` re-runs and diffs against a previous output, which is the check to
    run before committing the root.

═══════════════════════════════════════════════════════════════════════════════