{"sources":{"contracts/AgentOrb.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\n/// @notice Minimal ERC721 receiver interface (only what this file needs).\ninterface IERC721Receiver {\n    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data)\n        external\n        returns (bytes4);\n}\n\n/// @notice SPACE ID Agent ID registry (BSC). Holding at least one Agent ID NFT\n///         (balanceOf > 0) is the only mint eligibility requirement.\ninterface IAgentIdRegistry {\n    function balanceOf(address account) external view returns (uint256);\n}\n\n/// @title AgentOrb (AORB)\n/// @notice Tiered-mint ERC721 gated by Binance Agent ID (SPACE ID registry on BSC).\n///         Each wallet may mint at most 30 orbs. The price depends on the wallet's own\n///         mint index: mint #1 is free, #2-10 cost 0.0002 BNB each, #11-15 cost\n///         0.0004 each, #16-20 cost 0.0008 each, #21-30 cost 0.001 each.\n///         Every mint assigns a pseudo-random tier: 0 Pearl (55%), 1 Jade (30%),\n///         2 Gold (12%), 3 Dragon (3%). The full payment is forwarded to the\n///         treasury inside the mint transaction; any excess is refunded.\n///         No taxes, no proxy, no owner mint shortcut: the owner mints through\n///         the same public ladder as everyone else.\ncontract AgentOrb {\n    // ------------------------------------------------------------------\n    // ERC721 metadata / events\n    // ------------------------------------------------------------------\n    string public name;\n    string public symbol;\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 OrbMinted(address indexed minter, uint256 indexed tokenId, uint8 tier);\n    event MintOpenChanged(bool open);\n    event BaseURIChanged(string baseURI);\n    event Withdrawn(address indexed to, uint256 amount);\n\n    // ------------------------------------------------------------------\n    // Constants\n    // ------------------------------------------------------------------\n    address public constant AGENT_ID_REGISTRY = 0x60B9262456dE011F7d15a54CdD8c48bAaa8B022F;\n    uint256 public constant MAX_SUPPLY = 10_000;\n    uint256 public constant MAX_PER_WALLET = 30;\n\n    uint256 public constant PRICE_2_TO_10 = 0.0002 ether; // mints #2-10\n    uint256 public constant PRICE_11_TO_15 = 0.0004 ether; // mints #11-15\n    uint256 public constant PRICE_16_TO_20 = 0.0008 ether; // mints #16-20\n    uint256 public constant PRICE_21_TO_30 = 0.001 ether; // mints #21-30\n\n    uint256 public constant TIER_PEARL = 0; // weight 55\n    uint256 public constant TIER_JADE = 1; // weight 30\n    uint256 public constant TIER_GOLD = 2; // weight 12\n    uint256 public constant TIER_DRAGON = 3; // weight 3\n\n    // ------------------------------------------------------------------\n    // State\n    // ------------------------------------------------------------------\n    address public owner;\n    address public treasury;\n    string public baseURI;\n    bool public mintOpen;\n\n    uint256 public totalSupply;\n    mapping(address => uint256) public mintedCount; // per-wallet mint count (ladder position)\n    mapping(uint256 => uint8) public tierOf;\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    bool private _locked; // reentrancy guard for mint/withdraw\n\n    modifier onlyOwner() {\n        require(msg.sender == owner, \"not owner\");\n        _;\n    }\n\n    modifier nonReentrant() {\n        require(!_locked, \"reentrant\");\n        _locked = true;\n        _;\n        _locked = false;\n    }\n\n    constructor(string memory name_, string memory symbol_, address treasury_, string memory baseURI_) {\n        require(treasury_ != address(0), \"treasury zero\");\n        name = name_;\n        symbol = symbol_;\n        // Owner is the treasury wallet, NOT msg.sender: deployment goes through\n        // the Arachnid CREATE2 proxy, so msg.sender here is the proxy contract.\n        owner = treasury_;\n        treasury = treasury_;\n        baseURI = baseURI_;\n        mintOpen = false; // deploy closed; opened later with setMintOpen(true)\n    }\n\n    // ------------------------------------------------------------------\n    // Pricing\n    // ------------------------------------------------------------------\n    /// @notice Price of the mint with 1-based per-wallet index `index` (1..30).\n    function priceAt(uint256 index) public pure returns (uint256) {\n        if (index == 1) return 0;\n        if (index <= 10) return PRICE_2_TO_10;\n        if (index <= 15) return PRICE_11_TO_15;\n        if (index <= 20) return PRICE_16_TO_20;\n        return PRICE_21_TO_30; // 21..30 (and any index above, capped by mint())\n    }\n\n    /// @notice Total price for `account` to mint `count` more orbs from its current position.\n    function priceFor(address account, uint256 count) public view returns (uint256 total) {\n        uint256 start = mintedCount[account];\n        for (uint256 i = 1; i <= count; ++i) {\n            total += priceAt(start + i);\n        }\n    }\n\n    function mintedCountOf(address account) public view returns (uint256) {\n        return mintedCount[account];\n    }\n\n    // ------------------------------------------------------------------\n    // Mint\n    // ------------------------------------------------------------------\n    function mint(uint256 count) external payable nonReentrant {\n        require(mintOpen, \"mint closed\");\n        require(count > 0, \"count zero\");\n        require(IAgentIdRegistry(AGENT_ID_REGISTRY).balanceOf(msg.sender) > 0, \"no agent id\");\n\n        uint256 already = mintedCount[msg.sender];\n        require(already + count <= MAX_PER_WALLET, \"wallet cap\");\n        require(totalSupply + count <= MAX_SUPPLY, \"supply cap\");\n\n        uint256 total = priceFor(msg.sender, count);\n        require(msg.value >= total, \"insufficient payment\");\n\n        // Effects first: ladder position and supply are locked in before any\n        // external call (safe-mint receiver hooks, treasury forward, refund).\n        mintedCount[msg.sender] = already + count;\n        uint256 firstId = totalSupply + 1;\n        totalSupply += count;\n\n        for (uint256 i = 0; i < count; ++i) {\n            uint256 tokenId = firstId + i;\n            uint8 tier = _rollTier(tokenId, msg.sender);\n            tierOf[tokenId] = tier;\n            _safeMint(msg.sender, tokenId);\n            emit OrbMinted(msg.sender, tokenId, tier);\n        }\n\n        // Forward the full price to treasury in the same transaction.\n        if (total > 0) {\n            (bool ok, ) = treasury.call{value: total}(\"\");\n            require(ok, \"treasury forward failed\");\n        }\n        // Refund any excess.\n        uint256 excess = msg.value - total;\n        if (excess > 0) {\n            (bool ok, ) = msg.sender.call{value: excess}(\"\");\n            require(ok, \"refund failed\");\n        }\n    }\n\n    /// @dev Simple gimmick roll: keccak(prevrandao, blockhash(prev), caller, tokenId, timestamp).\n    function _rollTier(uint256 tokenId, address minter) internal view returns (uint8) {\n        uint256 r = uint256(\n            keccak256(abi.encodePacked(block.prevrandao, blockhash(block.number - 1), minter, tokenId, block.timestamp))\n        ) % 100;\n        if (r < 55) return 0; // Pearl 55\n        if (r < 85) return 1; // Jade 30\n        if (r < 97) return 2; // Gold 12\n        return 3; // Dragon 3\n    }\n\n    // ------------------------------------------------------------------\n    // Owner\n    // ------------------------------------------------------------------\n    function setMintOpen(bool open_) external onlyOwner {\n        mintOpen = open_;\n        emit MintOpenChanged(open_);\n    }\n\n    function setBaseURI(string calldata baseURI_) external onlyOwner {\n        baseURI = baseURI_;\n        emit BaseURIChanged(baseURI_);\n    }\n\n    /// @notice Failsafe: sweep any BNB stuck in the contract to the treasury.\n    function withdraw() external onlyOwner nonReentrant {\n        uint256 amount = address(this).balance;\n        require(amount > 0, \"nothing to withdraw\");\n        (bool ok, ) = treasury.call{value: amount}(\"\");\n        require(ok, \"withdraw failed\");\n        emit Withdrawn(treasury, amount);\n    }\n\n    // ------------------------------------------------------------------\n    // ERC721 views\n    // ------------------------------------------------------------------\n    function balanceOf(address account) public 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 tokenURI(uint256 tokenId) public view returns (string memory) {\n        require(_owners[tokenId] != address(0), \"nonexistent token\");\n        return string(abi.encodePacked(baseURI, _toString(tokenId), \".json\"));\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 isApprovedForAll(address account, address operator) public view returns (bool) {\n        return _operatorApprovals[account][operator];\n    }\n\n    function supportsInterface(bytes4 interfaceId) public pure returns (bool) {\n        return interfaceId == 0x01ffc9a7 // ERC165\n            || interfaceId == 0x80ac58cd // ERC721\n            || interfaceId == 0x5b5e139f; // ERC721Metadata\n    }\n\n    // ------------------------------------------------------------------\n    // ERC721 transfers / approvals (no taxes, plain transfers)\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        require(_isAuthorized(from, tokenId), \"not authorized\");\n        _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        require(_isAuthorized(from, tokenId), \"not authorized\");\n        _transfer(from, to, tokenId);\n        require(_checkOnERC721Received(from, to, tokenId, data), \"unsafe recipient\");\n    }\n\n    // ------------------------------------------------------------------\n    // Internals\n    // ------------------------------------------------------------------\n    function _isAuthorized(address from, uint256 tokenId) internal view returns (bool) {\n        address o = _owners[tokenId];\n        return o == from\n            && (msg.sender == o || _tokenApprovals[tokenId] == msg.sender || _operatorApprovals[o][msg.sender]);\n    }\n\n    function _transfer(address from, address to, uint256 tokenId) internal {\n        require(to != address(0), \"zero address\");\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 _safeMint(address to, uint256 tokenId) internal {\n        require(to != address(0), \"zero address\");\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, \"\"), \"unsafe recipient\");\n    }\n\n    function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data)\n        internal\n        returns (bool)\n    {\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    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) {\n            ++digits;\n            temp /= 10;\n        }\n        bytes memory buffer = new bytes(digits);\n        while (value != 0) {\n            --digits;\n            buffer[digits] = bytes1(uint8(48 + (value % 10)));\n            value /= 10;\n        }\n        return string(buffer);\n    }\n\n    receive() external payable {}\n}\n"}},"abi":[{"type":"constructor","inputs":[{"name":"name_","type":"string","internalType":"string"},{"name":"symbol_","type":"string","internalType":"string"},{"name":"treasury_","type":"address","internalType":"address"},{"name":"baseURI_","type":"string","internalType":"string"}],"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":"BaseURIChanged","type":"event","inputs":[{"name":"baseURI","type":"string","indexed":false,"internalType":"string"}],"anonymous":false},{"name":"MintOpenChanged","type":"event","inputs":[{"name":"open","type":"bool","indexed":false,"internalType":"bool"}],"anonymous":false},{"name":"OrbMinted","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"}],"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":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"AGENT_ID_REGISTRY","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"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":"PRICE_11_TO_15","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"PRICE_16_TO_20","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"PRICE_21_TO_30","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"PRICE_2_TO_10","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"TIER_DRAGON","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"TIER_GOLD","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"TIER_JADE","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"TIER_PEARL","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"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":"baseURI","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"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":"mint","type":"function","inputs":[{"name":"count","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"payable"},{"name":"mintOpen","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"mintedCount","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"mintedCountOf","type":"function","inputs":[{"name":"account","type":"address","internalType":"address"}],"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":"priceAt","type":"function","inputs":[{"name":"index","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"name":"priceFor","type":"function","inputs":[{"name":"account","type":"address","internalType":"address"},{"name":"count","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"total","type":"uint256","internalType":"uint256"}],"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":"setApprovalForAll","type":"function","inputs":[{"name":"operator","type":"address","internalType":"address"},{"name":"approved","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setBaseURI","type":"function","inputs":[{"name":"baseURI_","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setMintOpen","type":"function","inputs":[{"name":"open_","type":"bool","internalType":"bool"}],"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":"tierOf","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"view"},{"name":"tokenURI","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"totalSupply","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":"treasury","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"withdraw","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"receive","stateMutability":"payable"}],"matchId":"58120321","creationMatch":"exact_match","runtimeMatch":"exact_match","verifiedAt":"2026-11-02T19:53:08Z","match":"exact_match","chainId":"56","address":"0x9dA90FE302a987d979C21d7c3Ae421cB0b2cf8b0"}