{"sources":{"AgentBunny.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\n/**\n *  AGENT BUNNY — 10,000 generative bunnies on BNB Chain.\n *\n *  MINT SCHEDULE\n *    - Hard cap           : 10,000\n *    - Per wallet cap     : 5\n *    - First 2 per wallet : FREE      (only while global supply < 5,000)\n *    - Next 3 per wallet  : $0.36     (only while global supply < 5,000)\n *    - After 5,000 minted : $0.60 ea. — every mint, including a wallet's\n *                           first two\n *\n *  Pricing is USD-denominated and converted at execution via a Chainlink\n *  BNB/USD feed. Overpayment is refunded in the same transaction.\n *\n *  OWNERSHIP\n *    The deployer is the owner and retains full administrative control:\n *    pricing, oracle, gate registry, royalties, withdrawal, mint switch\n *    and ownership transfer. `freezeConfig()` optionally locks the\n *    economic parameters forever.\n *\n *  ART\n *    Every bunny is drawn by the contract. The SVG is embedded as a base64\n *    data URI inside on-chain metadata — no external host, no IPFS pin.\n */\n\ninterface IERC721Balance {\n    function balanceOf(address owner) external view returns (uint256);\n}\n\ninterface AggregatorV3Interface {\n    function latestRoundData()\n        external\n        view\n        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);\n}\n\ncontract AgentBunny {\n    /* ============================ constants ============================ */\n\n    uint256 public constant MAX_SUPPLY = 10_000;\n    uint256 public constant MAX_PER_WALLET = 5;\n    uint256 public constant FREE_PER_WALLET = 2;\n    uint256 public constant FINAL_PHASE_START = 5_000; // 0-based index\n\n    /* ============================ ERC-721 ============================== */\n\n    string public name = \"Agent Bunny\";\n    string public symbol = \"BUNNY\";\n\n    uint256 public totalSupply;\n\n    mapping(uint256 => address) private _owners;\n    mapping(address => uint256) private _balances;\n    mapping(uint256 => address) private _tokenApprovals;\n    mapping(address => mapping(address => bool)) private _operatorApprovals;\n\n    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n    /* ============================ mint state =========================== */\n\n    address public owner;\n    address public pendingOwner;\n\n    bool public mintLive;\n    bool public frozen;\n\n    /// USD price in cents for mints 3..5 per wallet, before the 5,000 mark.\n    uint256 public tier2Cents = 36;\n    /// USD price in cents once global supply reaches 5,000.\n    uint256 public finalCents = 60;\n\n    /// Fallbacks used only if `priceOracle` is unset.\n    uint256 public flatTier2Wei;\n    uint256 public flatFinalWei;\n\n    address public priceOracle;\n    uint256 public oracleMaxAge = 1 hours;\n\n    /// SPACE ID Agent ID registry (ERC-721). Zero disables the gate.\n    address public agentIdContract;\n\n    /// ERC-2981 royalty, basis points (500 = 5%).\n    uint256 public royaltyBps = 500;\n    address public royaltyReceiver;\n\n    mapping(address => uint256) public freeMinted;\n    mapping(address => uint256) public paidMinted;\n    mapping(address => uint256) public totalMintedBy;\n\n    uint256 private _lock;\n\n    /* ============================== errors ============================= */\n\n    error NoAgentId();\n    error SoldOut();\n    error MaxPerWallet();\n    error MintNotLive();\n    error BadQuantity();\n    error WrongPayment();\n    error NotOwner();\n    error ConfigFrozen();\n    error ZeroAddress();\n    error NoBalance();\n    error TransferFailed();\n    error Reentrant();\n    error BadRoyalty();\n\n    /* ============================ modifiers ============================ */\n\n    modifier onlyOwner() {\n        if (msg.sender != owner) revert NotOwner();\n        _;\n    }\n\n    modifier notFrozen() {\n        if (frozen) revert ConfigFrozen();\n        _;\n    }\n\n    modifier nonReentrant() {\n        if (_lock == 1) revert Reentrant();\n        _lock = 1;\n        _;\n        _lock = 0;\n    }\n\n    constructor(address agentIdContract_, address priceOracle_) {\n        owner = msg.sender;\n        royaltyReceiver = msg.sender;\n        agentIdContract = agentIdContract_;\n        priceOracle = priceOracle_;\n    }\n\n    /* ============================== mint =============================== */\n\n    /**\n     * @notice Mint up to MAX_PER_WALLET tokens.\n     * @dev Charged exactly what the schedule dictates; any excess msg.value\n     *      is refunded in the same transaction.\n     */\n    function mint(uint256 qty) external payable nonReentrant returns (uint256 firstTokenId) {\n        if (!mintLive) revert MintNotLive();\n        if (qty == 0 || qty > MAX_PER_WALLET) revert BadQuantity();\n\n        uint256 supply = totalSupply;\n        if (supply + qty > MAX_SUPPLY) revert SoldOut();\n        if (_balances[msg.sender] + qty > MAX_PER_WALLET) revert MaxPerWallet();\n        if (!hasAgentId(msg.sender)) revert NoAgentId();\n\n        uint256 cost;\n        uint256 paidInBatch;\n        uint256 t2 = _usdToWei(tier2Cents, flatTier2Wei);\n        uint256 tf = _usdToWei(finalCents, flatFinalWei);\n        uint256 freeUsed = freeMinted[msg.sender];\n\n        for (uint256 i = 0; i < qty; ++i) {\n            uint256 idx = supply + i; // 0-based position of this token\n            if (idx >= FINAL_PHASE_START) {\n                // final phase: everyone pays, including the free slots\n                cost += tf;\n                paidInBatch++;\n            } else if (freeUsed < FREE_PER_WALLET) {\n                freeUsed++;\n            } else {\n                cost += t2;\n                paidInBatch++;\n            }\n        }\n\n        if (msg.value < cost) revert WrongPayment();\n\n        firstTokenId = supply + 1;\n        for (uint256 i = 0; i < qty; ++i) {\n            _mint(msg.sender, firstTokenId + i);\n        }\n        unchecked {\n            totalMintedBy[msg.sender] += qty;\n            paidMinted[msg.sender] += paidInBatch;\n        }\n        freeMinted[msg.sender] = freeUsed;\n\n        uint256 excess = msg.value - cost;\n        if (excess > 0) {\n            (bool ok, ) = payable(msg.sender).call{value: excess}(\"\");\n            if (!ok) revert TransferFailed();\n        }\n    }\n\n    /* ============================= pricing ============================= */\n\n    /// @notice Cost in wei to mint `qty` right now for `minter`.\n    function quote(address minter, uint256 qty) external view returns (uint256 cost) {\n        uint256 supply = totalSupply;\n        uint256 t2 = _usdToWei(tier2Cents, flatTier2Wei);\n        uint256 tf = _usdToWei(finalCents, flatFinalWei);\n        uint256 freeUsed = freeMinted[minter];\n        for (uint256 i = 0; i < qty; ++i) {\n            uint256 idx = supply + i;\n            if (idx >= FINAL_PHASE_START) cost += tf;\n            else if (freeUsed < FREE_PER_WALLET) freeUsed++;\n            else cost += t2;\n        }\n    }\n\n    /// @notice True once the 5,000 mark has been reached.\n    function finalPhase() public view returns (bool) {\n        return totalSupply >= FINAL_PHASE_START;\n    }\n\n    /// @notice Free mints still available to `minter` (0 in the final phase).\n    function freeRemaining(address minter) public view returns (uint256) {\n        if (finalPhase()) return 0;\n        uint256 used = freeMinted[minter];\n        return used >= FREE_PER_WALLET ? 0 : FREE_PER_WALLET - used;\n    }\n\n    function _usdToWei(uint256 cents, uint256 flat) private view returns (uint256) {\n        address oracle = priceOracle;\n        if (oracle == address(0) || cents == 0) return flat;\n        (, int256 answer, , uint256 updatedAt, ) = AggregatorV3Interface(oracle).latestRoundData();\n        require(answer > 0, \"BAD_ORACLE\");\n        require(block.timestamp - updatedAt <= oracleMaxAge, \"STALE_ORACLE\");\n        // cents (USD*100) -> wei:  cents * 1e24 / answer(8dp USD/BNB)\n        return (cents * 1e24) / uint256(answer);\n    }\n\n    function hasAgentId(address account) public view returns (bool) {\n        address registry = agentIdContract;\n        if (registry == address(0)) return true;\n        try IERC721Balance(registry).balanceOf(account) returns (uint256 bal) {\n            return bal > 0;\n        } catch {\n            return false;\n        }\n    }\n\n    /* ============================ ERC-721 ============================== */\n\n    function balanceOf(address account) public view returns (uint256) {\n        return _balances[account];\n    }\n\n    function ownerOf(uint256 tokenId) public view returns (address o) {\n        o = _owners[tokenId];\n        require(o != address(0), \"NONEXISTENT\");\n    }\n\n    function getApproved(uint256 tokenId) external view returns (address) {\n        return _tokenApprovals[tokenId];\n    }\n\n    function isApprovedForAll(address account, address operator) external view returns (bool) {\n        return _operatorApprovals[account][operator];\n    }\n\n    function approve(address to, uint256 tokenId) external {\n        address o = ownerOf(tokenId);\n        require(msg.sender == o || _operatorApprovals[o][msg.sender], \"NOT_AUTHORIZED\");\n        _tokenApprovals[tokenId] = to;\n        emit Approval(o, to, tokenId);\n    }\n\n    function setApprovalForAll(address operator, bool approved) external {\n        _operatorApprovals[msg.sender][operator] = approved;\n        emit ApprovalForAll(msg.sender, operator, approved);\n    }\n\n    function transferFrom(address from, address to, uint256 tokenId) public {\n        if (from != ownerOf(tokenId)) revert NotOwner();\n        if (to == address(0)) revert ZeroAddress();\n        require(\n            msg.sender == from || _operatorApprovals[from][msg.sender] || msg.sender == _tokenApprovals[tokenId],\n            \"NOT_AUTHORIZED\"\n        );\n        delete _tokenApprovals[tokenId];\n        unchecked {\n            _balances[from] -= 1;\n            _balances[to] += 1;\n        }\n        _owners[tokenId] = to;\n        emit Transfer(from, to, tokenId);\n    }\n\n    function safeTransferFrom(address from, address to, uint256 tokenId) external {\n        safeTransferFrom(from, to, tokenId, \"\");\n    }\n\n    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public {\n        transferFrom(from, to, tokenId);\n        if (to.code.length > 0) {\n            (bool ok, ) = to.call(\n                abi.encodeWithSignature(\"onERC721Received(address,address,uint256,bytes)\", msg.sender, from, tokenId, data)\n            );\n            require(ok, \"UNSAFE_RECIPIENT\");\n        }\n    }\n\n    function _mint(address to, uint256 tokenId) private {\n        if (_owners[tokenId] != address(0)) revert SoldOut();\n        unchecked {\n            _balances[to] += 1;\n            totalSupply = tokenId;\n        }\n        _owners[tokenId] = to;\n        emit Transfer(address(0), to, tokenId);\n    }\n\n    /* ============================ ERC-2981 ============================= */\n\n    function royaltyInfo(uint256, uint256 salePrice) external view returns (address, uint256) {\n        return (royaltyReceiver, (salePrice * royaltyBps) / 10_000);\n    }\n\n    function supportsInterface(bytes4 interfaceId) external pure returns (bool) {\n        return interfaceId == 0x01ffc9a7 // ERC-165\n            || interfaceId == 0x80ac58cd // ERC-721\n            || interfaceId == 0x5b5e139f // ERC-721 Metadata\n            || interfaceId == 0x2a55205a; // ERC-2981\n    }\n\n    /* ============================== traits ============================= */\n\n    string[6] private FUR = [\"Snow\", \"Cocoa\", \"Ash\", \"Caramel\", \"Rose\", \"Midnight\"];\n    string[6] private FUR_HEX = [\"#f6f1e7\", \"#8a5a3b\", \"#9aa0a6\", \"#c98a4b\", \"#e6a3ae\", \"#3b3a52\"];\n    string[5] private BG = [\"Meadow\", \"Dawn\", \"Dusk\", \"Mint\", \"Blush\"];\n    string[5] private BG_HEX = [\"#dff0d2\", \"#ffe6c7\", \"#2b2540\", \"#d6f2ea\", \"#ffe0e6\"];\n    string[3] private EARS = [\"Upright\", \"Lop\", \"Half-Lop\"];\n    string[4] private EYES = [\"Dot\", \"Sparkle\", \"Sleepy\", \"Wink\"];\n    string[5] private ACC = [\"None\", \"Star\", \"Crown\", \"Bow\", \"Halo\"];\n    string[4] private MARK = [\"None\", \"Blaze\", \"Freckles\", \"Star Patch\"];\n    string[4] private AURA = [\"None\", \"Glow\", \"Ring\", \"Sparkle\"];\n\n    function _seed(uint256 tokenId) private view returns (uint256) {\n        return uint256(keccak256(abi.encodePacked(tokenId, address(this), \"agentbunny\")));\n    }\n\n    /// @notice Trait indices: fur, background, ears, eyes, accessory, marking, aura.\n    function traits(uint256 tokenId) public view returns (uint256[7] memory t) {\n        uint256 s = _seed(tokenId);\n        t[0] = s % 6;\n        t[1] = (s >> 8) % 5;\n        t[2] = (s >> 16) % 3;\n        t[3] = (s >> 24) % 4;\n        t[4] = (s >> 32) % 5;\n        t[5] = (s >> 40) % 4;\n        t[6] = (s >> 48) % 4;\n    }\n\n    /* ============================= tokenURI ============================ */\n\n    function tokenURI(uint256 tokenId) external view returns (string memory) {\n        ownerOf(tokenId);\n        uint256[7] memory t = traits(tokenId);\n        string memory json = string(\n            abi.encodePacked(\n                '{\"name\":\"Agent Bunny #',\n                _toString(tokenId),\n                '\",\"description\":\"10,000 generative bunnies drawn on-chain on BNB Chain.\",',\n                '\"image\":\"data:image/svg+xml;base64,',\n                _b64(bytes(_svg(tokenId, t))),\n                '\",\"attributes\":[',\n                _attrs(t),\n                \"]}\"\n            )\n        );\n        return string(abi.encodePacked(\"data:application/json;base64,\", _b64(bytes(json))));\n    }\n\n    /// @notice The exact SVG the contract will draw for `tokenId` — even before it is minted.\n    /// @dev Lets a front end show true art without re-implementing the renderer.\n    function previewSVG(uint256 tokenId) external view returns (string memory) {\n        require(tokenId >= 1 && tokenId <= MAX_SUPPLY, \"OUT_OF_RANGE\");\n        return _svg(tokenId, traits(tokenId));\n    }\n\n    function _attrs(uint256[7] memory t) private view returns (string memory) {\n        return string(\n            abi.encodePacked(\n                _attr(\"Fur\", FUR[t[0]]),\n                _attr(\"Background\", BG[t[1]]),\n                _attr(\"Ears\", EARS[t[2]]),\n                _attr(\"Eyes\", EYES[t[3]]),\n                _attr(\"Accessory\", ACC[t[4]]),\n                _attr(\"Marking\", MARK[t[5]]),\n                _attrLast(\"Aura\", AURA[t[6]])\n            )\n        );\n    }\n\n    function _attr(string memory k, string memory v) private pure returns (string memory) {\n        return string(abi.encodePacked('{\"trait_type\":\"', k, '\",\"value\":\"', v, '\"},'));\n    }\n\n    function _attrLast(string memory k, string memory v) private pure returns (string memory) {\n        return string(abi.encodePacked('{\"trait_type\":\"', k, '\",\"value\":\"', v, '\"}'));\n    }\n\n    /* ============================== drawing ============================ */\n\n    function _svg(uint256 tokenId, uint256[7] memory t) private view returns (string memory) {\n        uint256 s = _seed(tokenId);\n        string memory fur = FUR_HEX[t[0]];\n\n        return string(abi.encodePacked(_svgTop(t, fur, s), _svgFace(t)));\n    }\n\n    function _svgTop(uint256[7] memory t, string memory fur, uint256 s) private view returns (string memory) {\n        return string(\n            abi.encodePacked(\n                '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\">',\n                '<rect width=\"512\" height=\"512\" fill=\"', BG_HEX[t[1]], '\"/>',\n                _aura(t[6], fur, s),\n                _ears(t[2], fur),\n                // head\n                '<ellipse cx=\"256\" cy=\"286\" rx=\"132\" ry=\"120\" fill=\"', fur, '\"/>',\n                // marking sits on the fur, under the features\n                _marking(t[5])\n            )\n        );\n    }\n\n    function _svgFace(uint256[7] memory t) private pure returns (string memory) {\n        return string(\n            abi.encodePacked(\n                // cheeks\n                '<ellipse cx=\"170\" cy=\"330\" rx=\"27\" ry=\"18\" fill=\"#ff9fb0\" opacity=\"0.5\"/>',\n                '<ellipse cx=\"342\" cy=\"330\" rx=\"27\" ry=\"18\" fill=\"#ff9fb0\" opacity=\"0.5\"/>',\n                _eyes(t[3]),\n                // nose\n                '<ellipse cx=\"256\" cy=\"300\" rx=\"10\" ry=\"7.5\" fill=\"#e0708a\"/>',\n                // mouth\n                '<path d=\"M256 307 v7\" stroke=\"#a9796a\" stroke-width=\"3.4\" stroke-linecap=\"round\"/>',\n                '<path d=\"M256 314 q-11 13 -22 3\" stroke=\"#a9796a\" stroke-width=\"3.4\" fill=\"none\" stroke-linecap=\"round\"/>',\n                '<path d=\"M256 314 q11 13 22 3\" stroke=\"#a9796a\" stroke-width=\"3.4\" fill=\"none\" stroke-linecap=\"round\"/>',\n                // whiskers\n                '<g stroke=\"#c9b8a8\" stroke-width=\"2.4\" stroke-linecap=\"round\" opacity=\"0.85\">',\n                '<path d=\"M206 314 l-34 -5\"/><path d=\"M206 324 l-34 5\"/>',\n                '<path d=\"M306 314 l34 -5\"/><path d=\"M306 324 l34 5\"/>',\n                '</g>',\n                _accessory(t[4]),\n                '</svg>'\n            )\n        );\n    }\n\n    function _marking(uint256 m) private pure returns (string memory) {\n        if (m == 0) return \"\";\n        if (m == 1) {\n            // blaze — pale stripe from brow to nose\n            return '<path d=\"M256 176 q18 64 11 122 q-11 8 -22 0 q-7 -58 11 -122 z\" fill=\"#ffffff\" opacity=\"0.5\"/>';\n        }\n        if (m == 2) {\n            // freckles on the muzzle\n            return string(abi.encodePacked(\n                '<circle cx=\"214\" cy=\"332\" r=\"4\" fill=\"#ffffff\" opacity=\"0.8\"/>',\n                '<circle cx=\"230\" cy=\"344\" r=\"3\" fill=\"#ffffff\" opacity=\"0.75\"/>',\n                '<circle cx=\"202\" cy=\"346\" r=\"2.5\" fill=\"#ffffff\" opacity=\"0.7\"/>',\n                '<circle cx=\"298\" cy=\"332\" r=\"4\" fill=\"#ffffff\" opacity=\"0.8\"/>',\n                '<circle cx=\"282\" cy=\"344\" r=\"3\" fill=\"#ffffff\" opacity=\"0.75\"/>',\n                '<circle cx=\"310\" cy=\"346\" r=\"2.5\" fill=\"#ffffff\" opacity=\"0.7\"/>'\n            ));\n        }\n        // star patch on the brow\n        return '<path d=\"M256 206 l9 21 23 3 -17 16 5 23 -20 -12 -20 12 5 -23 -17 -16 23 -3 z\" fill=\"#ffffff\" opacity=\"0.72\"/>';\n    }\n\n    function _ears(uint256 style, string memory fur) private pure returns (string memory) {\n        // Head occupies x 124..388, y 166..406. Ears are drawn first, so they\n        // must reach beyond the head silhouette to be visible.\n        if (style == 0) {\n            // upright\n            return string(abi.encodePacked(\n                '<ellipse cx=\"204\" cy=\"170\" rx=\"36\" ry=\"98\" fill=\"', fur, '\" transform=\"rotate(-9 204 170)\"/>',\n                '<ellipse cx=\"308\" cy=\"170\" rx=\"36\" ry=\"98\" fill=\"', fur, '\" transform=\"rotate(9 308 170)\"/>',\n                '<ellipse cx=\"204\" cy=\"174\" rx=\"17\" ry=\"64\" fill=\"#ffb3c1\" transform=\"rotate(-9 204 174)\"/>',\n                '<ellipse cx=\"308\" cy=\"174\" rx=\"17\" ry=\"64\" fill=\"#ffb3c1\" transform=\"rotate(9 308 174)\"/>'\n            ));\n        }\n        if (style == 1) {\n            // lop — flopped outward and down, clear of the head outline\n            return string(abi.encodePacked(\n                '<ellipse cx=\"110\" cy=\"262\" rx=\"34\" ry=\"92\" fill=\"', fur, '\" transform=\"rotate(24 110 262)\"/>',\n                '<ellipse cx=\"402\" cy=\"262\" rx=\"34\" ry=\"92\" fill=\"', fur, '\" transform=\"rotate(-24 402 262)\"/>',\n                '<ellipse cx=\"110\" cy=\"266\" rx=\"16\" ry=\"60\" fill=\"#ffb3c1\" transform=\"rotate(24 110 266)\"/>',\n                '<ellipse cx=\"402\" cy=\"266\" rx=\"16\" ry=\"60\" fill=\"#ffb3c1\" transform=\"rotate(-24 402 266)\"/>'\n            ));\n        }\n        // half-lop: left upright, right floppy\n        return string(abi.encodePacked(\n            '<ellipse cx=\"206\" cy=\"170\" rx=\"35\" ry=\"96\" fill=\"', fur, '\" transform=\"rotate(-10 206 170)\"/>',\n            '<ellipse cx=\"206\" cy=\"174\" rx=\"16\" ry=\"62\" fill=\"#ffb3c1\" transform=\"rotate(-10 206 174)\"/>',\n            '<ellipse cx=\"402\" cy=\"266\" rx=\"32\" ry=\"88\" fill=\"', fur, '\" transform=\"rotate(-24 402 266)\"/>',\n            '<ellipse cx=\"402\" cy=\"270\" rx=\"15\" ry=\"56\" fill=\"#ffb3c1\" transform=\"rotate(-24 402 270)\"/>'\n        ));\n    }\n\n    function _eyes(uint256 style) private pure returns (string memory) {\n        if (style == 0) {\n            return '<circle cx=\"214\" cy=\"270\" r=\"13\" fill=\"#2a2233\"/><circle cx=\"298\" cy=\"270\" r=\"13\" fill=\"#2a2233\"/>';\n        }\n        if (style == 1) {\n            return string(abi.encodePacked(\n                '<circle cx=\"214\" cy=\"270\" r=\"15\" fill=\"#2a2233\"/><circle cx=\"298\" cy=\"270\" r=\"15\" fill=\"#2a2233\"/>',\n                '<circle cx=\"219\" cy=\"264\" r=\"5\" fill=\"#fff\"/><circle cx=\"303\" cy=\"264\" r=\"5\" fill=\"#fff\"/>',\n                '<circle cx=\"209\" cy=\"276\" r=\"2.5\" fill=\"#fff\"/><circle cx=\"293\" cy=\"276\" r=\"2.5\" fill=\"#fff\"/>'\n            ));\n        }\n        if (style == 2) {\n            return string(abi.encodePacked(\n                '<path d=\"M200 272 q14 14 28 0\" stroke=\"#2a2233\" stroke-width=\"6\" fill=\"none\" stroke-linecap=\"round\"/>',\n                '<path d=\"M284 272 q14 14 28 0\" stroke=\"#2a2233\" stroke-width=\"6\" fill=\"none\" stroke-linecap=\"round\"/>'\n            ));\n        }\n        return string(abi.encodePacked(\n            '<circle cx=\"214\" cy=\"270\" r=\"13\" fill=\"#2a2233\"/>',\n            '<path d=\"M284 272 q14 14 28 0\" stroke=\"#2a2233\" stroke-width=\"6\" fill=\"none\" stroke-linecap=\"round\"/>'\n        ));\n    }\n\n    function _accessory(uint256 acc) private pure returns (string memory) {\n        if (acc == 0) return \"\";\n        if (acc == 1) {\n            // star\n            return '<path d=\"M256 120 l11 26 28 3 -21 19 6 28 -24 -15 -24 15 6 -28 -21 -19 28 -3 z\" fill=\"#ffd34d\"/>';\n        }\n        if (acc == 2) {\n            // crown\n            return string(abi.encodePacked(\n                '<path d=\"M206 186 l18 -40 32 26 32 -26 18 40 z\" fill=\"#ffd34d\" stroke=\"#d9a520\" stroke-width=\"3\"/>',\n                '<circle cx=\"256\" cy=\"160\" r=\"6\" fill=\"#e0708a\"/>'\n            ));\n        }\n        if (acc == 3) {\n            // bow\n            return string(abi.encodePacked(\n                '<path d=\"M256 380 l-34 -20 0 40 z\" fill=\"#e85d75\"/>',\n                '<path d=\"M256 380 l34 -20 0 40 z\" fill=\"#e85d75\"/>',\n                '<circle cx=\"256\" cy=\"380\" r=\"9\" fill=\"#c94a60\"/>'\n            ));\n        }\n        // halo\n        return '<ellipse cx=\"256\" cy=\"132\" rx=\"74\" ry=\"18\" fill=\"none\" stroke=\"#ffe9a3\" stroke-width=\"9\" opacity=\"0.9\"/>';\n    }\n\n    function _aura(uint256 a, string memory fur, uint256 s) private pure returns (string memory) {\n        if (a == 0) return \"\";\n        if (a == 1) {\n            return string(abi.encodePacked(\n                '<circle cx=\"256\" cy=\"276\" r=\"196\" fill=\"', fur, '\" opacity=\"0.16\"/>'\n            ));\n        }\n        if (a == 2) {\n            return '<circle cx=\"256\" cy=\"276\" r=\"208\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"5\" opacity=\"0.35\"/>';\n        }\n        return string(abi.encodePacked(\n            _spark(s, 0), _spark(s, 1), _spark(s, 2), _spark(s, 3), _spark(s, 4), _spark(s, 5)\n        ));\n    }\n\n    function _spark(uint256 s, uint256 i) private pure returns (string memory) {\n        uint256 r = uint256(keccak256(abi.encodePacked(s, i)));\n        return string(\n            abi.encodePacked(\n                '<circle cx=\"', _toString((r % 460) + 26),\n                '\" cy=\"', _toString(((r >> 16) % 460) + 26),\n                '\" r=\"', ((r >> 32) % 3 == 0) ? \"4\" : \"2.5\",\n                '\" fill=\"#fff\" opacity=\"0.85\"/>'\n            )\n        );\n    }\n\n    /* ============================= utils =============================== */\n\n    function _toString(uint256 v) private pure returns (string memory) {\n        if (v == 0) return \"0\";\n        uint256 tmp = v;\n        uint256 digits;\n        while (tmp != 0) {\n            digits++;\n            tmp /= 10;\n        }\n        bytes memory buf = new bytes(digits);\n        while (v != 0) {\n            digits--;\n            buf[digits] = bytes1(uint8(48 + (v % 10)));\n            v /= 10;\n        }\n        return string(buf);\n    }\n\n    /// @dev Standard base64 with correct '=' padding.\n    function _b64(bytes memory data) private pure returns (string memory) {\n        uint256 len = data.length;\n        if (len == 0) return \"\";\n\n        bytes memory table = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n        uint256 outLen = ((len + 2) / 3) * 4;\n        bytes memory out = new bytes(outLen);\n\n        uint256 i;\n        uint256 j;\n        for (; i + 3 <= len; i += 3) {\n            uint256 n = (uint256(uint8(data[i])) << 16) | (uint256(uint8(data[i + 1])) << 8) | uint256(uint8(data[i + 2]));\n            out[j] = table[(n >> 18) & 0x3F];\n            out[j + 1] = table[(n >> 12) & 0x3F];\n            out[j + 2] = table[(n >> 6) & 0x3F];\n            out[j + 3] = table[n & 0x3F];\n            j += 4;\n        }\n\n        uint256 rem = len - i;\n        if (rem == 1) {\n            uint256 n1 = uint256(uint8(data[i])) << 16;\n            out[j] = table[(n1 >> 18) & 0x3F];\n            out[j + 1] = table[(n1 >> 12) & 0x3F];\n            out[j + 2] = bytes1(0x3d);\n            out[j + 3] = bytes1(0x3d);\n        } else if (rem == 2) {\n            uint256 n2 = (uint256(uint8(data[i])) << 16) | (uint256(uint8(data[i + 1])) << 8);\n            out[j] = table[(n2 >> 18) & 0x3F];\n            out[j + 1] = table[(n2 >> 12) & 0x3F];\n            out[j + 2] = table[(n2 >> 6) & 0x3F];\n            out[j + 3] = bytes1(0x3d);\n        }\n        return string(out);\n    }\n\n    /* ============================ admin ================================ */\n\n    event MintLiveSet(bool live);\n    event PricesSet(uint256 tier2Cents, uint256 finalCents);\n    event OracleSet(address oracle);\n    event AgentIdSet(address registry);\n    event RoyaltySet(address receiver, uint256 bps);\n    event Withdrawn(address to, uint256 amount);\n    event OwnershipTransferStarted(address indexed from, address indexed to);\n    event OwnershipTransferred(address indexed from, address indexed to);\n    event Frozen();\n\n    function setMintLive(bool live) external onlyOwner {\n        mintLive = live;\n        emit MintLiveSet(live);\n    }\n\n    function setPrices(uint256 tier2Cents_, uint256 finalCents_, uint256 flatTier2Wei_, uint256 flatFinalWei_)\n        external\n        onlyOwner\n        notFrozen\n    {\n        tier2Cents = tier2Cents_;\n        finalCents = finalCents_;\n        flatTier2Wei = flatTier2Wei_;\n        flatFinalWei = flatFinalWei_;\n        emit PricesSet(tier2Cents_, finalCents_);\n    }\n\n    function setPriceOracle(address oracle) external onlyOwner notFrozen {\n        priceOracle = oracle;\n        emit OracleSet(oracle);\n    }\n\n    function setOracleMaxAge(uint256 seconds_) external onlyOwner {\n        oracleMaxAge = seconds_;\n    }\n\n    function setAgentIdContract(address registry) external onlyOwner notFrozen {\n        agentIdContract = registry;\n        emit AgentIdSet(registry);\n    }\n\n    function setRoyalty(address receiver, uint256 bps) external onlyOwner {\n        if (receiver == address(0)) revert ZeroAddress();\n        if (bps > 1000) revert BadRoyalty();\n        royaltyReceiver = receiver;\n        royaltyBps = bps;\n        emit RoyaltySet(receiver, bps);\n    }\n\n    /// @notice Irreversibly lock prices, oracle and gate configuration.\n    function freezeConfig() external onlyOwner {\n        frozen = true;\n        emit Frozen();\n    }\n\n    function withdraw(address to) external onlyOwner nonReentrant {\n        if (to == address(0)) revert ZeroAddress();\n        uint256 bal = address(this).balance;\n        if (bal == 0) revert NoBalance();\n        (bool ok, ) = payable(to).call{value: bal}(\"\");\n        if (!ok) revert TransferFailed();\n        emit Withdrawn(to, bal);\n    }\n\n    function transferOwnership(address newOwner) external onlyOwner {\n        if (newOwner == address(0)) revert ZeroAddress();\n        pendingOwner = newOwner;\n        emit OwnershipTransferStarted(owner, newOwner);\n    }\n\n    function acceptOwnership() external {\n        require(msg.sender == pendingOwner, \"NOT_PENDING\");\n        emit OwnershipTransferred(owner, msg.sender);\n        owner = msg.sender;\n        pendingOwner = address(0);\n    }\n\n    receive() external payable {}\n}\n"}},"abi":[{"type":"constructor","inputs":[{"name":"agentIdContract_","type":"address","internalType":"address"},{"name":"priceOracle_","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"name":"BadQuantity","type":"error","inputs":[]},{"name":"BadRoyalty","type":"error","inputs":[]},{"name":"ConfigFrozen","type":"error","inputs":[]},{"name":"MaxPerWallet","type":"error","inputs":[]},{"name":"MintNotLive","type":"error","inputs":[]},{"name":"NoAgentId","type":"error","inputs":[]},{"name":"NoBalance","type":"error","inputs":[]},{"name":"NotOwner","type":"error","inputs":[]},{"name":"Reentrant","type":"error","inputs":[]},{"name":"SoldOut","type":"error","inputs":[]},{"name":"TransferFailed","type":"error","inputs":[]},{"name":"WrongPayment","type":"error","inputs":[]},{"name":"ZeroAddress","type":"error","inputs":[]},{"name":"AgentIdSet","type":"event","inputs":[{"name":"registry","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"name":"Approval","type":"event","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"approved","type":"address","indexed":true,"internalType":"address"},{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"}],"anonymous":false},{"name":"ApprovalForAll","type":"event","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"operator","type":"address","indexed":true,"internalType":"address"},{"name":"approved","type":"bool","indexed":false,"internalType":"bool"}],"anonymous":false},{"name":"Frozen","type":"event","inputs":[],"anonymous":false},{"name":"MintLiveSet","type":"event","inputs":[{"name":"live","type":"bool","indexed":false,"internalType":"bool"}],"anonymous":false},{"name":"OracleSet","type":"event","inputs":[{"name":"oracle","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"name":"OwnershipTransferStarted","type":"event","inputs":[{"name":"from","type":"address","indexed":true,"internalType":"address"},{"name":"to","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"name":"OwnershipTransferred","type":"event","inputs":[{"name":"from","type":"address","indexed":true,"internalType":"address"},{"name":"to","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"name":"PricesSet","type":"event","inputs":[{"name":"tier2Cents","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"finalCents","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"RoyaltySet","type":"event","inputs":[{"name":"receiver","type":"address","indexed":false,"internalType":"address"},{"name":"bps","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"Transfer","type":"event","inputs":[{"name":"from","type":"address","indexed":true,"internalType":"address"},{"name":"to","type":"address","indexed":true,"internalType":"address"},{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"}],"anonymous":false},{"name":"Withdrawn","type":"event","inputs":[{"name":"to","type":"address","indexed":false,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"FINAL_PHASE_START","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"FREE_PER_WALLET","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"MAX_PER_WALLET","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"MAX_SUPPLY","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"acceptOwnership","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"name":"agentIdContract","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"approve","type":"function","inputs":[{"name":"to","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"balanceOf","type":"function","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"finalCents","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"finalPhase","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"flatFinalWei","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"flatTier2Wei","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"freeMinted","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"freeRemaining","type":"function","inputs":[{"name":"minter","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"freezeConfig","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"name":"frozen","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"getApproved","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"hasAgentId","type":"function","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"isApprovedForAll","type":"function","inputs":[{"name":"account","type":"address","internalType":"address"},{"name":"operator","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"mint","type":"function","inputs":[{"name":"qty","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"firstTokenId","type":"uint256","internalType":"uint256"}],"stateMutability":"payable"},{"name":"mintLive","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"name","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"oracleMaxAge","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"owner","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"ownerOf","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"o","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"paidMinted","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"pendingOwner","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"previewSVG","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"priceOracle","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"quote","type":"function","inputs":[{"name":"minter","type":"address","internalType":"address"},{"name":"qty","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"cost","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"royaltyBps","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"royaltyInfo","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"},{"name":"salePrice","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"royaltyReceiver","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"safeTransferFrom","type":"function","inputs":[{"name":"from","type":"address","internalType":"address"},{"name":"to","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"safeTransferFrom","type":"function","inputs":[{"name":"from","type":"address","internalType":"address"},{"name":"to","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setAgentIdContract","type":"function","inputs":[{"name":"registry","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setApprovalForAll","type":"function","inputs":[{"name":"operator","type":"address","internalType":"address"},{"name":"approved","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setMintLive","type":"function","inputs":[{"name":"live","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setOracleMaxAge","type":"function","inputs":[{"name":"seconds_","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setPriceOracle","type":"function","inputs":[{"name":"oracle","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setPrices","type":"function","inputs":[{"name":"tier2Cents_","type":"uint256","internalType":"uint256"},{"name":"finalCents_","type":"uint256","internalType":"uint256"},{"name":"flatTier2Wei_","type":"uint256","internalType":"uint256"},{"name":"flatFinalWei_","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setRoyalty","type":"function","inputs":[{"name":"receiver","type":"address","internalType":"address"},{"name":"bps","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"supportsInterface","type":"function","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"name":"symbol","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"tier2Cents","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"tokenURI","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"totalMintedBy","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"totalSupply","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"traits","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"t","type":"uint256[7]","internalType":"uint256[7]"}],"stateMutability":"view"},{"name":"transferFrom","type":"function","inputs":[{"name":"from","type":"address","internalType":"address"},{"name":"to","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"transferOwnership","type":"function","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"withdraw","type":"function","inputs":[{"name":"to","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"receive","stateMutability":"payable"}],"matchId":"57706878","creationMatch":"match","runtimeMatch":"match","verifiedAt":"2026-10-30T17:45:43Z","match":"match","chainId":"56","address":"0x546d13C0b614F4403471E6D6C4E83548e73308dc"}