{"sources":{"src/MonkeAgents.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\n/// @title Monke Agents\n/// @notice A membership collection for .ai Agent IDs. 3,333 fixed supply: the first 777 are free,\n///         one free mint per identity, five monkes per identity in total, every path gated on the\n///         .ai registry. Each monke carries a monthly AI credit allowance handled off-chain.\ncontract MonkeAgents {\n    error NotAgent();\n    error FreeSeatsTaken();\n    error AlreadyClaimedFree();\n    error QuotaReached();\n    error SoldOut();\n    error BadQty();\n    error BadValue(uint256 expected, uint256 got);\n    error NotOwner();\n    error NotAllowed();\n    error UnknownToken();\n\n    string public constant name = \"Monke Agents\";\n    string public constant symbol = \"MONKE\";\n\n    uint256 public constant MAX_SUPPLY = 3333;\n    uint256 public constant FREE_SEATS = 777;\n    uint256 public constant QUOTA = 5;\n    uint256 public constant LAYERS = 6;\n    uint256 public constant TRAITS_PER_LAYER = 12;\n    uint256 public constant DNA_MASK = 0x0f;\n    uint256 public constant BITS_PER_LAYER = 4;\n\n    address public owner;\n    address public registry;\n    uint256 public paidPrice;\n\n    uint256 public nextTokenId = 1;\n    uint256 public totalMinted;\n\n    mapping(uint256 => address) public ownerOf;\n    mapping(address => uint256) public balanceOf;\n    mapping(uint256 => address) public getApproved;\n    mapping(uint256 => uint32) public dna;\n    mapping(uint256 => uint256) public identityOf;      // agentId that minted the token\n    mapping(uint256 => uint256) public mintedByIdentity;  // how many this identity minted\n    mapping(uint256 => bool) public claimedFree;\n    mapping(address => mapping(address => bool)) public isApprovedForAll;\n\n    string public imageURI;        // one picture for the whole collection\n    string public sealedURI;       // shown until reveal\n    bool public revealed;\n    bool public imageFrozen;\n\n    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n    event Minted(uint256 indexed tokenId, address indexed to, uint256 indexed identity, bool free);\n    event Cleared(address indexed to, uint256 amount);\n\n    modifier onlyOwner() {\n        if (msg.sender != owner) revert NotOwner();\n        _;\n    }\n\n    constructor(address registry_, uint256 paidPrice_, string memory sealed_) {\n        owner = msg.sender;\n        registry = registry_;\n        paidPrice = paidPrice_;\n        sealedURI = sealed_;\n    }\n\n    // ---------------------------------------------------------------- gate\n\n    function _identity() internal view returns (uint256 id) {\n        (bool ok, bytes memory ret) = registry.staticcall(\n            abi.encodeWithSignature(\"handleOf(address)\", msg.sender)\n        );\n        if (!ok || ret.length < 64) revert NotAgent();\n        string memory handle = abi.decode(ret, (string));\n        if (bytes(handle).length == 0) revert NotAgent();\n        return uint256(keccak256(bytes(handle)));\n    }\n\n    // ---------------------------------------------------------------- mint\n\n    /// @notice Free seat, one per .ai identity, while the first 777 last.\n    function mint() external returns (uint256 tokenId) {\n        uint256 id = _identity();\n        if (totalMinted >= FREE_SEATS) revert FreeSeatsTaken();\n        if (claimedFree[id]) revert AlreadyClaimedFree();\n        if (mintedByIdentity[id] >= QUOTA) revert QuotaReached();\n\n        claimedFree[id] = true;\n        tokenId = _mintOne(id, true);\n    }\n\n    /// @notice Paid seats, still gated on .ai, still counted against the same quota of five.\n    function mintPaid(uint256 qty) external payable returns (uint256 firstTokenId) {\n        if (qty == 0) revert BadQty();\n        if (msg.value != paidPrice * qty) revert BadValue(paidPrice * qty, msg.value);\n\n        uint256 id = _identity();\n        if (mintedByIdentity[id] + qty > QUOTA) revert QuotaReached();\n        if (totalMinted + qty > MAX_SUPPLY) revert SoldOut();\n\n        for (uint256 i = 0; i < qty; i++) {\n            uint256 tokenId = _mintOne(id, false);\n            if (i == 0) firstTokenId = tokenId;\n        }\n        // BNB stays in this contract until the owner calls clearETH().\n    }\n\n    function _mintOne(uint256 id, bool free) internal returns (uint256 tokenId) {\n        tokenId = nextTokenId++;\n        totalMinted += 1;\n        mintedByIdentity[id] += 1;\n\n        ownerOf[tokenId] = msg.sender;\n        balanceOf[msg.sender] += 1;\n        identityOf[tokenId] = id;\n        dna[tokenId] = _roll(tokenId);\n\n        emit Transfer(address(0), msg.sender, tokenId);\n        emit Minted(tokenId, msg.sender, id, free);\n    }\n\n    /// @dev Deterministic from tokenId alone so the art can be reproduced off-chain, byte for byte,\n    ///      before anybody mints. Identity is not part of the seed on purpose: the picture belongs\n    ///      to the seat, not to the person who took it.\n    function _roll(uint256 tokenId) internal pure returns (uint32 out) {\n        uint256 seed = uint256(keccak256(abi.encodePacked(tokenId)));\n        for (uint256 layer = 0; layer < LAYERS; layer++) {\n            out |= uint32((seed >> (layer * 8)) % TRAITS_PER_LAYER) << (layer * BITS_PER_LAYER);\n        }\n    }\n\n    function traitsOf(uint256 tokenId) external view returns (uint8[6] memory out) {\n        if (ownerOf[tokenId] == address(0)) revert UnknownToken();\n        uint32 d = dna[tokenId];\n        for (uint256 i = 0; i < LAYERS; i++) {\n            out[i] = uint8((d >> (i * BITS_PER_LAYER)) & DNA_MASK);\n        }\n    }\n\n    function freeSeatsLeft() external view returns (uint256) {\n        return totalMinted >= FREE_SEATS ? 0 : FREE_SEATS - totalMinted;\n    }\n\n    // -------------------------------------------------------------- erc721\n\n    event Approval(address indexed owner, address indexed spender, uint256 indexed tokenId);\n    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n    function supportsInterface(bytes4 id) external pure returns (bool) {\n        return id == 0x01ffc9a7 || id == 0x80ac58cd;\n    }\n\n    function approve(address to, uint256 tokenId) external {\n        address holder = ownerOf[tokenId];\n        if (holder != msg.sender && !isApprovedForAll[holder][msg.sender]) revert NotOwner();\n        getApproved[tokenId] = to;\n        emit Approval(holder, to, tokenId);\n    }\n\n    function setApprovalForAll(address operator, bool approved) external {\n        isApprovedForAll[msg.sender][operator] = approved;\n        emit ApprovalForAll(msg.sender, operator, approved);\n    }\n\n    function transferFrom(address from, address to, uint256 tokenId) external {\n        if (ownerOf[tokenId] != from) revert NotOwner();\n        if (to == address(0)) revert NotAllowed();\n        if (msg.sender != from && getApproved[tokenId] != msg.sender && !isApprovedForAll[from][msg.sender]) {\n            revert NotAllowed();\n        }\n        ownerOf[tokenId] = to;\n        balanceOf[from] -= 1;\n        balanceOf[to] += 1;\n        delete getApproved[tokenId];\n        emit Transfer(from, to, tokenId);\n    }\n\n    // ------------------------------------------------------- token uri\n\n    /// @notice Point the collection at its picture. One URI, the whole supply.\n    function setImageURI(string calldata uri) external onlyOwner {\n        require(!imageFrozen, \"frozen\");\n        imageURI = uri;\n    }\n\n    /// @notice Switch every token from the sealed placeholder to the picture.\n    function setRevealed(bool value) external onlyOwner {\n        revealed = value;\n    }\n\n    function freezeImageURI() external onlyOwner {\n        imageFrozen = true;\n    }\n\n    function tokenURI(uint256 tokenId) external view returns (string memory) {\n        if (ownerOf[tokenId] == address(0)) revert UnknownToken();\n        uint8[6] memory t = this.traitsOf(tokenId);\n        string memory attrs = string.concat(\n            '{\"trait_type\":\"head\",\"value\":', _num(t[0]), '},',\n            '{\"trait_type\":\"eyes\",\"value\":', _num(t[1]), '},',\n            '{\"trait_type\":\"mouth\",\"value\":', _num(t[2]), '},',\n            '{\"trait_type\":\"grid\",\"value\":', _num(t[3]), '},',\n            '{\"trait_type\":\"ink\",\"value\":', _num(t[4]), '},',\n            '{\"trait_type\":\"serial\",\"value\":', _num(t[5]), '}'\n        );\n        string memory json = string.concat(\n            '{\"name\":\"Monke Agent #', _num(tokenId),\n            '\",\"description\":\"A .ai Agent ID membership. Each monke carries a monthly AI credit allowance.\",',\n            '\"image\":\"', revealed ? imageURI : sealedURI, '\",',\n            '\"attributes\":[', attrs, ']}'\n        );\n        return string.concat(\"data:application/json;base64,\", _b64(bytes(json)));\n    }\n\n    function _num(uint256 v) internal pure returns (string memory) {\n        if (v == 0) return \"0\";\n        uint256 tmp = v;\n        uint256 digits = 0;\n        while (tmp != 0) { digits++; tmp /= 10; }\n        bytes memory buf = new bytes(digits);\n        while (v != 0) { digits--; buf[digits] = bytes1(uint8(48 + v % 10)); v /= 10; }\n        return string(buf);\n    }\n\n    function _b64(bytes memory data) internal pure returns (string memory) {\n        bytes memory table = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n        uint256 len = data.length;\n        uint256 outLen = 4 * ((len + 2) / 3);\n        bytes memory out = new bytes(outLen);\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) & 63];\n            out[j++] = table[(n >> 12) & 63];\n            out[j++] = table[(n >> 6) & 63];\n            out[j++] = table[n & 63];\n        }\n        if (len - i == 1) {\n            uint256 n = uint256(uint8(data[i])) << 16;\n            out[j++] = table[(n >> 18) & 63];\n            out[j++] = table[(n >> 12) & 63];\n            out[j++] = \"=\";\n            out[j++] = \"=\";\n        } else if (len - i == 2) {\n            uint256 n = (uint256(uint8(data[i])) << 16) | (uint256(uint8(data[i + 1])) << 8);\n            out[j++] = table[(n >> 18) & 63];\n            out[j++] = table[(n >> 12) & 63];\n            out[j++] = table[(n >> 6) & 63];\n            out[j++] = \"=\";\n        }\n        return string(out);\n    }\n\n    // ------------------------------------------------------------- admin\n\n    function setPaidPrice(uint256 price) external onlyOwner {\n        paidPrice = price;\n    }\n\n    /// @notice Withdraw every wei held by this contract to the owner. Nothing is swept automatically.\n    function clearETH() external onlyOwner returns (uint256 amount) {\n        amount = address(this).balance;\n        if (amount == 0) return 0;\n        (bool ok, ) = owner.call{value: amount}(\"\");\n        require(ok, \"clear failed\");\n        emit Cleared(owner, amount);\n    }\n\n    /// @notice Balance waiting for clearETH(), for dashboards and agents.\n    function heldETH() external view returns (uint256) {\n        return address(this).balance;\n    }\n}\n"}},"abi":[{"type":"constructor","inputs":[{"name":"registry_","type":"address","internalType":"address"},{"name":"paidPrice_","type":"uint256","internalType":"uint256"},{"name":"sealed_","type":"string","internalType":"string"}],"stateMutability":"nonpayable"},{"name":"AlreadyClaimedFree","type":"error","inputs":[]},{"name":"BadQty","type":"error","inputs":[]},{"name":"BadValue","type":"error","inputs":[{"name":"expected","type":"uint256","internalType":"uint256"},{"name":"got","type":"uint256","internalType":"uint256"}]},{"name":"FreeSeatsTaken","type":"error","inputs":[]},{"name":"NotAgent","type":"error","inputs":[]},{"name":"NotAllowed","type":"error","inputs":[]},{"name":"NotOwner","type":"error","inputs":[]},{"name":"QuotaReached","type":"error","inputs":[]},{"name":"SoldOut","type":"error","inputs":[]},{"name":"UnknownToken","type":"error","inputs":[]},{"name":"Approval","type":"event","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"spender","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":"Cleared","type":"event","inputs":[{"name":"to","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"Minted","type":"event","inputs":[{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"to","type":"address","indexed":true,"internalType":"address"},{"name":"identity","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"free","type":"bool","indexed":false,"internalType":"bool"}],"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":"BITS_PER_LAYER","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"DNA_MASK","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"FREE_SEATS","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"LAYERS","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":"QUOTA","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"TRAITS_PER_LAYER","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":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"claimedFree","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"clearETH","type":"function","inputs":[],"outputs":[{"name":"amount","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"name":"dna","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint32","internalType":"uint32"}],"stateMutability":"view"},{"name":"freeSeatsLeft","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"freezeImageURI","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"name":"getApproved","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"heldETH","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"identityOf","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"imageFrozen","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"imageURI","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"isApprovedForAll","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"mint","type":"function","inputs":[],"outputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"name":"mintPaid","type":"function","inputs":[{"name":"qty","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"firstTokenId","type":"uint256","internalType":"uint256"}],"stateMutability":"payable"},{"name":"mintedByIdentity","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"name","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"nextTokenId","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":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"paidPrice","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"registry","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"revealed","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"sealedURI","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"setApprovalForAll","type":"function","inputs":[{"name":"operator","type":"address","internalType":"address"},{"name":"approved","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setImageURI","type":"function","inputs":[{"name":"uri","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setPaidPrice","type":"function","inputs":[{"name":"price","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setRevealed","type":"function","inputs":[{"name":"value","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"supportsInterface","type":"function","inputs":[{"name":"id","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":"traitsOf","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"out","type":"uint8[6]","internalType":"uint8[6]"}],"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":"58208607","creationMatch":"exact_match","runtimeMatch":"exact_match","verifiedAt":"2026-11-03T11:28:34Z","match":"exact_match","chainId":"56","address":"0xD06518eA638cab567328ccD7b67e43A119C275E0"}