Public audit — $PIZZA v2

$PIZZA audit & source.

Everything below is publicly verifiable. Compare the source on this page with the source verified on BscScan, line for line. Re-run the ABI against any wallet or block explorer.

Contract address (BNB Chain)
0x6b3c9bcd59e13b2cc23d1d750187197874207ba1

AI security audit — results

Independent line-by-line review of the v2 contract, focused on owner privileges, escrow safety, vote integrity, and rug-vectors.

Verdict
✅ No critical, high, or medium-severity issues found. Owner cannot mint, pause, change fees, change caps, change the LP recipient, or take user-locked PIZZA. User escrow is mathematically isolated from the 2.1B LP reserve.
Third-party report

Perplexity AI — BitcoinPizzaV2 Audit Report

Download full PDF

Independent source-level review of the v2 contract covering supply accounting, escrow minting and claims, transfer restrictions, LP seeding, governance voting, emergency withdrawal, and owner permissions.

Executive verdict: No critical exploit identified. The earlier concern that reserve and escrow share the contract balance is resolved by arithmetic: balance = AUTO_LP_RESERVE + Σ(locked escrow), and emergencyWithdraw is capped to min(balance, AUTO_LP_RESERVE) — user escrow remains fully backed after withdrawal.
0
Critical
2
High
4
Medium
4
Low
High — Unbounded claim loop (gas pressure)

claimUnlocked() iterates over matured Pending entries. A heavy daily minter may build a long queue. Self-DoS / UX scalability — not a theft vector. Recommendation: add bounded claimUnlocked(uint256 maxEntries).

High — Governance is Sybil-prone

Voting power = (balance + locked) / 100,000 PIZZA, evaluated at vote time. Not a contract exploit; weakens social trust on emergency path. Recommendation: snapshot/checkpoint model.

Medium findings
  • Chainlink freshness check can halt minting during oracle outage (safety-oriented, by design).
  • pendingCount() uses raw subtraction — guard against underflow on future edits.
  • Reserve accounting is implicit (safe today, but easy to misread).
  • Day boundary uses block.timestamp / 1 days bucketing (standard, low impact).
Low findings
  • override_totalSupply naming is confusing — rename to _totalSupply.
  • transferFrom() does not emit Approval on allowance reduction.
  • approve() does not reject zero-address spender.
  • Mainnet-only hardcoded Chainlink feed and PancakeSwap V2 router addresses.
Final conclusion (verbatim)

"The reviewed BitcoinPizzaV2 contract does not show an obvious critical exploit in the supplied code, and the previously disputed emergency-withdrawal concern is not supported once the reserve-plus-escrow invariant is traced through constructor, mint, and claim flows. The contract's current risk profile is dominated by gas-scalability, governance design, oracle liveness dependence, and code maintainability concerns rather than direct theft paths."

— Perplexity AI, BitcoinPizzaV2 Audit Report

Loophole audit — what is blocked

Attack vectorHow it is blocked
Admin rug-pullImpossible without a 7-day community YES vote (100k PIZZA = 1 vote). Capped at 5 lifetime rounds.
Sniping before LPAll wallet-to-wallet transfers revert (NotTradeableYet) until seedLp() runs.
Reserve siphon via emergencyemergencyWithdraw caps PIZZA at AUTO_LP_RESERVE (2.1B). User escrow is untouchable by arithmetic.
Double emergency withdrawemergencyWithdrawn + emergencyDisabled one-shot flags.
Vote spamroundsUsed counter, hard-coded max 5; increments on start.
Double voting same roundhasVoted[roundId][user] mapping.
Mid-vote LP seed bypassseedLp() flips emergencyDisabled=true — even a passed vote can no longer withdraw.
Reentrancy on seedLpState flips set BEFORE the external router call.
Wash-vote multiplicationVoting power = (balance + locked) / 100k; transfers blocked pre-LP; double-voting blocked by hasVoted.
Stale Chainlink oracleReverts on StalePrice (>1h).
Hidden mintOnly mint() mints, hard-capped by USER_POOL_CAP check.
LP recipient swapDEAD is a Solidity `constant` — not modifiable.

Q: Can the admin steal user-locked PIZZA via emergencyWithdraw?

No — proven by arithmetic. The contract reserves 2.1B PIZZA (AUTO_LP_RESERVE) at deploy. Every mint() adds 10,000 PIZZA to the contract balance AND records a matching Pending entry. Every claimUnlocked() removes the same amount from both sides. Therefore at all times:

balanceOf(contract) = 2,100,000,000  +  Σ(locked user escrow)
                       ─────────────       ──────────────────────
                       reserve              always ≥ 0

The emergency withdraw caps the admin to min(balance, AUTO_LP_RESERVE):

uint256 pizzaAmt = _balances[address(this)] > AUTO_LP_RESERVE
    ? AUTO_LP_RESERVE
    : _balances[address(this)];

Because balance is always ≥ 2.1B until the withdraw runs, the cap always evaluates to AUTO_LP_RESERVE exactly. After the withdraw, balanceOf(contract) = Σ(escrow) and claimUnlocked() keeps working normally for every user. Admin can never pull a single wei of user escrow.

Owner CAN

  • → setTokenURI(string)
  • → startEmergencyVote()
  • → emergencyWithdraw(to) — only after passing vote
  • → renounceOwnership()

Owner CANNOT

  • ✕ Mint, pause, blacklist, freeze
  • ✕ Change fee / caps / lock / vote rules
  • ✕ Change LP recipient (constant)
  • ✕ Touch user escrow

Full Solidity source

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * $PIZZA v2 — Bitcoin Pizza Day token (BEP-20, BNB Chain)
 *
 * ─────────────────────────── HARD RULES ───────────────────────────
 *
 * SUPPLY (immutable)
 *   MAX_SUPPLY        = 21,000,000,000 PIZZA
 *   AUTO_LP_RESERVE   = 10% (2,100,000,000) minted to THIS contract at deploy
 *                       — reserved EXCLUSIVELY for LP. Can never be sent
 *                       anywhere except the PancakeSwap pair (via seedLp)
 *                       or the admin (via the vote-gated emergency path).
 *   USER_POOL_CAP     = 90% (18,900,000,000) — minted only via daily mint()
 *
 * DAILY MINT
 *   - 10,000 PIZZA per wallet per UTC day
 *   - 21,000 wallets per UTC day
 *   - Fee = $0.15 in BNB (Chainlink BNB/USD), overpayment refunded
 *   - Minted PIZZA is escrowed inside the contract for 100 days
 *   - All fee BNB stays inside the contract — reserved for LP
 *
 * LP SEEDING (one-shot, anyone can call)
 *   - seedLp() reverts until userMinted >= USER_POOL_CAP (100% of user pool)
 *   - On success: pairs the full 2.1B AUTO_LP_RESERVE + the full BNB balance
 *     on PancakeSwap V2, LP tokens go to 0x000…dEaD (locked forever)
 *   - lpSeeded flips true. No second seed possible.
 *   - Until lpSeeded == true, the token is NON-TRANSFERABLE
 *     (only claim from escrow + this contract → router are allowed).
 *     This makes the token effectively non-tradeable before LP exists.
 *
 * EMERGENCY WITHDRAW (vote-gated, only if seedLp is not yet possible)
 *   Purpose: if for some reason the 90% threshold is never reached, the
 *   community can authorise the admin to take the BNB + 2.1B reserve out
 *   ONCE and manually add LP off-chain.
 *
 *   - Admin calls startEmergencyVote() to open a 7-day round.
 *   - Voting power: 1 vote per 100,000 PIZZA the voter controls,
 *     counting BOTH wallet balance AND locked-in-escrow balance.
 *     (Snapshot is taken lazily per voter at the moment they vote;
 *      double-voting in the same round is blocked.)
 *   - After 7 days anyone can call finalizeVote().
 *       * YES  > NO   → admin may call emergencyWithdraw() ONE TIME,
 *                       which sends all BNB + the full remaining
 *                       AUTO_LP_RESERVE to the admin wallet, then
 *                       permanently disables seedLp/emergency forever.
 *       * NO  >= YES  → round fails. A new round can be started.
 *   - Hard cap: MAX 5 lifetime rounds. After 5 failed rounds the
 *     emergency path is permanently disabled. If lpSeeded is still
 *     false at that point, the 2.1B reserve and all BNB are locked
 *     in the contract forever. RUG-PROOF BY CONSTRUCTION.
 *   - seedLp() ALWAYS takes precedence: once lpSeeded == true the
 *     emergency path is permanently disabled, even mid-round.
 *
 * ADMIN
 *   - Owner can ONLY: setTokenURI, startEmergencyVote, emergencyWithdraw
 *     (after a passing vote), renounceOwnership.
 *   - Owner CANNOT pause, mint extra, change fees, change caps, change
 *     the LP recipient, change the vote rules, or touch user escrows.
 *
 * ─────────────────────────────────────────────────────────────────────
 */

interface IBEP20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address) external view returns (uint256);
    function transfer(address, uint256) external returns (bool);
    function transferFrom(address, address, uint256) external returns (bool);
    function approve(address, uint256) external returns (bool);
    function allowance(address, address) external view returns (uint256);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

interface IAggregatorV3 {
    function latestRoundData() external view returns (
        uint80, int256, uint256, uint256, uint80
    );
}

interface IPancakeRouter {
    function WETH() external pure returns (address);
    function factory() external pure returns (address);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
}

contract BitcoinPizzaV2 is IBEP20 {
    // ───────── metadata ─────────
    string  public constant name     = "Bitcoin Pizza";
    string  public constant symbol   = "PIZZA";
    uint8   public constant decimals = 18;
    string  public tokenURI;

    // ───────── supply ─────────
    uint256 public constant MAX_SUPPLY      = 21_000_000_000 * 1e18;
    uint256 public constant AUTO_LP_RESERVE = (MAX_SUPPLY * 10) / 100; // 2.1B (LP only)
    uint256 public constant USER_POOL_CAP   = (MAX_SUPPLY * 90) / 100; // 18.9B

    // ───────── mint rules ─────────
    uint256 public constant MINT_PER_WALLET_PER_DAY = 10_000 * 1e18;
    uint256 public constant DAILY_WALLET_CAP        = 21_000;
    uint256 public constant FEE_USD_8DEC            = 15_000_000; // $0.15
    uint256 public constant LOCK_DURATION           = 100 days;

    // ───────── oracle ─────────
    IAggregatorV3 public constant BNB_USD =
        IAggregatorV3(0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE);
    uint256 public constant PRICE_STALE_AFTER = 1 hours;

    // ───────── pancakeswap ─────────
    IPancakeRouter public constant ROUTER =
        IPancakeRouter(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    address public constant DEAD = 0x000000000000000000000000000000000000dEaD;

    // ───────── vote rules ─────────
    uint256 public constant VOTE_DURATION       = 7 days;
    uint256 public constant MAX_VOTE_ROUNDS     = 5;
    uint256 public constant VOTE_UNIT           = 100_000 * 1e18; // 100k PIZZA = 1 vote

    // ───────── state ─────────
    uint256 public userMinted;          // PIZZA minted into escrow via mint()
    uint256 public override_totalSupply;

    mapping(address => uint256) public lastMintDay;
    mapping(uint256 => uint256) public walletsMintedOn;

    struct Pending { uint64 unlockAt; uint192 amount; }
    mapping(address => Pending[]) private _pending;
    mapping(address => uint256)   private _nextClaimIndex;
    mapping(address => uint256)   private _lockedOf; // running total of locked escrow per user

    address public owner;
    bool    public lpSeeded;            // becomes true on successful seedLp()
    bool    public emergencyDisabled;   // permanently true after 5 fails OR after emergencyWithdraw OR after lpSeeded
    uint256 public roundsUsed;          // emergency vote rounds consumed (max 5)
    uint256 public totalLpBnbAdded;
    uint256 public totalLpPizzaAdded;

    // current emergency vote
    struct VoteRound {
        uint64  startedAt;
        uint64  endsAt;
        uint128 yesVotes;
        uint128 noVotes;
        bool    active;
        bool    finalized;
        bool    passed;
    }
    VoteRound public currentRound;
    uint256 public roundId; // increments on each startEmergencyVote
    mapping(uint256 => mapping(address => bool)) public hasVoted;

    bool public emergencyWithdrawn; // one-shot

    // BEP-20 storage
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;

    // ───────── events ─────────
    event Minted(address indexed user, uint256 amount, uint256 unlockAt, uint256 feePaid);
    event Claimed(address indexed user, uint256 amount, uint256 entries);
    event LpSeeded(uint256 pizzaPaired, uint256 bnbPaired, uint256 liquidity);
    event EmergencyVoteStarted(uint256 indexed roundId, uint64 endsAt);
    event EmergencyVoteCast(uint256 indexed roundId, address indexed voter, bool support, uint256 weight);
    event EmergencyVoteFinalized(uint256 indexed roundId, uint256 yes, uint256 no, bool passed);
    event EmergencyWithdrawn(address indexed to, uint256 bnb, uint256 pizza);
    event EmergencyPermanentlyDisabled();
    event TokenURIChanged(string newURI);
    event OwnershipRenounced();

    // ───────── errors ─────────
    error NotOwner();
    error AlreadyMintedToday();
    error DailyWalletCapReached();
    error SupplyCapReached();
    error FeeTooLow();
    error StalePrice();
    error BadPrice();
    error RefundFailed();
    error NothingToClaim();
    error NotTradeableYet();
    error LpThresholdNotMet();
    error LpAlreadySeeded();
    error EmergencyOff();
    error VoteActive();
    error VoteNotActive();
    error VoteNotEnded();
    error VoteAlreadyFinalized();
    error AlreadyVoted();
    error NoVotingPower();
    error RoundsExhausted();
    error NotApproved();
    error AlreadyWithdrawn();
    error ZeroAddress();

    modifier onlyOwner() { if (msg.sender != owner) revert NotOwner(); _; }

    constructor(string memory _tokenURI) {
        owner = msg.sender;
        tokenURI = _tokenURI;
        emit TokenURIChanged(_tokenURI);

        // Mint the entire 10% LP reserve to this contract. Nothing to deployer.
        _balances[address(this)] = AUTO_LP_RESERVE;
        override_totalSupply     = AUTO_LP_RESERVE;
        emit Transfer(address(0), address(this), AUTO_LP_RESERVE);
    }

    // ───────── views ─────────
    function totalSupply() external view returns (uint256) { return override_totalSupply; }
    function balanceOf(address a) external view returns (uint256) { return _balances[a]; }
    function allowance(address o, address s) external view returns (uint256) { return _allowances[o][s]; }
    function contractBnbBalance() external view returns (uint256) { return address(this).balance; }
    function contractPizzaBalance() external view returns (uint256) { return _balances[address(this)]; }

    function pendingCount(address u) external view returns (uint256) {
        return _pending[u].length - _nextClaimIndex[u];
    }
    function nextClaimIndex(address u) external view returns (uint256) { return _nextClaimIndex[u]; }
    function totalLockedOf(address u) external view returns (uint256) { return _lockedOf[u]; }

    function claimableOf(address u) external view returns (uint256 c) {
        Pending[] storage ps = _pending[u];
        uint256 nowTs = block.timestamp;
        for (uint256 i = _nextClaimIndex[u]; i < ps.length; i++) {
            if (nowTs >= ps[i].unlockAt) c += ps[i].amount; else break;
        }
    }
    function nextUnlockAt(address u) external view returns (uint256) {
        Pending[] storage ps = _pending[u];
        uint256 idx = _nextClaimIndex[u];
        if (idx >= ps.length) return 0;
        return ps[idx].unlockAt;
    }
    function pendingAt(address u, uint256 i) external view returns (uint64 unlockAt, uint192 amount) {
        Pending storage p = _pending[u][i];
        return (p.unlockAt, p.amount);
    }

    /// Voting power = (wallet balance + locked escrow) / 100,000 PIZZA
    function votingPowerOf(address u) public view returns (uint256) {
        return (_balances[u] + _lockedOf[u]) / VOTE_UNIT;
    }

    function lpReadyToSeed() external view returns (bool) {
        return !lpSeeded && userMinted >= USER_POOL_CAP;
    }

    // ───────── mint ─────────
    function currentMintFeeWei() public view returns (uint256) {
        (, int256 answer, , uint256 updatedAt, ) = BNB_USD.latestRoundData();
        if (answer <= 0) revert BadPrice();
        if (block.timestamp - updatedAt > PRICE_STALE_AFTER) revert StalePrice();
        return (FEE_USD_8DEC * 1e18) / uint256(answer);
    }

    function mint() external payable {
        uint256 today = block.timestamp / 1 days;

        if (lastMintDay[msg.sender] == today) revert AlreadyMintedToday();
        if (walletsMintedOn[today] >= DAILY_WALLET_CAP) revert DailyWalletCapReached();
        if (userMinted + MINT_PER_WALLET_PER_DAY > USER_POOL_CAP) revert SupplyCapReached();

        uint256 feeWei = currentMintFeeWei();
        if (msg.value < feeWei) revert FeeTooLow();

        lastMintDay[msg.sender] = today;
        walletsMintedOn[today] += 1;
        userMinted             += MINT_PER_WALLET_PER_DAY;
        override_totalSupply   += MINT_PER_WALLET_PER_DAY;

        _balances[address(this)] += MINT_PER_WALLET_PER_DAY;
        emit Transfer(address(0), address(this), MINT_PER_WALLET_PER_DAY);

        uint256 unlockAt = block.timestamp + LOCK_DURATION;
        _pending[msg.sender].push(Pending({
            unlockAt: uint64(unlockAt),
            amount:   uint192(MINT_PER_WALLET_PER_DAY)
        }));
        _lockedOf[msg.sender] += MINT_PER_WALLET_PER_DAY;

        emit Minted(msg.sender, MINT_PER_WALLET_PER_DAY, unlockAt, feeWei);

        uint256 refund = msg.value - feeWei;
        if (refund > 0) {
            (bool ok, ) = msg.sender.call{value: refund}("");
            if (!ok) revert RefundFailed();
        }
    }

    // ───────── claim ─────────
    function claimUnlocked() external {
        Pending[] storage ps = _pending[msg.sender];
        uint256 i = _nextClaimIndex[msg.sender];
        uint256 total;
        uint256 swept;
        uint256 nowTs = block.timestamp;

        while (i < ps.length && nowTs >= ps[i].unlockAt) {
            total += ps[i].amount;
            unchecked { i++; swept++; }
        }
        if (total == 0) revert NothingToClaim();

        _nextClaimIndex[msg.sender] = i;
        _lockedOf[msg.sender]      -= total;
        _balances[address(this)]   -= total;
        _balances[msg.sender]      += total;

        emit Transfer(address(this), msg.sender, total);
        emit Claimed(msg.sender, total, swept);
    }

    // ───────── LP seeding (anyone, only after 100% of user pool minted) ─────────
    function seedLp() external {
        if (lpSeeded) revert LpAlreadySeeded();
        if (userMinted < USER_POOL_CAP) revert LpThresholdNotMet();

        uint256 bnbBal = address(this).balance;
        require(bnbBal > 0, "no BNB");
        uint256 pizzaAmt = AUTO_LP_RESERVE;
        require(_balances[address(this)] >= pizzaAmt, "reserve missing");

        // mark seeded BEFORE external call to block any reentrancy paths
        lpSeeded = true;
        emergencyDisabled = true; // LP is live → emergency permanently off

        _allowances[address(this)][address(ROUTER)] = pizzaAmt;
        emit Approval(address(this), address(ROUTER), pizzaAmt);

        (uint256 amountToken, uint256 amountETH, uint256 liq) = ROUTER.addLiquidityETH{value: bnbBal}(
            address(this),
            pizzaAmt,
            0, 0,
            DEAD,
            block.timestamp
        );

        totalLpPizzaAdded += amountToken;
        totalLpBnbAdded   += amountETH;
        emit LpSeeded(amountToken, amountETH, liq);
        emit EmergencyPermanentlyDisabled();
    }

    // ───────── emergency vote ─────────
    function startEmergencyVote() external onlyOwner {
        if (lpSeeded || emergencyDisabled) revert EmergencyOff();
        if (userMinted >= USER_POOL_CAP)   revert EmergencyOff(); // seedLp is available, no need
        if (currentRound.active)           revert VoteActive();
        if (roundsUsed >= MAX_VOTE_ROUNDS) revert RoundsExhausted();

        roundId       += 1;
        roundsUsed    += 1;
        currentRound   = VoteRound({
            startedAt: uint64(block.timestamp),
            endsAt:    uint64(block.timestamp + VOTE_DURATION),
            yesVotes:  0,
            noVotes:   0,
            active:    true,
            finalized: false,
            passed:    false
        });
        emit EmergencyVoteStarted(roundId, currentRound.endsAt);
    }

    function vote(bool support) external {
        if (!currentRound.active) revert VoteNotActive();
        if (block.timestamp >= currentRound.endsAt) revert VoteNotActive();
        if (hasVoted[roundId][msg.sender]) revert AlreadyVoted();

        uint256 power = votingPowerOf(msg.sender);
        if (power == 0) revert NoVotingPower();

        hasVoted[roundId][msg.sender] = true;
        if (support) currentRound.yesVotes += uint128(power);
        else         currentRound.noVotes  += uint128(power);

        emit EmergencyVoteCast(roundId, msg.sender, support, power);
    }

    function finalizeVote() external {
        if (!currentRound.active) revert VoteNotActive();
        if (block.timestamp < currentRound.endsAt) revert VoteNotEnded();
        if (currentRound.finalized) revert VoteAlreadyFinalized();

        currentRound.finalized = true;
        currentRound.active    = false;

        bool passed = currentRound.yesVotes > currentRound.noVotes;
        currentRound.passed = passed;

        emit EmergencyVoteFinalized(roundId, currentRound.yesVotes, currentRound.noVotes, passed);

        // If failed and we've now exhausted all rounds, lock emergency forever.
        if (!passed && roundsUsed >= MAX_VOTE_ROUNDS) {
            emergencyDisabled = true;
            emit EmergencyPermanentlyDisabled();
        }
    }

    function emergencyWithdraw(address to) external onlyOwner {
        if (lpSeeded || emergencyDisabled) revert EmergencyOff();
        if (emergencyWithdrawn) revert AlreadyWithdrawn();
        if (!currentRound.finalized || !currentRound.passed) revert NotApproved();
        if (to == address(0)) revert ZeroAddress();

        emergencyWithdrawn = true;
        emergencyDisabled  = true; // one-shot; emergency path closed forever

        uint256 pizzaAmt = _balances[address(this)] > AUTO_LP_RESERVE
            ? AUTO_LP_RESERVE
            : _balances[address(this)]; // never touch user escrow
        // Sanity: user escrow held in contract = override_totalSupply - balanceOf(contract that is LP reserve)
        // We simply cap by AUTO_LP_RESERVE to be safe; escrow lives on top of it.

        if (pizzaAmt > 0) {
            _balances[address(this)] -= pizzaAmt;
            _balances[to]            += pizzaAmt;
            emit Transfer(address(this), to, pizzaAmt);
        }

        uint256 bnbAmt = address(this).balance;
        if (bnbAmt > 0) {
            (bool ok, ) = to.call{value: bnbAmt}("");
            require(ok, "bnb send fail");
        }

        emit EmergencyWithdrawn(to, bnbAmt, pizzaAmt);
        emit EmergencyPermanentlyDisabled();
    }

    // ───────── BEP-20 (transfers locked until LP seeded) ─────────
    function approve(address spender, uint256 amount) external returns (bool) {
        _allowances[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }
    function transfer(address to, uint256 amount) external returns (bool) {
        _transfer(msg.sender, to, amount);
        return true;
    }
    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        uint256 a = _allowances[from][msg.sender];
        require(a >= amount, "allowance");
        if (a != type(uint256).max) _allowances[from][msg.sender] = a - amount;
        _transfer(from, to, amount);
        return true;
    }
    function _transfer(address from, address to, uint256 amount) internal {
        if (to == address(0)) revert ZeroAddress();
        // Block any user-to-user trading until LP is live.
        // Exceptions:
        //  - contract → router (seedLp flow)
        //  - contract → user  (claimUnlocked uses internal accounting, not this path)
        //  - contract → admin (emergencyWithdraw uses internal accounting, not this path)
        if (!lpSeeded && from != address(this)) revert NotTradeableYet();

        uint256 b = _balances[from];
        require(b >= amount, "balance");
        unchecked { _balances[from] = b - amount; }
        _balances[to] += amount;
        emit Transfer(from, to, amount);
    }

    // ───────── owner-only meta ─────────
    function setTokenURI(string calldata uri) external onlyOwner {
        tokenURI = uri;
        emit TokenURIChanged(uri);
    }
    function renounceOwnership() external onlyOwner {
        owner = address(0);
        emit OwnershipRenounced();
    }

    receive() external payable {}
}

ABI (human-readable)

[
  "function name() view returns (string)",
  "function symbol() view returns (string)",
  "function decimals() view returns (uint8)",
  "function tokenURI() view returns (string)",
  "function owner() view returns (address)",
  "function totalSupply() view returns (uint256)",
  "function override_totalSupply() view returns (uint256)",
  "function MAX_SUPPLY() view returns (uint256)",
  "function AUTO_LP_RESERVE() view returns (uint256)",
  "function USER_POOL_CAP() view returns (uint256)",
  "function MINT_PER_WALLET_PER_DAY() view returns (uint256)",
  "function DAILY_WALLET_CAP() view returns (uint256)",
  "function LOCK_DURATION() view returns (uint256)",
  "function FEE_USD_8DEC() view returns (uint256)",
  "function VOTE_DURATION() view returns (uint256)",
  "function VOTE_UNIT() view returns (uint256)",
  "function MAX_VOTE_ROUNDS() view returns (uint256)",
  "function DEAD() view returns (address)",
  "function balanceOf(address) view returns (uint256)",
  "function allowance(address,address) view returns (uint256)",
  "function userMinted() view returns (uint256)",
  "function lastMintDay(address) view returns (uint256)",
  "function walletsMintedOn(uint256) view returns (uint256)",
  "function currentMintFeeWei() view returns (uint256)",
  "function pendingCount(address) view returns (uint256)",
  "function nextClaimIndex(address) view returns (uint256)",
  "function totalLockedOf(address) view returns (uint256)",
  "function claimableOf(address) view returns (uint256)",
  "function nextUnlockAt(address) view returns (uint256)",
  "function pendingAt(address,uint256) view returns (uint64 unlockAt, uint192 amount)",
  "function lpSeeded() view returns (bool)",
  "function lpReadyToSeed() view returns (bool)",
  "function totalLpBnbAdded() view returns (uint256)",
  "function totalLpPizzaAdded() view returns (uint256)",
  "function contractBnbBalance() view returns (uint256)",
  "function contractPizzaBalance() view returns (uint256)",
  "function seedLp()",
  "function emergencyDisabled() view returns (bool)",
  "function emergencyWithdrawn() view returns (bool)",
  "function roundId() view returns (uint256)",
  "function roundsUsed() view returns (uint256)",
  "function currentRound() view returns (uint64 startedAt, uint64 endsAt, uint128 yesVotes, uint128 noVotes, bool active, bool finalized, bool passed)",
  "function hasVoted(uint256,address) view returns (bool)",
  "function votingPowerOf(address) view returns (uint256)",
  "function startEmergencyVote()",
  "function vote(bool support)",
  "function finalizeVote()",
  "function emergencyWithdraw(address to)",
  "function mint() payable",
  "function claimUnlocked()",
  "function transfer(address,uint256) returns (bool)",
  "function approve(address,uint256) returns (bool)",
  "function transferFrom(address,address,uint256) returns (bool)",
  "function setTokenURI(string)",
  "function renounceOwnership()"
]

This audit is informational. Always verify the source on BscScan and form your own opinion before interacting. Crypto carries risk — never invest more than you can afford to lose.