{"sources":{"src/M3MONFT.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\nimport {IAgentIdRegistry} from \"./Interfaces.sol\";\n\n/// @notice Minimal ERC-721 receiver interface for safe transfers.\ninterface IERC721Receiver {\n    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data)\n        external\n        returns (bytes4);\n}\n\n/// @title M3MO — the M3M3 AGENT mascot NFT, the project's core identity layer.\n/// @notice Purely free mint for Agent ID holders: the caller only needs to own\n///         a SPACE ID / Agent ID.ai identity (same check as the M3M3 census\n///         registry). No token purchase, no census registration, no payment of\n///         any kind is required — one NFT per Agent ID, forever. No price, no\n///         withdraw function: the contract never touches funds; minters only\n///         pay their own BSC gas. Secondary-market creator fee: 5% via\n///         EIP-2981, paid by marketplaces to the royalty receiver — the mint\n///         itself is and stays free.\n/// @dev Standalone contract: it only READS the Agent ID registry, so it can be\n///      deployed and run independently of the census gate outcome.\ncontract M3MONFT {\n    // ------------------------------------------------------------------\n    // ERC-721 metadata\n    // ------------------------------------------------------------------\n    string public constant name = \"M3MO\";\n    string public constant symbol = \"M3MO\";\n\n    // ------------------------------------------------------------------\n    // Immutable configuration\n    // ------------------------------------------------------------------\n    /// @notice The Agent ID registry (BSC: 0x60B9262456dE011F7d15a54CdD8c48bAaa8B022F).\n    IAgentIdRegistry public immutable agentIds;\n    /// @notice Hard cap on tokens that can ever be minted (mainnet: 10,000).\n    uint256 public immutable maxSupply;\n\n    // ------------------------------------------------------------------\n    // State\n    // ------------------------------------------------------------------\n    /// @notice Deployer. Can open/close the mint and set the base URI. Fixed:\n    ///         there is no ownership transfer and no renounce.\n    address public immutable owner;\n    bool public mintOpen;\n    string public baseURI;\n    /// @notice Creator fee on secondary sales (EIP-2981): 500 bps = 5%.\n    uint256 public constant ROYALTY_BPS = 500;\n    uint256 internal constant BPS_DENOMINATOR = 10_000;\n    /// @notice Who receives the creator fee. Set at deployment; the deployer\n    ///         can redirect it later with setRoyaltyReceiver.\n    address public royaltyReceiver;\n    /// @notice Number of tokens minted so far; token IDs run 1..totalMinted.\n    uint256 public totalMinted;\n    /// @notice One mint per Agent ID — stays claimed even if the NFT is\n    ///         transferred away, so an identity can never mint twice.\n    mapping(uint256 agentId => bool) public claimedBy;\n\n    mapping(uint256 tokenId => address) private _owners;\n    mapping(address account => uint256) private _balances;\n    mapping(uint256 tokenId => address) private _tokenApprovals;\n    mapping(address account => mapping(address operator => bool)) private _operatorApprovals;\n\n    // ------------------------------------------------------------------\n    // Events (ERC-721 standard + mint administration)\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 Minted(uint256 indexed agentId, address indexed minter, uint256 indexed tokenId);\n    event MintOpened();\n    event MintClosed();\n    event BaseURISet(string baseURI);\n    event RoyaltyReceiverSet(address receiver);\n\n    constructor(\n        address agentIds_,\n        uint256 maxSupply_,\n        string memory baseURI_,\n        address royaltyReceiver_\n    ) {\n        require(agentIds_ != address(0), \"M3MO: zero address\");\n        require(royaltyReceiver_ != address(0), \"M3MO: zero address\");\n        require(maxSupply_ > 0, \"M3MO: zero supply\");\n        agentIds = IAgentIdRegistry(agentIds_);\n        maxSupply = maxSupply_;\n        baseURI = baseURI_;\n        royaltyReceiver = royaltyReceiver_;\n        owner = msg.sender;\n    }\n\n    modifier onlyOwner() {\n        require(msg.sender == owner, \"M3MO: not owner\");\n        _;\n    }\n\n    // ------------------------------------------------------------------\n    // Agent ID gate (mirrors M3M3Registry.idOf exactly)\n    // ------------------------------------------------------------------\n    /// @notice A wallet's Agent ID: uint256(keccak256(bytes(handle))). 0 if none.\n    function idOf(address user) public view returns (uint256) {\n        string memory handle = agentIds.handleOf(user);\n        if (bytes(handle).length == 0) return 0;\n        return uint256(keccak256(bytes(handle)));\n    }\n\n    // ------------------------------------------------------------------\n    // Mint\n    // ------------------------------------------------------------------\n    /// @notice Mint one M3MO to the caller. Purely free; the caller pays only\n    ///         gas. Requires: mint open, caller owns an Agent ID, that Agent ID\n    ///         has never minted an M3MO, and supply remains.\n    function mint() external returns (uint256 tokenId) {\n        require(mintOpen, \"M3MO: mint closed\");\n        uint256 agentId = idOf(msg.sender);\n        require(agentId != 0 && agentIds.ownerOf(agentId) == msg.sender, \"M3MO: no Agent ID\");\n        require(!claimedBy[agentId], \"M3MO: already minted\");\n        require(totalMinted < maxSupply, \"M3MO: sold out\");\n\n        claimedBy[agentId] = true;\n        tokenId = ++totalMinted;\n        _owners[tokenId] = msg.sender;\n        _balances[msg.sender] += 1;\n        emit Transfer(address(0), msg.sender, tokenId);\n        emit Minted(agentId, msg.sender, tokenId);\n        _checkReceiver(msg.sender, address(0), tokenId, \"\");\n    }\n\n    // ------------------------------------------------------------------\n    // Admin (deployer only; no function in this contract moves funds)\n    // ------------------------------------------------------------------\n    function openMint() external onlyOwner {\n        mintOpen = true;\n        emit MintOpened();\n    }\n\n    function closeMint() external onlyOwner {\n        mintOpen = false;\n        emit MintClosed();\n    }\n\n    function setBaseURI(string calldata baseURI_) external onlyOwner {\n        baseURI = baseURI_;\n        emit BaseURISet(baseURI_);\n    }\n\n    function setRoyaltyReceiver(address receiver_) external onlyOwner {\n        require(receiver_ != address(0), \"M3MO: zero address\");\n        royaltyReceiver = receiver_;\n        emit RoyaltyReceiverSet(receiver_);\n    }\n\n    // ------------------------------------------------------------------\n    // EIP-2981 creator fee (5% of the sale price, paid by the marketplace)\n    // ------------------------------------------------------------------\n    function royaltyInfo(uint256, uint256 salePrice)\n        external\n        view\n        returns (address receiver, uint256 royaltyAmount)\n    {\n        return (royaltyReceiver, (salePrice * ROYALTY_BPS) / BPS_DENOMINATOR);\n    }\n\n    // ------------------------------------------------------------------\n    // ERC-721 views\n    // ------------------------------------------------------------------\n    function ownerOf(uint256 tokenId) public view returns (address) {\n        address tokenOwner = _owners[tokenId];\n        require(tokenOwner != address(0), \"M3MO: nonexistent token\");\n        return tokenOwner;\n    }\n\n    function balanceOf(address account) external view returns (uint256) {\n        require(account != address(0), \"M3MO: zero address\");\n        return _balances[account];\n    }\n\n    function tokenURI(uint256 tokenId) external view returns (string memory) {\n        require(_owners[tokenId] != address(0), \"M3MO: nonexistent token\");\n        if (bytes(baseURI).length == 0) return \"\";\n        return string.concat(baseURI, _toString(tokenId));\n    }\n\n    function getApproved(uint256 tokenId) public view returns (address) {\n        require(_owners[tokenId] != address(0), \"M3MO: 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) external pure returns (bool) {\n        return interfaceId == 0x01ffc9a7 // ERC-165\n            || interfaceId == 0x80ac58cd // ERC-721\n            || interfaceId == 0x5b5e139f // ERC-721 Metadata\n            || interfaceId == 0x2a55205a; // EIP-2981 royalties\n    }\n\n    // ------------------------------------------------------------------\n    // ERC-721 approvals & transfers\n    // ------------------------------------------------------------------\n    function approve(address approved, uint256 tokenId) external {\n        address tokenOwner = ownerOf(tokenId);\n        require(\n            msg.sender == tokenOwner || isApprovedForAll(tokenOwner, msg.sender),\n            \"M3MO: not authorized\"\n        );\n        _tokenApprovals[tokenId] = approved;\n        emit Approval(tokenOwner, approved, tokenId);\n    }\n\n    function setApprovalForAll(address operator, bool approved) external {\n        require(operator != msg.sender, \"M3MO: self operator\");\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(msg.sender, tokenId), \"M3MO: 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        transferFrom(from, to, tokenId);\n        _checkReceiver(to, from, tokenId, data);\n    }\n\n    // ------------------------------------------------------------------\n    // Internals\n    // ------------------------------------------------------------------\n    function _isAuthorized(address spender, uint256 tokenId) internal view returns (bool) {\n        address tokenOwner = ownerOf(tokenId);\n        return spender == tokenOwner || spender == _tokenApprovals[tokenId]\n            || _operatorApprovals[tokenOwner][spender];\n    }\n\n    function _transfer(address from, address to, uint256 tokenId) internal {\n        require(ownerOf(tokenId) == from, \"M3MO: wrong from\");\n        require(to != address(0), \"M3MO: 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 _checkReceiver(address to, address from, uint256 tokenId, bytes memory data)\n        internal\n    {\n        if (to.code.length == 0) return;\n        try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (\n            bytes4 retval\n        ) {\n            require(retval == IERC721Receiver.onERC721Received.selector, \"M3MO: bad receiver\");\n        } catch {\n            revert(\"M3MO: bad receiver\");\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 -= 1;\n            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n            value /= 10;\n        }\n        return string(buffer);\n    }\n}\n"},"src/Interfaces.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\n/// @notice Minimal ERC20 used by the registry for USDT and payments.\ninterface IERC20 {\n    function totalSupply() external view returns (uint256);\n    function balanceOf(address account) external view returns (uint256);\n    function allowance(address owner, address spender) external view returns (uint256);\n    function approve(address spender, uint256 amount) external returns (bool);\n    function transfer(address to, uint256 amount) external returns (bool);\n    function transferFrom(address from, address to, uint256 amount) external returns (bool);\n    function decimals() external view returns (uint8);\n}\n\n/// @notice The Agent ID.ai registry (reference: 0x60B9262456dE011F7d15a54CdD8c48bAaa8B022F on BSC).\n///         registrant id = uint256(keccak256(bytes(handle))).\ninterface IAgentIdRegistry {\n    function ownerOf(uint256 id) external view returns (address);\n    function handleOf(address owner) external view returns (string memory);\n}\n\n/// @notice PancakeSwap V2 Factory (BSC: 0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73).\ninterface IPancakeFactory {\n    function getPair(address tokenA, address tokenB) external view returns (address pair);\n    function createPair(address tokenA, address tokenB) external returns (address pair);\n}\n\n/// @notice PancakeSwap V2 Pair (getReserves follows the real ABI: uint112/uint112/uint32).\n///         mint() is used by the remediation H-01 direct path in the registry (liquidity\n///         without the router if the pair is one-sided-seeded before the gate).\ninterface IPancakePair {\n    function token0() external view returns (address);\n    function token1() external view returns (address);\n    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);\n    function mint(address to) external returns (uint256 liquidity);\n}\n\n/// @notice PancakeSwap V2 Router (BSC: 0x10ED43C718714eb63d5aA57B78B54704E256024E).\ninterface IPancakeRouter {\n    function factory() external view returns (address);\n    function WETH() external view returns (address);\n    function addLiquidity(\n        address tokenA,\n        address tokenB,\n        uint256 amountADesired,\n        uint256 amountBDesired,\n        uint256 amountAMin,\n        uint256 amountBMin,\n        address to,\n        uint256 deadline\n    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);\n    function swapExactETHForTokensSupportingFeeOnTransferTokens(\n        uint256 amountOutMin,\n        address[] calldata path,\n        address to,\n        uint256 deadline\n    ) external payable;\n}\n\n/// @notice Chainlink BNB/USD feed (BSC: 0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE, 8 decimals).\ninterface IChainlinkFeed {\n    function decimals() external view returns (uint8);\n    function latestRoundData()\n        external\n        view\n        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);\n}\n"}},"abi":[{"type":"constructor","inputs":[{"name":"agentIds_","type":"address","internalType":"address"},{"name":"maxSupply_","type":"uint256","internalType":"uint256"},{"name":"baseURI_","type":"string","internalType":"string"},{"name":"royaltyReceiver_","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":"BaseURISet","type":"event","inputs":[{"name":"baseURI","type":"string","indexed":false,"internalType":"string"}],"anonymous":false},{"name":"MintClosed","type":"event","inputs":[],"anonymous":false},{"name":"MintOpened","type":"event","inputs":[],"anonymous":false},{"name":"Minted","type":"event","inputs":[{"name":"agentId","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"minter","type":"address","indexed":true,"internalType":"address"},{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"}],"anonymous":false},{"name":"RoyaltyReceiverSet","type":"event","inputs":[{"name":"receiver","type":"address","indexed":false,"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":"ROYALTY_BPS","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"agentIds","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IAgentIdRegistry"}],"stateMutability":"view"},{"name":"approve","type":"function","inputs":[{"name":"approved","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":"claimedBy","type":"function","inputs":[{"name":"agentId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"closeMint","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"name":"getApproved","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"idOf","type":"function","inputs":[{"name":"user","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"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":"maxSupply","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"mint","type":"function","inputs":[],"outputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"name":"mintOpen","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":"openMint","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"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":"royaltyInfo","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"},{"name":"salePrice","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"receiver","type":"address","internalType":"address"},{"name":"royaltyAmount","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":"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":"setRoyaltyReceiver","type":"function","inputs":[{"name":"receiver_","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":"symbol","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"tokenURI","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"string","internalType":"string"}],"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"}],"matchId":"58172672","creationMatch":"match","runtimeMatch":"match","verifiedAt":"2026-11-03T04:55:33Z","match":"match","chainId":"56","address":"0x08BE8a0727b6fc801171c1F4F4Dd547EA44Ca339"}