{"sources":{"src/MacroSwapTracker.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\n/// @dev What the NFT reads from MacroSwap for the on-chain version of the data.\ninterface IMacroSwapRead {\n    struct Asset {\n        address token;\n        address feed;\n        uint8 tokenDecimals;\n        uint16 slippageBps;\n        uint32 maxStaleness;\n        bytes path;\n    }\n\n    struct Order {\n        uint128 budget;\n        uint128 spent;\n        uint96 amountPerBuy;\n        uint96 keeperTip;\n        uint32 minInterval;\n        uint32 lastExec;\n        uint64 expiry;\n        uint16 dipBps;\n        bool active;\n        uint8 window;\n        uint16 refRule;\n        uint64 refPrice; // USD, 8 decimals\n        uint32 maxWait;\n        uint32 createdAt;\n        uint128 btcCost;\n        uint128 btcBought;\n    }\n\n    function getOrder(address user) external view returns (Order memory);\n    function getBtc() external view returns (Asset memory);\n    function mixOf(address user) external view returns (uint16 btc, uint16 a, uint16 b, bool custom);\n    function stableDecimals() external view returns (uint8);\n    function btcYearDrawdown() external view returns (bool ok, uint256 price, uint256 dd);\n}\n\n/// @title MacroSwapTracker\n/// @notice Non-transferable NFT (soulbound, ERC-5192): one per wallet, with the wallet's buy history and\n///         unrealized result on MacroSwap. The data and image come from the API (`baseURI`), which computes everything\n///         from MacroSwap's public events; the NFT holds and represents no asset.\n///         - Only the owner mints their own NFT (nobody receives an NFT unintentionally) and only the owner burns it.\n///         - There is no transfer and no approval.\n///         - tokenId = the wallet's address, so each wallet has at most one NFT.\n///         - ERC-4906: tells marketplaces to refresh the image when the data changes.\n///         - No API: with an empty `baseURI`, the `tokenURI` is generated 100% on-chain (`onchainURI`), with the\n///           current order's data read from MacroSwap. Never blank, even if the API goes down.\ncontract MacroSwapTracker {\n    string public constant name = \"MacroSwap Tracker\";\n    string public constant symbol = \"MSTRACK\";\n\n    address public immutable macroSwap; // the contract whose buys the NFT shows (same network)\n    address public owner; // Safe: only adjusts the API URI; no access to anything of the users'\n    string public baseURI; // e.g.: https://nft.macroswap.dev/tracker/1/  (ends with /)\n\n    mapping(uint256 tokenId => bool) internal minted;\n    uint256 public totalSupply;\n\n    // formatting constants (numbers, USD, base64, hex) and scale\n    uint256 internal constant BPS = 1e4;\n    uint8 internal constant WAD_DECIMALS = 18;\n    uint256 internal constant WAD = 1e18;\n    uint256 internal constant CENT_WAD = 1e16; // $0.01 in 18 decimals\n    uint256 internal constant FEED_TO_WAD = 1e10; // 8-decimal price -> 18\n    uint8 internal constant BTC_SHOW_DECIMALS = 8; // BTC shown with 8 decimals in the image\n    uint256 internal constant TEN = 10; // decimal base\n    uint256 internal constant PERCENT = 100; // bps -> % and cents\n    uint256 internal constant THOUSAND = 1e3; // thousands separator\n    uint256 internal constant ASCII_ZERO = 48; // \"0\"\n    uint256 internal constant ROW_Y0 = 316; // first table row in the SVG\n    uint256 internal constant ROW_STEP = 32; // height of each row\n    uint256 internal constant BYTE_BITS = 8;\n    uint256 internal constant B64_IN = 3; // 3 bytes become 4 characters\n    uint256 internal constant B64_OUT = 4;\n    uint256 internal constant B64_MASK = 63; // 6 bits\n    uint256 internal constant B64_SHIFT_1 = 18; // 1st character: bits 23..18\n    uint256 internal constant B64_SHIFT_2 = 12;\n    uint256 internal constant B64_SHIFT_3 = 6;\n    uint256 internal constant HEX_BITS = 4;\n    uint256 internal constant HEX_LEN = 42; // \"0x\" + 40 digits\n\n    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n    event Locked(uint256 tokenId); // ERC-5192\n    event MetadataUpdate(uint256 tokenId); // ERC-4906\n    event BatchMetadataUpdate(uint256 fromTokenId, uint256 toTokenId); // ERC-4906\n    event OwnershipRenounced();\n\n    modifier onlyOwner() {\n        require(msg.sender == owner, \"not owner\");\n        _;\n    }\n\n    constructor(address _macroSwap, address _owner, string memory _baseURI) {\n        require(_macroSwap != address(0) && _owner != address(0), \"zero address\");\n        macroSwap = _macroSwap;\n        owner = _owner;\n        baseURI = _baseURI;\n    }\n\n    // ------------------------------------------------------------------- tracker\n\n    function tokenIdOf(address holder) public pure returns (uint256) {\n        return uint256(uint160(holder));\n    }\n\n    /// @notice Mints the caller's own NFT. Free (just the gas).\n    function mint() external returns (uint256 id) {\n        id = tokenIdOf(msg.sender);\n        require(!minted[id], \"already minted\");\n        minted[id] = true;\n        totalSupply++;\n        emit Transfer(address(0), msg.sender, id);\n        emit Locked(id);\n    }\n\n    /// @notice The owner deletes their own NFT (e.g. no longer wants the history tied to the wallet).\n    function burn() external {\n        uint256 id = tokenIdOf(msg.sender);\n        require(minted[id], \"not minted\");\n        minted[id] = false;\n        totalSupply--;\n        emit Transfer(msg.sender, address(0), id);\n    }\n\n    /// @notice Asks marketplaces to reload the NFT's image (NFT owner or collection owner).\n    function refresh(uint256 id) external {\n        require(minted[id], \"not minted\");\n        require(msg.sender == address(uint160(id)) || msg.sender == owner, \"not allowed\");\n        emit MetadataUpdate(id);\n    }\n\n    // ----------------------------------------------------------------- ERC-721\n\n    function balanceOf(address holder) external view returns (uint256) {\n        require(holder != address(0), \"zero address\");\n        return minted[tokenIdOf(holder)] ? 1 : 0;\n    }\n\n    function ownerOf(uint256 id) public view returns (address) {\n        require(minted[id], \"not minted\");\n        // a minted id = the wallet's address (tokenIdOf), so it fits in uint160; a larger id is never minted (reverts above)\n        // aderyn-ignore-next-line(unsafe-casting)\n        return address(uint160(id));\n    }\n\n    /// @notice API when configured; with no `baseURI`, the data and image come from the contract itself.\n    function tokenURI(uint256 id) external view returns (string memory) {\n        address holder = ownerOf(id);\n        if (bytes(baseURI).length == 0) return _onchainURI(holder);\n        return string.concat(baseURI, _hex(holder));\n    }\n\n    /// @notice 100% on-chain version of the metadata (data: URI), always available. Shows the wallet's\n    ///         current order; what is not recorded in the contract (deposit buys, other tokens) only the API shows.\n    function onchainURI(uint256 id) external view returns (string memory) {\n        return _onchainURI(ownerOf(id));\n    }\n\n    /// @notice Collection metadata (OpenSea).\n    function contractURI() external view returns (string memory) {\n        return string.concat(baseURI, \"collection\");\n    }\n\n    /// @notice ERC-5192: every NFT is locked forever.\n    function locked(uint256 id) external view returns (bool) {\n        require(minted[id], \"not minted\");\n        return true;\n    }\n\n    function getApproved(uint256 id) external view returns (address) {\n        require(ownerOf(id) != address(0), \"not minted\"); // reverts if it does not exist (ERC-721); there is never an approved address\n        return address(0);\n    }\n\n    function isApprovedForAll(address, address) external pure returns (bool) {\n        return false;\n    }\n\n    function approve(address, uint256) external pure {\n        revert(\"soulbound\");\n    }\n\n    function setApprovalForAll(address, bool) external pure {\n        revert(\"soulbound\");\n    }\n\n    function transferFrom(address, address, uint256) external pure {\n        revert(\"soulbound\");\n    }\n\n    function safeTransferFrom(address, address, uint256) external pure {\n        revert(\"soulbound\");\n    }\n\n    function safeTransferFrom(address, address, uint256, bytes calldata) external pure {\n        revert(\"soulbound\");\n    }\n\n    function supportsInterface(bytes4 id) external pure returns (bool) {\n        return id == 0x01ffc9a7 // ERC-165\n            || id == 0x80ac58cd // ERC-721\n            || id == 0x5b5e139f // ERC-721 Metadata\n            || id == 0xb45a3c0e // ERC-5192\n            || id == 0x49064906; // ERC-4906\n    }\n\n    // ------------------------------------------------------------------- admin\n\n    /// @dev Changes the API (new domain, etc.). Tells marketplaces to reload everything.\n    // Centralization accepted: the owner (Safe) only changes the image URI; no access to any funds; empty = 100% on-chain.\n    // aderyn-ignore-next-line(centralization-risk)\n    function setBaseURI(string calldata uri) external onlyOwner {\n        baseURI = uri;\n        emit BatchMetadataUpdate(0, type(uint160).max);\n    }\n\n    // Only removes power (owner becomes zero).\n    // aderyn-ignore-next-line(centralization-risk)\n    function renounceOwnership() external onlyOwner {\n        owner = address(0);\n        emit OwnershipRenounced();\n    }\n\n    // -------------------------------------------------------------- on-chain\n\n    struct View {\n        bool hasBuys;\n        bool active;\n        uint8 window;\n        uint16 dipBps;\n        uint256 spent; // USD 18 decimals (current order, with fee and tip)\n        uint256 btcAmount; // 8 decimals\n        uint256 avgPrice; // USD 18 decimals\n        uint256 btcPrice; // USD 18 decimals (0 = oracle unavailable)\n        uint256 startPrice; // BTC at order creation, USD 18 decimals (0 = no record)\n        int256 pnlBps; // the order's unrealized BTC result\n        string mix; // BTC / group A / group B split chosen by the wallet, e.g.: \"75 / 20 / 5%\"\n    }\n\n    // False positive: the drawdown (dd) from btcYearDrawdown does not go into the image; only `ok` and the price do.\n    // slither-disable-next-line unused-return\n    function _view(address holder) internal view returns (View memory v) {\n        IMacroSwapRead core = IMacroSwapRead(macroSwap);\n        uint256 usdScale = TEN ** (WAD_DECIMALS - core.stableDecimals());\n        uint8 btcDec = core.getBtc().tokenDecimals;\n        IMacroSwapRead.Order memory o = core.getOrder(holder);\n        (bool ok, uint256 price,) = core.btcYearDrawdown();\n\n        v.active = o.active;\n        v.window = o.window;\n        v.dipBps = o.dipBps;\n        v.spent = o.spent * usdScale;\n        v.btcPrice = ok ? price : 0;\n        v.startPrice = uint256(o.refPrice) * FEED_TO_WAD;\n        v.mix = _mixText(core, holder);\n        uint256 bought = o.btcBought;\n        if (bought == 0) return v;\n        v.hasBuys = true;\n        v.btcAmount = btcDec >= BTC_SHOW_DECIMALS\n            ? bought / TEN ** (btcDec - BTC_SHOW_DECIMALS)\n            : bought * TEN ** (BTC_SHOW_DECIMALS - btcDec);\n        uint256 cost = o.btcCost * usdScale;\n        v.avgPrice = (cost * TEN ** btcDec) / bought;\n        if (ok) {\n            uint256 value = (bought * price) / TEN ** btcDec;\n            v.pnlBps = ((int256(value) - int256(cost)) * int256(BPS)) / int256(cost);\n        }\n    }\n\n    // False positive: `custom` does not change the text (it shows the allocation in use, default or chosen).\n    // slither-disable-next-line unused-return\n    function _mixText(IMacroSwapRead core, address holder) internal view returns (string memory) {\n        (uint16 b, uint16 a, uint16 g,) = core.mixOf(holder);\n        return string.concat(_share(b), \" / \", _share(a), \" / \", _share(g), \"%\");\n    }\n\n    function _onchainURI(address holder) internal view returns (string memory) {\n        View memory v = _view(holder);\n        string memory pnl = v.hasBuys && v.btcPrice > 0 ? _pct(v.pnlBps) : unicode\"—\";\n        string memory json = string.concat(\n            '{\"name\":\"MacroSwap Tracker ',\n            _hex(holder),\n            '\",\"description\":\"Non-transferable record of this wallet\\'s buys on MacroSwap (on-chain data of the current order). Unrealized result: does not account for sales or transfers and is not a promise of returns.\",\"image\":\"data:image/svg+xml;base64,',\n            _base64(bytes(_svg(holder, v, pnl))),\n            '\",',\n            _attributes(v, pnl),\n            \"}\"\n        );\n        return string.concat(\"data:application/json;base64,\", _base64(bytes(json)));\n    }\n\n    function _attributes(View memory v, string memory pnl) internal pure returns (string memory) {\n        return string.concat(\n            '\"attributes\":[{\"trait_type\":\"Unrealized BTC result\",\"value\":\"',\n            pnl,\n            '\"},{\"trait_type\":\"Invested in the current order (USD)\",\"value\":\"',\n            _usd(v.spent, 2),\n            '\"},{\"trait_type\":\"Average BTC price (USD)\",\"value\":\"',\n            v.hasBuys ? _usd(v.avgPrice, 0) : unicode\"—\",\n            '\"},{\"trait_type\":\"BTC at order creation (USD)\",\"value\":\"',\n            v.startPrice > 0 ? _usd(v.startPrice, 0) : unicode\"—\",\n            '\"},{\"trait_type\":\"BTC / A / B split\",\"value\":\"',\n            v.mix,\n            '\"},{\"trait_type\":\"Source\",\"value\":\"on-chain\"}]'\n        );\n    }\n\n    function _svg(address holder, View memory v, string memory pnl) internal pure returns (string memory) {\n        string memory tone = !v.hasBuys || v.btcPrice == 0 ? \"#9AA4B8\" : v.pnlBps >= 0 ? \"#34D399\" : \"#F87171\";\n        string memory head = string.concat(\n            '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 600 600\" font-family=\"Helvetica,Arial,sans-serif\">',\n            '<rect width=\"600\" height=\"600\" fill=\"#0A0E17\"/><rect x=\"24\" y=\"24\" width=\"552\" height=\"552\" rx=\"28\" fill=\"#111726\" stroke=\"#1F2738\"/>',\n            '<g transform=\"translate(56 56) scale(1.5)\"><path d=\"M4 25 L10.5 8 L16 19.5 L21.5 8 L28 25\" stroke=\"#E8ECF5\" stroke-width=\"3.2\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><circle cx=\"16\" cy=\"19.5\" r=\"3.4\" fill=\"#FFB020\"/></g>',\n            '<text x=\"112\" y=\"82\" fill=\"#E8ECF5\" font-size=\"24\" font-weight=\"600\">MacroSwap Tracker</text>',\n            '<text x=\"112\" y=\"104\" fill=\"#9AA4B8\" font-size=\"13\">',\n            _hex(holder),\n            \"</text>\"\n        );\n        string memory big = string.concat(\n            '<text x=\"56\" y=\"178\" fill=\"#9AA4B8\" font-size=\"14\" letter-spacing=\"1.5\">UNREALIZED BTC RESULT</text>',\n            '<text x=\"56\" y=\"240\" fill=\"',\n            tone,\n            '\" font-size=\"56\" font-weight=\"600\" font-family=\"Menlo,monospace\">',\n            pnl,\n            \"</text>\"\n        );\n        string memory order = v.spent == 0 && !v.active\n            ? \"none\"\n            : string.concat(v.active ? \"active\" : \"closed\", v.window == 2 ? \" - ATH dip -\" : v.window == 1 ? \" - macro dip -\" : \" - short dip -\", _dec(v.dipBps, 2), \"%\");\n        // in two parts: 7 lines in a single concat would overflow the stack\n        string memory rows = string.concat(\n            _row(0, \"Invested in the current order\", _usd(v.spent, 2)),\n            _row(1, \"BTC bought\", v.hasBuys ? _dec(v.btcAmount, BTC_SHOW_DECIMALS) : unicode\"—\"),\n            _row(2, \"Average BTC price\", _avgText(v)),\n            _row(3, \"BTC at order creation\", v.startPrice > 0 ? _usd(v.startPrice, 0) : unicode\"—\")\n        );\n        rows = string.concat(\n            rows,\n            _row(4, \"BTC now (Chainlink)\", v.btcPrice > 0 ? _usd(v.btcPrice, 0) : unicode\"—\"),\n            _row(5, \"Order\", order),\n            _row(6, \"BTC / A / B split\", v.mix)\n        );\n        return string.concat(\n            head,\n            big,\n            rows,\n            '<text x=\"56\" y=\"548\" fill=\"#9AA4B8\" font-size=\"12\">On-chain data of the current order &#183; non-transferable &#183; not a promise of returns</text></svg>'\n        );\n    }\n\n    /// @dev Average price and how far it is below (or above) the price at creation: \"$91,200 (-7.30%)\".\n    function _avgText(View memory v) internal pure returns (string memory) {\n        if (!v.hasBuys) return unicode\"—\";\n        if (v.startPrice == 0) return _usd(v.avgPrice, 0);\n        int256 diff = ((int256(v.avgPrice) - int256(v.startPrice)) * int256(BPS)) / int256(v.startPrice);\n        return string.concat(_usd(v.avgPrice, 0), \" (\", _pct(diff), \")\");\n    }\n\n    function _row(uint256 i, string memory k, string memory val) internal pure returns (string memory) {\n        string memory y = _u(ROW_Y0 + i * ROW_STEP);\n        return string.concat(\n            '<text x=\"56\" y=\"',\n            y,\n            '\" fill=\"#9AA4B8\" font-size=\"15\">',\n            k,\n            '</text><text x=\"544\" y=\"',\n            y,\n            '\" fill=\"#E8ECF5\" font-size=\"15\" text-anchor=\"end\" font-family=\"Menlo,monospace\">',\n            val,\n            \"</text>\"\n        );\n    }\n\n    // ----------------------------------------------------------------- helpers\n\n    function _u(uint256 v) internal pure returns (string memory) {\n        if (v == 0) return \"0\";\n        uint256 len = 0;\n        for (uint256 t = v; t > 0; t /= TEN) len++;\n        bytes memory b = new bytes(len);\n        for (; v > 0; v /= TEN) b[--len] = bytes1(uint8(ASCII_ZERO + (v % TEN)));\n        return string(b);\n    }\n\n    /// @dev bps as a percentage with no unneeded decimals: 7500 -> \"75\", 1250 -> \"12.5\".\n    function _share(uint256 bps) internal pure returns (string memory) {\n        if (bps % PERCENT == 0) return _u(bps / PERCENT);\n        return bps % TEN == 0 ? _dec(bps / TEN, 1) : _dec(bps, 2);\n    }\n\n    /// @dev Integer with a thousands separator: 78409 -> \"78,409\".\n    function _grouped(uint256 v) internal pure returns (string memory) {\n        string memory tail = \"\";\n        for (; v >= THOUSAND; v /= THOUSAND) {\n            uint256 part = v % THOUSAND;\n            tail = string.concat(\",\", part < TEN ? \"00\" : part < PERCENT ? \"0\" : \"\", _u(part), tail);\n        }\n        return string.concat(_u(v), tail);\n    }\n\n    /// @dev Value with `d` decimals: (12345, 2) -> \"123.45\".\n    function _dec(uint256 v, uint256 d) internal pure returns (string memory) {\n        if (d == 0) return _u(v);\n        uint256 unit = TEN ** d;\n        string memory frac = _u(v % unit);\n        while (bytes(frac).length < d) frac = string.concat(\"0\", frac);\n        return string.concat(_u(v / unit), \".\", frac);\n    }\n\n    /// @dev USD with 18 decimals, shown with 0 or 2 places: \"$78,409\" or \"$200.00\".\n    function _usd(uint256 v18, uint256 d) internal pure returns (string memory) {\n        string memory whole = string.concat(\"$\", _grouped(v18 / WAD));\n        if (d == 0) return whole;\n        uint256 cents = (v18 / CENT_WAD) % PERCENT;\n        return string.concat(whole, cents < TEN ? \".0\" : \".\", _u(cents));\n    }\n\n    function _pct(int256 bps) internal pure returns (string memory) {\n        uint256 a = uint256(bps < 0 ? -bps : bps);\n        return string.concat(bps < 0 ? \"-\" : \"+\", _dec(a, 2), \"%\");\n    }\n\n    // False positive: 4 * ceil(len / 3) is the exact size of the base64 output (the integer division is on purpose).\n    // slither-disable-next-line divide-before-multiply\n    function _base64(bytes memory data) internal pure returns (string memory) {\n        bytes memory table = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n        uint256 len = data.length;\n        if (len == 0) return \"\";\n        bytes memory out = new bytes(B64_OUT * ((len + B64_IN - 1) / B64_IN));\n        uint256 j = 0;\n        for (uint256 i = 0; i < len; i += B64_IN) {\n            uint256 n = uint256(uint8(data[i])) << (2 * BYTE_BITS);\n            if (i + 1 < len) n |= uint256(uint8(data[i + 1])) << BYTE_BITS;\n            if (i + 2 < len) n |= uint256(uint8(data[i + 2]));\n            out[j++] = table[(n >> B64_SHIFT_1) & B64_MASK];\n            out[j++] = table[(n >> B64_SHIFT_2) & B64_MASK];\n            out[j++] = i + 1 < len ? table[(n >> B64_SHIFT_3) & B64_MASK] : bytes1(\"=\");\n            out[j++] = i + 2 < len ? table[n & B64_MASK] : bytes1(\"=\");\n        }\n        return string(out);\n    }\n\n    /// @dev Address as lowercase hex with 0x (the path the API receives).\n    function _hex(address a) internal pure returns (string memory) {\n        bytes memory s = new bytes(HEX_LEN);\n        s[0] = \"0\";\n        s[1] = \"x\";\n        uint160 v = uint160(a);\n        for (uint256 i = HEX_LEN - 1; i > 1; i--) {\n            s[i] = bytes16(\"0123456789abcdef\")[v & 0xf];\n            v >>= HEX_BITS;\n        }\n        return string(s);\n    }\n}\n"}},"abi":[{"type":"constructor","inputs":[{"name":"_macroSwap","type":"address","internalType":"address"},{"name":"_owner","type":"address","internalType":"address"},{"name":"_baseURI","type":"string","internalType":"string"}],"stateMutability":"nonpayable"},{"name":"BatchMetadataUpdate","type":"event","inputs":[{"name":"fromTokenId","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"toTokenId","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"Locked","type":"event","inputs":[{"name":"tokenId","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"MetadataUpdate","type":"event","inputs":[{"name":"tokenId","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"OwnershipRenounced","type":"event","inputs":[],"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":"approve","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"pure"},{"name":"balanceOf","type":"function","inputs":[{"name":"holder","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":"burn","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"name":"contractURI","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"getApproved","type":"function","inputs":[{"name":"id","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"isApprovedForAll","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"name":"locked","type":"function","inputs":[{"name":"id","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"macroSwap","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"mint","type":"function","inputs":[],"outputs":[{"name":"id","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"name":"name","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"onchainURI","type":"function","inputs":[{"name":"id","type":"uint256","internalType":"uint256"}],"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":"id","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"refresh","type":"function","inputs":[{"name":"id","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"renounceOwnership","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"name":"safeTransferFrom","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"pure"},{"name":"safeTransferFrom","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"},{"name":"","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"pure"},{"name":"setApprovalForAll","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"pure"},{"name":"setBaseURI","type":"function","inputs":[{"name":"uri","type":"string","internalType":"string"}],"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":"tokenIdOf","type":"function","inputs":[{"name":"holder","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"name":"tokenURI","type":"function","inputs":[{"name":"id","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":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"pure"}],"matchId":"58958951","creationMatch":"exact_match","runtimeMatch":"exact_match","verifiedAt":"2026-11-10T02:33:07Z","match":"exact_match","chainId":"56","address":"0xa37b3fF7A1cABf77256a4B246eBa1652011e4569"}