{"sources":{"contracts/interfaces/Interfaces.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\n/// @dev Shared interfaces for the ONI suite. OniToken.sol keeps its own\n///      PancakeSwap interface declarations (ported verbatim from the ORE\n///      token); these are the cross-contract views the other ONI contracts use.\n\ninterface IERC20Mini {\n    function balanceOf(address account) external view returns (uint256);\n    function transfer(address to, uint256 amount) external returns (bool);\n    function transferFrom(address from, address to, uint256 amount) external returns (bool);\n    function approve(address spender, uint256 amount) external returns (bool);\n}\n\ninterface IWBNB {\n    function deposit() external payable;\n    function transfer(address to, uint256 amount) external returns (bool);\n    function balanceOf(address account) external view returns (uint256);\n}\n\ninterface IPcsFactory {\n    function getPair(address a, address b) external view returns (address);\n    function createPair(address a, address b) external returns (address);\n}\n\ninterface IPcsPair {\n    function mint(address to) external returns (uint256 liquidity);\n    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);\n    function token0() external view returns (address);\n    function token1() external view returns (address);\n}\n\ninterface IPcsRouter {\n    function factory() external view returns (address);\n    function WETH() external view returns (address);\n    function swapExactETHForTokensSupportingFeeOnTransferTokens(\n        uint256 amountOutMin,\n        address[] calldata path,\n        address to,\n        uint256 deadline\n    ) external payable;\n}\n\n/// @dev SPACE ID Agent-ID registry (BSC 0x60B9262456dE011F7d15a54CdD8c48bAaa8B022F):\n///      an ERC-721 whose balanceOf gates Agent-ID holders.\ninterface IAgentIdRegistry {\n    function balanceOf(address owner) external view returns (uint256);\n}\n\ninterface IOniToken {\n    function balanceOf(address account) external view returns (uint256);\n    function totalSupply() external view returns (uint256);\n    function transfer(address to, uint256 amount) external returns (bool);\n    function transferFrom(address from, address to, uint256 amount) external returns (bool);\n    function approve(address spender, uint256 amount) external returns (bool);\n    function ownerMint(address to, uint256 amount) external;\n    function openTrading() external;\n    function setAbandonTime(uint256 t) external;\n    function registerFor(address account) external;\n    function pair() external view returns (address);\n    function tradingOpen() external view returns (bool);\n}\n\ninterface IOniIEO {\n    function launched() external view returns (bool);\n    function launchedAt() external view returns (uint256);\n    function sold() external view returns (uint256);\n    /// @return BNB wei per whole token (1e18 token-wei), marginal curve price.\n    function currentPrice() external view returns (uint256);\n}\n\ninterface IOniMiningPool {\n    function stakeOf(address user) external view returns (uint256);\n    function isStaked(address user) external view returns (bool);\n}\n"},"contracts/OniNFT.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport {IERC20Mini, IAgentIdRegistry} from \"./interfaces/Interfaces.sol\";\n\ninterface IPcsFactoryMini {\n    function getPair(address a, address b) external view returns (address);\n    function createPair(address a, address b) external returns (address);\n}\n\ninterface IPcsRouterLiq {\n    function addLiquidityETH(\n        address token,\n        uint256 amountTokenDesired,\n        uint256 amountTokenMin,\n        uint256 amountETHMin,\n        address to,\n        uint256 deadline\n    ) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);\n}\n\n/**\n * @title OniNFT — \"The 4,444 Oni\"\n * @notice ERC-721 collection of generative on-chain SVG oni (Japanese legend),\n *         gated to Binance Agent ID holders. Also hosts the ONI token claim\n *         and the PancakeSwap pool seeding, so no fourth contract is needed.\n *\n * Rarity is a pure function of tokenId ranges, which makes tier depletion\n * exact with simple counters (no id arrays):\n *   Common 1..2667 · Rare 2668..3778 · Epic 3779..4222 ·\n *   Legendary 4223..4400 · Mythic 4401..4444\n * Random mints draw a tier weighted by its remaining count (Mythic is never\n * in the random pool — it is sold directly). Ids are issued sequentially\n * inside the drawn tier's range.\n *\n * Mint paths (quota per Agent ID wallet: 1 free + 10 paid in total):\n *   - mintFree()   first 1,000 random mints, 1 per wallet, gas only\n *   - mintPaid()   0.001 BNB, random Common–Legendary, 3,400 pool\n *   - mintMythic() 0.01 BNB, direct Mythic sale, 44 pool\n * All mint BNB stays in this contract; the only way BNB leaves is seedPool().\n *\n * Claim: this contract holds the token's claim bucket (7,600,000) and\n * liquidity bucket (8,400,000), kept apart by `liquidityReserve` accounting.\n * claim(tokenId) pays the rarity amount to the token's CURRENT owner, once\n * per tokenId, any time after mint until sellout + 30 days. After that\n * deadline the unclaimed remainder can be swept to the liquidity reserve.\n */\ncontract OniNFT {\n    // ─── ERC-721 metadata ──────────────────────────────────────────────────\n    string public constant name = \"ONI\";\n    string public constant symbol = \"ONI\";\n\n    // ─── Collection constants ──────────────────────────────────────────────\n    uint256 public constant MAX_SUPPLY = 4444;\n    uint256 public constant FREE_POOL = 1000;\n    uint256 public constant PAID_POOL = 3400;\n    uint256 public constant PAID_PRICE = 0.001 ether;\n    uint256 public constant MYTHIC_PRICE = 0.01 ether;\n    uint256 public constant MAX_PAID_PER_WALLET = 10;\n\n    uint8 public constant TIER_COMMON = 0;\n    uint8 public constant TIER_RARE = 1;\n    uint8 public constant TIER_EPIC = 2;\n    uint8 public constant TIER_LEGENDARY = 3;\n    uint8 public constant TIER_MYTHIC = 4;\n\n    // tier sizes and first tokenId per tier (index = tier)\n    uint256 internal constant T0 = 2667; uint256 internal constant S0 = 1;\n    uint256 internal constant T1 = 1111; uint256 internal constant S1 = 2668;\n    uint256 internal constant T2 = 444;  uint256 internal constant S2 = 3779;\n    uint256 internal constant T3 = 178;  uint256 internal constant S3 = 4223;\n    uint256 internal constant T4 = 44;   uint256 internal constant S4 = 4401;\n\n    // claim amounts per tier (whole ONI, 18 dp applied at payout)\n    uint256 internal constant C0 = 760;\n    uint256 internal constant C1 = 1520;\n    uint256 internal constant C2 = 3800;\n    uint256 internal constant C3 = 7600;\n    uint256 internal constant C4 = 19000;\n\n    uint256 public constant CLAIM_WINDOW = 30 days;\n    uint256 public constant LIQUIDITY_BUCKET = 8_400_000 ether;\n    uint256 public constant SEED_BNB = 3 ether;\n    uint256 public constant SEED_TOKENS = 3_281_250 ether;\n    address public constant DEAD = 0x000000000000000000000000000000000000dEaD;\n\n    // ─── Wiring ────────────────────────────────────────────────────────────\n    IAgentIdRegistry public immutable registry;\n    IPcsRouterLiq public immutable router;\n    IPcsFactoryMini public immutable factory;\n    address public immutable wbnb;\n    address public owner;\n    IERC20Mini public token; // set once via setToken\n\n    // ─── ERC-721 state ─────────────────────────────────────────────────────\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    // ─── Sale / claim state ────────────────────────────────────────────────\n    uint256 public totalMinted;\n    uint256 public freeMinted;\n    uint256 public paidMinted;    // random paid draws (pool of 3,400)\n    uint256 public mythicMinted;  // direct Mythic sales (pool of 44)\n    uint256[5] public tierMinted;\n    mapping(address => bool) public freeUsed;\n    mapping(address => uint256) public paidCount; // paid + mythic buys per wallet\n    mapping(uint256 => bool) public claimed;\n    uint256 public totalClaimedPaid;\n    uint256 public mintOpensAt;   // 0 = open; owner may set/reset it until the first mint\n    uint256 public selloutTime;   // set once when totalMinted hits 4,444\n    uint256 public liquidityReserve = LIQUIDITY_BUCKET;\n    bool public claimSwept;\n    bool public poolSeeded;\n    address public pair;\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    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n    event TokenSet(address indexed token);\n    event MintOpensAtSet(uint256 timestamp);\n    event Minted(address indexed minter, uint256 indexed tokenId, uint8 tier, uint8 kind); // kind: 0 free, 1 paid, 2 mythic\n    event Claimed(address indexed owner, uint256 indexed tokenId, uint8 tier, uint256 amount);\n    event SoldOut(uint256 timestamp);\n    event ClaimSwept(address indexed to, uint256 amount);\n    event PoolSeeded(address indexed pair, uint256 bnb, uint256 tokens);\n\n    modifier onlyOwner() {\n        require(msg.sender == owner, \"Not owner\");\n        _;\n    }\n\n    constructor(address _registry, address _router, address _factory, address _wbnb, address _initialOwner) {\n        require(_registry != address(0) && _router != address(0) && _factory != address(0) && _wbnb != address(0) && _initialOwner != address(0), \"Invalid\");\n        registry = IAgentIdRegistry(_registry);\n        router = IPcsRouterLiq(_router);\n        factory = IPcsFactoryMini(_factory);\n        wbnb = _wbnb;\n        owner = _initialOwner;\n    }\n\n    function transferOwnership(address newOwner) external onlyOwner {\n        require(newOwner != address(0), \"Invalid\");\n        emit OwnershipTransferred(owner, newOwner);\n        owner = newOwner;\n    }\n\n    /// @notice Set the mint opening time. The owner may set or reset it any\n    ///         time before the first mint; once minting has started, the\n    ///         opening time is locked forever.\n    function setMintOpensAt(uint256 t) external onlyOwner {\n        require(totalMinted == 0, \"Mint already started\");\n        require(t > block.timestamp, \"Open time in past\");\n        mintOpensAt = t;\n        emit MintOpensAtSet(t);\n    }\n\n    /// @notice Wire the ONI token once (it mints the claim + liquidity buckets to this contract).\n    function setToken(address _token) external onlyOwner {\n        require(address(token) == address(0) && _token != address(0), \"Token already set\");\n        token = IERC20Mini(_token);\n        emit TokenSet(_token);\n    }\n\n    // ─── Views ─────────────────────────────────────────────────────────────\n\n    function _tierSize(uint8 tier) internal pure returns (uint256) {\n        if (tier == 0) return T0; if (tier == 1) return T1; if (tier == 2) return T2;\n        if (tier == 3) return T3; return T4;\n    }\n\n    function _tierStart(uint8 tier) internal pure returns (uint256) {\n        if (tier == 0) return S0; if (tier == 1) return S1; if (tier == 2) return S2;\n        if (tier == 3) return S3; return S4;\n    }\n\n    /// @notice Rarity tier of any tokenId in 1..4444 (pure range function).\n    function rarityOf(uint256 tokenId) public pure returns (uint8) {\n        require(tokenId >= 1 && tokenId <= MAX_SUPPLY, \"Bad id\");\n        if (tokenId < S1) return TIER_COMMON;\n        if (tokenId < S2) return TIER_RARE;\n        if (tokenId < S3) return TIER_EPIC;\n        if (tokenId < S4) return TIER_LEGENDARY;\n        return TIER_MYTHIC;\n    }\n\n    function claimAmountOf(uint256 tokenId) public pure returns (uint256) {\n        uint8 t = rarityOf(tokenId);\n        if (t == 0) return C0 * 1 ether; if (t == 1) return C1 * 1 ether;\n        if (t == 2) return C2 * 1 ether; if (t == 3) return C3 * 1 ether;\n        return C4 * 1 ether;\n    }\n\n    function randomRemaining() public view returns (uint256) {\n        return (T0 + T1 + T2 + T3) - (tierMinted[0] + tierMinted[1] + tierMinted[2] + tierMinted[3]);\n    }\n\n    function claimDeadline() public view returns (uint256) {\n        return selloutTime == 0 ? 0 : selloutTime + CLAIM_WINDOW;\n    }\n\n    // ─── Minting ───────────────────────────────────────────────────────────\n\n    function _requireAgentId() internal view {\n        require(registry.balanceOf(msg.sender) > 0, \"Agent ID required\");\n    }\n\n    function _requireMintOpen() internal view {\n        require(mintOpensAt == 0 || block.timestamp >= mintOpensAt, \"Mint not open yet\");\n    }\n\n    /// @dev Weighted draw over the remaining Common–Legendary pool.\n    function _drawTier() internal returns (uint8) {\n        uint256 r0 = T0 - tierMinted[0];\n        uint256 r1 = T1 - tierMinted[1];\n        uint256 r2 = T2 - tierMinted[2];\n        uint256 r3 = T3 - tierMinted[3];\n        uint256 total = r0 + r1 + r2 + r3;\n        require(total > 0, \"Random pool empty\");\n        uint256 seed = uint256(keccak256(abi.encodePacked(\n            block.prevrandao, blockhash(block.number - 1), msg.sender, totalMinted, address(this)\n        )));\n        uint256 roll = seed % total;\n        if (roll < r0) return TIER_COMMON;\n        roll -= r0;\n        if (roll < r1) return TIER_RARE;\n        roll -= r1;\n        if (roll < r2) return TIER_EPIC;\n        return TIER_LEGENDARY;\n    }\n\n    function _issue(address to, uint8 tier, uint8 kind) internal returns (uint256 tokenId) {\n        tokenId = _tierStart(tier) + tierMinted[tier];\n        tierMinted[tier] += 1;\n        totalMinted += 1;\n        _mint(to, tokenId);\n        emit Minted(to, tokenId, tier, kind);\n        if (totalMinted == MAX_SUPPLY && selloutTime == 0) {\n            selloutTime = block.timestamp;\n            emit SoldOut(block.timestamp);\n        }\n    }\n\n    /// @notice Free random mint: first 1,000 draws, 1 per Agent ID wallet.\n    function mintFree() external returns (uint256 tokenId) {\n        _requireMintOpen();\n        _requireAgentId();\n        require(!freeUsed[msg.sender], \"Free already used\");\n        require(freeMinted < FREE_POOL, \"Free pool exhausted\");\n        freeUsed[msg.sender] = true;\n        freeMinted += 1;\n        tokenId = _issue(msg.sender, _drawTier(), 0);\n    }\n\n    /// @notice Paid random mint at 0.001 BNB (Common–Legendary), pool of 3,400.\n    function mintPaid() external payable returns (uint256 tokenId) {\n        _requireMintOpen();\n        _requireAgentId();\n        require(msg.value == PAID_PRICE, \"Price is 0.001 BNB\");\n        require(paidCount[msg.sender] < MAX_PAID_PER_WALLET, \"Paid quota reached\");\n        require(paidMinted < PAID_POOL, \"Paid pool exhausted\");\n        paidCount[msg.sender] += 1;\n        paidMinted += 1;\n        tokenId = _issue(msg.sender, _drawTier(), 1);\n    }\n\n    /// @notice Direct Mythic sale at 0.01 BNB — the only way to get a Mythic.\n    function mintMythic() external payable returns (uint256 tokenId) {\n        _requireMintOpen();\n        _requireAgentId();\n        require(msg.value == MYTHIC_PRICE, \"Price is 0.01 BNB\");\n        require(paidCount[msg.sender] < MAX_PAID_PER_WALLET, \"Paid quota reached\");\n        require(mythicMinted < T4, \"Mythic sold out\");\n        paidCount[msg.sender] += 1;\n        mythicMinted += 1;\n        tokenId = _issue(msg.sender, TIER_MYTHIC, 2);\n    }\n\n    // ─── Claim ─────────────────────────────────────────────────────────────\n\n    /// @notice Claim this NFT's ONI (by rarity) to its current owner. Once per tokenId.\n    function claim(uint256 tokenId) external {\n        require(address(token) != address(0), \"Token not set\");\n        require(ownerOf(tokenId) == msg.sender, \"Not token owner\");\n        require(!claimed[tokenId], \"Already claimed\");\n        if (selloutTime != 0) {\n            require(block.timestamp <= selloutTime + CLAIM_WINDOW, \"Claim closed\");\n        }\n        uint256 amount = claimAmountOf(tokenId);\n        claimed[tokenId] = true;\n        totalClaimedPaid += amount;\n        require(token.transfer(msg.sender, amount), \"Claim transfer failed\");\n        emit Claimed(msg.sender, tokenId, rarityOf(tokenId), amount);\n    }\n\n    /// @notice After the claim deadline, sweep the unclaimed claim-bucket\n    ///         remainder to the liquidity reserve address.\n    function sweepUnclaimed(address to) external onlyOwner {\n        require(address(token) != address(0), \"Token not set\");\n        require(selloutTime != 0 && block.timestamp > selloutTime + CLAIM_WINDOW, \"Claim still open\");\n        require(!claimSwept, \"Already swept\");\n        require(to != address(0), \"Invalid\");\n        uint256 bal = token.balanceOf(address(this));\n        require(bal >= liquidityReserve, \"Accounting broken\");\n        uint256 amount = bal - liquidityReserve;\n        claimSwept = true;\n        if (amount > 0) {\n            require(token.transfer(to, amount), \"Sweep failed\");\n        }\n        emit ClaimSwept(to, amount);\n    }\n\n    // ─── Pool seeding ──────────────────────────────────────────────────────\n\n    /// @notice Seed the PancakeSwap pool: 3 BNB from the mint proceeds held\n    ///         by this contract + 3,281,250 ONI from the liquidity reserve,\n    ///         LP burned to the dead address.\n    function seedPool() external onlyOwner {\n        require(address(token) != address(0), \"Token not set\");\n        require(!poolSeeded, \"Already seeded\");\n        require(selloutTime != 0 && block.timestamp >= selloutTime + 24 hours, \"Too early: sellout + 24h\");\n        require(address(this).balance >= SEED_BNB, \"Insufficient BNB\");\n        require(liquidityReserve >= SEED_TOKENS, \"Insufficient reserve\");\n\n        address p = factory.getPair(address(token), wbnb);\n        if (p == address(0)) {\n            p = factory.createPair(address(token), wbnb);\n        }\n        pair = p;\n        poolSeeded = true;\n        liquidityReserve -= SEED_TOKENS;\n\n        require(token.approve(address(router), SEED_TOKENS), \"Approve failed\");\n        router.addLiquidityETH{value: SEED_BNB}(\n            address(token), SEED_TOKENS, 0, 0, DEAD, block.timestamp\n        );\n        emit PoolSeeded(p, SEED_BNB, SEED_TOKENS);\n    }\n\n    receive() external payable {}\n\n    // ─── ERC-721 core ──────────────────────────────────────────────────────\n\n    function balanceOf(address account) external view returns (uint256) {\n        require(account != address(0), \"Zero address\");\n        return _balances[account];\n    }\n\n    function ownerOf(uint256 tokenId) public view returns (address) {\n        address o = _owners[tokenId];\n        require(o != address(0), \"Nonexistent token\");\n        return o;\n    }\n\n    function approve(address to, uint256 tokenId) external {\n        address o = ownerOf(tokenId);\n        require(to != o, \"Approve to owner\");\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 getApproved(uint256 tokenId) public view returns (address) {\n        require(_owners[tokenId] != address(0), \"Nonexistent token\");\n        return _tokenApprovals[tokenId];\n    }\n\n    function setApprovalForAll(address operator, bool approved) external {\n        require(operator != msg.sender, \"Approve to caller\");\n        _operatorApprovals[msg.sender][operator] = approved;\n        emit ApprovalForAll(msg.sender, operator, approved);\n    }\n\n    function isApprovedForAll(address account, address operator) public view returns (bool) {\n        return _operatorApprovals[account][operator];\n    }\n\n    function transferFrom(address from, address to, uint256 tokenId) public {\n        require(_isApprovedOrOwner(msg.sender, tokenId), \"Not authorized\");\n        _transfer(from, to, tokenId);\n    }\n\n    function safeTransferFrom(address from, address to, uint256 tokenId) public {\n        safeTransferFrom(from, to, tokenId, \"\");\n    }\n\n    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public {\n        require(_isApprovedOrOwner(msg.sender, tokenId), \"Not authorized\");\n        _transfer(from, to, tokenId);\n        require(_checkOnERC721Received(from, to, tokenId, data), \"Receiver rejected\");\n    }\n\n    function supportsInterface(bytes4 interfaceId) external pure returns (bool) {\n        return interfaceId == 0x80ac58cd || interfaceId == 0x5b5e139f || interfaceId == 0x01ffc9a7;\n    }\n\n    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {\n        address o = ownerOf(tokenId);\n        return spender == o || _tokenApprovals[tokenId] == spender || _operatorApprovals[o][spender];\n    }\n\n    function _mint(address to, uint256 tokenId) internal {\n        require(to != address(0), \"Mint to zero\");\n        require(_owners[tokenId] == address(0), \"Already minted\");\n        _balances[to] += 1;\n        _owners[tokenId] = to;\n        emit Transfer(address(0), to, tokenId);\n        require(_checkOnERC721Received(address(0), to, tokenId, \"\"), \"Receiver rejected\");\n    }\n\n    function _transfer(address from, address to, uint256 tokenId) internal {\n        require(ownerOf(tokenId) == from, \"From not owner\");\n        require(to != address(0), \"Transfer to zero\");\n        delete _tokenApprovals[tokenId];\n        _balances[from] -= 1;\n        _balances[to] += 1;\n        _owners[tokenId] = to;\n        emit Transfer(from, to, tokenId);\n    }\n\n    function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private returns (bool) {\n        if (to.code.length == 0) return true;\n        try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {\n            return retval == IERC721Receiver.onERC721Received.selector;\n        } catch {\n            return false;\n        }\n    }\n\n    // ─── On-chain generative art ───────────────────────────────────────────\n\n    function _seedOf(uint256 tokenId) internal pure returns (uint256) {\n        return uint256(keccak256(abi.encodePacked(\"ONI-SEED\", tokenId)));\n    }\n\n    function _skin(uint256 i) internal pure returns (string memory color, string memory label) {\n        if (i == 0) return (\"#d93a2b\", \"Aka\");\n        if (i == 1) return (\"#3b6fd4\", \"Ao\");\n        if (i == 2) return (\"#3fa34d\", \"Midori\");\n        if (i == 3) return (\"#33333d\", \"Kuro\");\n        return (\"#e8b93c\", \"Kin\");\n    }\n\n    function _eyes(uint256 i) internal pure returns (string memory color, string memory label) {\n        if (i == 0) return (\"#ffd23c\", \"Gold\");\n        if (i == 1) return (\"#ff5030\", \"Ember\");\n        if (i == 2) return (\"#7ce7ff\", \"Ice\");\n        return (\"#c792ff\", \"Violet\");\n    }\n\n    function _bg(uint256 i) internal pure returns (string memory color, string memory label) {\n        if (i == 0) return (\"#0d1026\", \"Night\");\n        if (i == 1) return (\"#1a0b2e\", \"Dusk\");\n        if (i == 2) return (\"#26060d\", \"Blood Moon\");\n        return (\"#04141c\", \"Deep Sea\");\n    }\n\n    function _aura(uint256 i) internal pure returns (string memory color, string memory label) {\n        if (i == 0) return (\"#ff5a2b\", \"Flame\");\n        if (i == 1) return (\"#3fa9ff\", \"Storm\");\n        if (i == 2) return (\"#b46bff\", \"Spirit\");\n        return (\"#3dff8f\", \"Jade\");\n    }\n\n    function _kanabo(uint256 i) internal pure returns (string memory color, string memory label) {\n        if (i == 0) return (\"#4a4a52\", \"Iron\");\n        if (i == 1) return (\"#8c6a2f\", \"Bronze\");\n        return (\"#d4af37\", \"Gold\");\n    }\n\n    function _horns(uint256 i) internal pure returns (string memory path, string memory label) {\n        if (i == 0) return (\"M148,110 L136,64 L170,90 Z M252,110 L264,64 L230,90 Z\", \"Short\");\n        if (i == 1) return (\"M146,112 C124,76 130,42 158,26 C146,60 152,86 166,100 Z M254,112 C276,76 270,42 242,26 C254,60 248,86 234,100 Z\", \"Curved\");\n        return (\"M144,114 C112,70 122,26 160,8 C150,52 156,88 172,102 Z M256,114 C288,70 278,26 240,8 C250,52 244,88 228,102 Z\", \"Grand\");\n    }\n\n    function _tierName(uint8 t) internal pure returns (string memory) {\n        if (t == 0) return \"Common\";\n        if (t == 1) return \"Rare\";\n        if (t == 2) return \"Epic\";\n        if (t == 3) return \"Legendary\";\n        return \"Mythic\";\n    }\n\n    function _svg(uint256 tokenId) internal pure returns (string memory) {\n        uint256 seed = _seedOf(tokenId);\n        bytes memory s;\n        {\n            (string memory bg, ) = _bg((seed >> 16) % 4);\n            (string memory aura, ) = _aura((seed >> 24) % 4);\n            s = abi.encodePacked(\n                '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 400\">',\n                '<rect width=\"400\" height=\"400\" fill=\"', bg, '\"/>',\n                '<circle cx=\"200\" cy=\"148\" r=\"106\" fill=\"#c1272d\" opacity=\"0.8\"/>',\n                '<g fill=\"#12060a\"><rect x=\"90\" y=\"54\" width=\"220\" height=\"14\" rx=\"5\"/><rect x=\"110\" y=\"82\" width=\"180\" height=\"10\" rx=\"4\"/><rect x=\"126\" y=\"68\" width=\"12\" height=\"124\"/><rect x=\"262\" y=\"68\" width=\"12\" height=\"124\"/></g>',\n                '<g fill=\"', aura, '\" opacity=\"0.5\"><path d=\"M104,330 C84,256 126,214 114,158 C148,208 148,262 134,330 Z\"/><path d=\"M296,330 C316,256 274,214 286,158 C252,208 252,262 266,330 Z\"/><path d=\"M200,318 C176,240 200,196 192,140 C224,196 226,252 214,318 Z\"/></g>'\n            );\n        }\n        {\n            (string memory club, ) = _kanabo((seed >> 32) % 3);\n            (string memory skin, ) = _skin(seed % 5);\n            (string memory horns, ) = _horns((seed >> 40) % 3);\n            s = abi.encodePacked(\n                s,\n                '<g transform=\"rotate(22 326 300)\"><rect x=\"314\" y=\"176\" width=\"24\" height=\"156\" rx=\"11\" fill=\"', club, '\"/><g fill=\"#e8e8e8\"><circle cx=\"326\" cy=\"200\" r=\"4\"/><circle cx=\"326\" cy=\"228\" r=\"4\"/><circle cx=\"326\" cy=\"256\" r=\"4\"/><circle cx=\"326\" cy=\"284\" r=\"4\"/></g></g>',\n                '<ellipse cx=\"200\" cy=\"208\" rx=\"114\" ry=\"120\" fill=\"#17171f\"/>',\n                '<ellipse cx=\"200\" cy=\"216\" rx=\"88\" ry=\"98\" fill=\"', skin, '\"/>',\n                '<path d=\"', horns, '\" fill=\"#e8e0cc\" stroke=\"#8a7f63\" stroke-width=\"2\"/>'\n            );\n        }\n        {\n            (string memory eye, ) = _eyes((seed >> 8) % 4);\n            s = abi.encodePacked(\n                s,\n                '<path d=\"M130,196 L184,209\" stroke=\"#14100c\" stroke-width=\"9\" stroke-linecap=\"round\"/>',\n                '<path d=\"M270,196 L216,209\" stroke=\"#14100c\" stroke-width=\"9\" stroke-linecap=\"round\"/>',\n                '<ellipse cx=\"162\" cy=\"223\" rx=\"17\" ry=\"11\" fill=\"#ffffff\"/><ellipse cx=\"238\" cy=\"223\" rx=\"17\" ry=\"11\" fill=\"#ffffff\"/>',\n                '<circle cx=\"162\" cy=\"223\" r=\"6\" fill=\"', eye, '\"/><circle cx=\"238\" cy=\"223\" r=\"6\" fill=\"', eye, '\"/>',\n                '<path d=\"M192,246 Q200,252 208,246\" stroke=\"#14100c\" stroke-width=\"4\" fill=\"none\" stroke-linecap=\"round\"/>',\n                '<path d=\"M156,272 Q200,294 244,272 L239,288 Q200,304 161,288 Z\" fill=\"#2a0d0d\"/>',\n                '<polygon points=\"170,277 179,277 174,293\" fill=\"#ffffff\"/><polygon points=\"221,277 230,277 226,293\" fill=\"#ffffff\"/>',\n                '<polygon points=\"188,292 195,292 191,282\" fill=\"#ffffff\"/><polygon points=\"205,292 212,292 209,282\" fill=\"#ffffff\"/>',\n                '</svg>'\n            );\n        }\n        return string(s);\n    }\n\n    function _attributes(uint256 tokenId) internal pure returns (string memory) {\n        uint256 seed = _seedOf(tokenId);\n        (, string memory skinName) = _skin(seed % 5);\n        (, string memory eyeName) = _eyes((seed >> 8) % 4);\n        (, string memory bgName) = _bg((seed >> 16) % 4);\n        (, string memory auraName) = _aura((seed >> 24) % 4);\n        (, string memory clubName) = _kanabo((seed >> 32) % 3);\n        (, string memory hornName) = _horns((seed >> 40) % 3);\n        bytes memory a = abi.encodePacked(\n            '[{\"trait_type\":\"Rarity\",\"value\":\"', _tierName(rarityOf(tokenId)), '\"},',\n            '{\"trait_type\":\"Skin\",\"value\":\"', skinName, '\"},',\n            '{\"trait_type\":\"Eyes\",\"value\":\"', eyeName, '\"},',\n            '{\"trait_type\":\"Background\",\"value\":\"', bgName, '\"}'\n        );\n        return string(abi.encodePacked(\n            a,\n            ',{\"trait_type\":\"Aura\",\"value\":\"', auraName, '\"},',\n            '{\"trait_type\":\"Kanabo\",\"value\":\"', clubName, '\"},',\n            '{\"trait_type\":\"Horns\",\"value\":\"', hornName, '\"}]'\n        ));\n    }\n\n    function tokenURI(uint256 tokenId) public view returns (string memory) {\n        require(_owners[tokenId] != address(0), \"Nonexistent token\");\n        string memory json = string(abi.encodePacked(\n            '{\"name\":\"ONI #', _toString(tokenId),\n            '\",\"description\":\"The 4,444 Oni - generative oni of Japanese legend, living fully on-chain. Holding an Oni unlocks a claim of ONI tokens by rarity.\",',\n            '\"image\":\"data:image/svg+xml;base64,', _b64(bytes(_svg(tokenId))), '\",',\n            '\"attributes\":', _attributes(tokenId), '}'\n        ));\n        return string(abi.encodePacked(\"data:application/json;base64,\", _b64(bytes(json))));\n    }\n\n    function _toString(uint256 value) internal pure returns (string memory) {\n        if (value == 0) return \"0\";\n        uint256 temp = value;\n        uint256 digits;\n        while (temp != 0) { digits++; temp /= 10; }\n        bytes memory buffer = new bytes(digits);\n        while (value != 0) {\n            digits -= 1;\n            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n            value /= 10;\n        }\n        return string(buffer);\n    }\n\n    /// @dev Base64 encode (pure Solidity; only used by the tokenURI view).\n    function _b64(bytes memory data) internal pure returns (string memory) {\n        bytes memory table = bytes(\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\");\n        uint256 len = data.length;\n        if (len == 0) return \"\";\n        bytes memory result = new bytes(4 * ((len + 2) / 3));\n        uint256 j = 0;\n        for (uint256 i = 0; i < len; i += 3) {\n            uint256 a = uint8(data[i]);\n            uint256 b = i + 1 < len ? uint8(data[i + 1]) : 0;\n            uint256 c = i + 2 < len ? uint8(data[i + 2]) : 0;\n            uint256 n = (a << 16) | (b << 8) | c;\n            result[j++] = table[(n >> 18) & 63];\n            result[j++] = table[(n >> 12) & 63];\n            result[j++] = i + 1 < len ? table[(n >> 6) & 63] : bytes1(\"=\");\n            result[j++] = i + 2 < len ? table[n & 63] : bytes1(\"=\");\n        }\n        return string(result);\n    }\n}\n\ninterface IERC721Receiver {\n    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);\n}\n"}},"abi":[{"type":"constructor","inputs":[{"name":"_registry","type":"address","internalType":"address"},{"name":"_router","type":"address","internalType":"address"},{"name":"_factory","type":"address","internalType":"address"},{"name":"_wbnb","type":"address","internalType":"address"},{"name":"_initialOwner","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"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":"ClaimSwept","type":"event","inputs":[{"name":"to","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"Claimed","type":"event","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"tier","type":"uint8","indexed":false,"internalType":"uint8"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"MintOpensAtSet","type":"event","inputs":[{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"Minted","type":"event","inputs":[{"name":"minter","type":"address","indexed":true,"internalType":"address"},{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"tier","type":"uint8","indexed":false,"internalType":"uint8"},{"name":"kind","type":"uint8","indexed":false,"internalType":"uint8"}],"anonymous":false},{"name":"OwnershipTransferred","type":"event","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"name":"PoolSeeded","type":"event","inputs":[{"name":"pair","type":"address","indexed":true,"internalType":"address"},{"name":"bnb","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"tokens","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"SoldOut","type":"event","inputs":[{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"TokenSet","type":"event","inputs":[{"name":"token","type":"address","indexed":true,"internalType":"address"}],"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":"CLAIM_WINDOW","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"DEAD","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"FREE_POOL","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"LIQUIDITY_BUCKET","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"MAX_PAID_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":"MYTHIC_PRICE","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"PAID_POOL","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"PAID_PRICE","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"SEED_BNB","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"SEED_TOKENS","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"TIER_COMMON","type":"function","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"view"},{"name":"TIER_EPIC","type":"function","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"view"},{"name":"TIER_LEGENDARY","type":"function","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"view"},{"name":"TIER_MYTHIC","type":"function","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"view"},{"name":"TIER_RARE","type":"function","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"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":"claim","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"claimAmountOf","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"name":"claimDeadline","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"claimSwept","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"claimed","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"factory","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IPcsFactoryMini"}],"stateMutability":"view"},{"name":"freeMinted","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"freeUsed","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"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":"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":"liquidityReserve","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"mintFree","type":"function","inputs":[],"outputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"name":"mintMythic","type":"function","inputs":[],"outputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"stateMutability":"payable"},{"name":"mintOpensAt","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"mintPaid","type":"function","inputs":[],"outputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"stateMutability":"payable"},{"name":"mythicMinted","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"name","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"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":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"paidCount","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"paidMinted","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"pair","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"poolSeeded","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"randomRemaining","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"rarityOf","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"pure"},{"name":"registry","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IAgentIdRegistry"}],"stateMutability":"view"},{"name":"router","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IPcsRouterLiq"}],"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":"seedPool","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"name":"selloutTime","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"setApprovalForAll","type":"function","inputs":[{"name":"operator","type":"address","internalType":"address"},{"name":"approved","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setMintOpensAt","type":"function","inputs":[{"name":"t","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setToken","type":"function","inputs":[{"name":"_token","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"supportsInterface","type":"function","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"name":"sweepUnclaimed","type":"function","inputs":[{"name":"to","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"symbol","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"tierMinted","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"token","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IERC20Mini"}],"stateMutability":"view"},{"name":"tokenURI","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"totalClaimedPaid","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"totalMinted","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"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":"wbnb","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"receive","stateMutability":"payable"}],"matchId":"58699730","creationMatch":"exact_match","runtimeMatch":"exact_match","verifiedAt":"2026-11-07T01:53:09Z","match":"exact_match","chainId":"56","address":"0xd576F638b5eF8906C13CE9Ca534113DdBd0a9038"}