{"author":{"address":"0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69","user":"https://learnblockchain.cn/people/2184"},"content":{"body":"# 使用 TheGraph 来索引和查询你的 NFTMarket 合约的上架 List 和成交 Sold 记录\r\n\r\n用foundry部署开源 NFTMarket 合约， 使用 TheGraph 索引 NFTMarket 的上架List和成交Sold记录\r\n\r\n### 1. 安装 Foundry\r\n\r\n首先，确保你已经安装了 Foundry，工具的安装使用，请参考官网的官方文档：[https://getfoundry.sh](https://getfoundry.sh/)\r\n\r\n```\r\ncurl -L https://foundry.paradigm.xyz | bash\r\nfoundryup\r\n```\r\n\r\n### 2. 创建项目\r\n\r\n创建一个新的 Foundry 项目：\r\n\r\n```\r\nforge init NFTMarketProject\r\ncd NFTMarketProject\r\n```\r\n\r\n### 3. 添加依赖\r\n\r\n如果需要使用 OpenZeppelin 库，可以添加依赖：\r\n\r\n```\r\nforge install OpenZeppelin/openzeppelin-contracts\r\n```\r\n\r\n在项目的 `foundry.toml` 文件中，添加如下内容：\r\n\r\n```\r\n[profile.default]\r\nsrc = 'src'\r\nout = 'out'\r\nlibs = ['lib']\r\nsolc_version = \"0.8.25\"\r\nevm_version = \"cancun\"\r\n\r\nremappings = [\r\n    '@openzeppelin/=lib/openzeppelin-contracts/',\r\n    'forge-std/=lib/forge-std/src/',\r\n    '@openzeppelin/contracts/=/Users/yhb/MyNFTMarketProject/lib/openzeppelin-contracts/contracts/',\r\n    'ds-test/=/Users/yhb/MyNFTMarketProject/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/',\r\n    'erc4626-tests/=/Users/yhb/MyNFTMarketProject/lib/openzeppelin-contracts/lib/erc4626-tests/',\r\n    'halmos-cheatcodes/=/Users/yhb/MyNFTMarketProject/lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/',\r\n    'openzeppelin-contracts/=/Users/yhb/MyNFTMarketProject/lib/openzeppelin-contracts/',\r\n]\r\n\r\n```\r\n\r\n\r\n\r\n### 4. 编写合约\r\n\r\n在 `src` 目录下创建合约文件 `NFTMarket.sol` 并编写合约代码：\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.20;\r\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\r\nimport {Ownable} from \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport {EIP712} from \"@openzeppelin/contracts/utils/cryptography/EIP712.sol\";\r\nimport {ECDSA} from \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\r\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\r\nimport {IERC721} from \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\r\n\r\n\r\ncontract NFTMarket is Ownable(msg.sender), EIP712(\"OpenSpaceNFTMarket\", \"1\") {\r\n    address public constant ETH_FLAG = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\r\n    uint256 public constant feeBP = 30; // 30/10000 = 0.3%\r\n    address public whiteListSigner;\r\n    address public feeTo; \r\n    mapping(bytes32 =\u003e SellOrder) public listingOrders; \r\n    mapping(address =\u003e mapping(uint256 =\u003e bytes32)) private _lastIds; \r\n    struct SellOrder {\r\n        address seller;  \r\n        address nft;     \r\n        uint256 tokenId; \r\n        address payToken;\r\n        uint256 price;   \r\n        uint256 deadline; \r\n    }\r\n    function listing(address nft, uint256 tokenId) external view returns (bytes32) {\r\n        bytes32 id = _lastIds[nft][tokenId];\r\n        return listingOrders[id].seller == address(0) ? bytes32(0x00) : id;\r\n    }\r\n    function list(address nft, uint256 tokenId, address payToken, uint256 price, uint256 deadline) external {\r\n        require(deadline \u003e block.timestamp, \"MKT: deadline is in the past\");\r\n        require(price \u003e 0, \"MKT: price is zero\");\r\n        require(payToken == address(0) || IERC20(payToken).totalSupply() \u003e 0, \"MKT: payToken is not valid\");\r\n\r\n        require(IERC721(nft).ownerOf(tokenId) == msg.sender, \"MKT: not owner\");\r\n        require(\r\n            IERC721(nft).getApproved(tokenId) == address(this)\r\n                || IERC721(nft).isApprovedForAll(msg.sender, address(this)),\r\n            \"MKT: not approved\"\r\n        );\r\n        SellOrder memory order = SellOrder({\r\n            seller: msg.sender,\r\n            nft: nft,\r\n            tokenId: tokenId,\r\n            payToken: payToken,\r\n            price: price,\r\n            deadline: deadline\r\n        });\r\n        bytes32 orderId = keccak256(abi.encode(order));\r\n        require(listingOrders[orderId].seller == address(0), \"MKT: order already listed\");\r\n        listingOrders[orderId] = order;\r\n        _lastIds[nft][tokenId] = orderId; \r\n        emit List(nft, tokenId, orderId, msg.sender, payToken, price, deadline);\r\n    }\r\n    function cancel(bytes32 orderId) external {\r\n        address seller = listingOrders[orderId].seller;\r\n        require(seller != address(0), \"MKT: order not listed\");\r\n        require(seller == msg.sender, \"MKT: only seller can cancel\");\r\n        delete listingOrders[orderId];\r\n        emit Cancel(orderId);\r\n    }\r\n    function buy(bytes32 orderId) public payable {\r\n        _buy(orderId, feeTo);\r\n    }\r\n    function buy(bytes32 orderId, bytes calldata signatureForWL) external payable {\r\n        _checkWL(signatureForWL);\r\n        _buy(orderId, address(0));\r\n    }\r\n    function _buy(bytes32 orderId, address feeReceiver) private {\r\n        SellOrder memory order = listingOrders[orderId];\r\n        require(order.seller != address(0), \"MKT: order not listed\");\r\n        require(order.deadline \u003e block.timestamp, \"MKT: order expired\");\r\n\r\n        delete listingOrders[orderId];\r\n        IERC721(order.nft).safeTransferFrom(order.seller, msg.sender, order.tokenId);\r\n\r\n        uint256 fee = feeReceiver == address(0) ? 0 : order.price * feeBP / 10000;\r\n        if (order.payToken == ETH_FLAG) {\r\n            require(msg.value == order.price, \"MKT: wrong eth value\");\r\n        } else {\r\n            require(msg.value == 0, \"MKT: wrong eth value\");\r\n        }\r\n        _transferOut(order.payToken, order.seller, order.price - fee);\r\n        if (fee \u003e 0) _transferOut(order.payToken, feeReceiver, fee);\r\n        emit Sold(orderId, msg.sender, fee);\r\n    }\r\n    function _transferOut(address token, address to, uint256 amount) private {\r\n        if (token == ETH_FLAG) {\r\n            (bool success,) = to.call{value: amount}(\"\");\r\n            require(success, \"MKT: transfer failed\");\r\n        } else {\r\n            SafeERC20.safeTransferFrom(IERC20(token), msg.sender, to, amount);\r\n        }\r\n    }\r\n    bytes32 constant WL_TYPEHASH = keccak256(\"IsWhiteList(address user)\");\r\n    function _checkWL(bytes calldata signature) private view {\r\n        bytes32 wlHash = _hashTypedDataV4(keccak256(abi.encode(WL_TYPEHASH, msg.sender)));\r\n        address signer = ECDSA.recover(wlHash, signature);\r\n        require(signer == whiteListSigner, \"MKT: not whiteListSigner\");\r\n    }\r\n\r\n    function setWhiteListSigner(address signer) external onlyOwner {\r\n        require(signer != address(0), \"MKT: zero address\");\r\n        require(whiteListSigner != signer, \"MKT:repeat set\");\r\n        whiteListSigner = signer;\r\n        emit SetWhiteListSigner(signer);\r\n    }\r\n    function setFeeTo(address to) external onlyOwner {\r\n        require(feeTo != to, \"MKT:repeat set\");\r\n        feeTo = to;\r\n        emit SetFeeTo(to);\r\n    }\r\n    event List(\r\n        address indexed nft,\r\n        uint256 indexed tokenId,\r\n        bytes32 orderId,\r\n        address seller,\r\n        address payToken,\r\n        uint256 price,\r\n        uint256 deadline\r\n    );\r\n    event Cancel(bytes32 orderId);\r\n    event Sold(bytes32 orderId, address buyer, uint256 fee);\r\n    event SetFeeTo(address to);\r\n    event SetWhiteListSigner(address signer);\r\n}\r\n```\r\n\r\n下面是合约代码的解释：\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.20;\r\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\r\nimport {Ownable} from \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport {EIP712} from \"@openzeppelin/contracts/utils/cryptography/EIP712.sol\";\r\nimport {ECDSA} from \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\r\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\r\nimport {IERC721} from \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\r\n\r\n//NFTMarket 合约继承了 Ownable 和 EIP712 合约。Ownable 合约用于管理合约的所有权，构造函数中 msg.sender 作为初始所有者，EIP712 用于定义结构化数据的标准化签名。\r\ncontract NFTMarket is Ownable(msg.sender), EIP712(\"OpenSpaceNFTMarket\", \"1\") {\r\n    //用作以太坊生态系统中的 ETH 标识符\r\n    address public constant ETH_FLAG = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\r\n    //定义手续费设置为千三\r\n    uint256 public constant feeBP = 30; // 30/10000 = 0.3%\r\n    //定义白名单签名者地址\r\n    address public whiteListSigner;\r\n    //定义手续费接收地址\r\n    address public feeTo; \r\n    //挂单的所有订单簿信息// listingOrders[orderId]=SellOrder \r\n    mapping(bytes32 =\u003e SellOrder) public listingOrders; \r\n    //为了方便进行订单检索，反向关联 nft id 最后一个挂单,_lastIds[nft][tokenId] = orderId; \r\n    mapping(address =\u003e mapping(uint256 =\u003e bytes32)) private _lastIds; //  nft -\u003e lastOrderId\r\n    //出售订单的结构体\r\n    struct SellOrder {\r\n        address seller;  //出售者\r\n        address nft;     //出售的nft\r\n        uint256 tokenId; //nft的编号\r\n        address payToken; //要支付的token\r\n        uint256 price;   //价格\r\n        uint256 deadline; //截止日期\r\n    }\r\n    // 用于查询指定 NFT（通过其合约地址 nft 和 Token ID tokenId 唯一标识）的最新挂牌订单\r\n    function listing(address nft, uint256 tokenId) external view returns (bytes32) {\r\n        //从 _lastIds 映射中获取指定 nft 地址和 tokenId 对应的最后一个 ID\r\n        bytes32 id = _lastIds[nft][tokenId];\r\n        //三元运算符 ? :：检查 listingOrders[id].seller 是否为空地址。如果是空地址，返回 bytes32(0x00)；否则返回订单 ID id。\r\n        return listingOrders[id].seller == address(0) ? bytes32(0x00) : id;\r\n    }\r\n    //上架nft\r\n    function list(address nft, uint256 tokenId, address payToken, uint256 price, uint256 deadline) external {\r\n        //验证是否过期\r\n        require(deadline \u003e block.timestamp, \"MKT: deadline is in the past\");\r\n        //价格要大于0\r\n        require(price \u003e 0, \"MKT: price is zero\");\r\n        //验证支付的币为合法的\r\n        require(payToken == address(0) || IERC20(payToken).totalSupply() \u003e 0, \"MKT: payToken is not valid\");\r\n\r\n        // 安全检查，nft的拥有者是交易的发起人\r\n        require(IERC721(nft).ownerOf(tokenId) == msg.sender, \"MKT: not owner\");\r\n        //单个或者全部的nft授权给合约地址\r\n        require(\r\n            IERC721(nft).getApproved(tokenId) == address(this)\r\n                || IERC721(nft).isApprovedForAll(msg.sender, address(this)),\r\n            \"MKT: not approved\"\r\n        );\r\n        //创建挂单订单，memory在函数接下来要用到\r\n        SellOrder memory order = SellOrder({\r\n            seller: msg.sender,\r\n            nft: nft,\r\n            tokenId: tokenId,\r\n            payToken: payToken,\r\n            price: price,\r\n            deadline: deadline\r\n        });\r\n        //将订单进行哈希，拿到orderId\r\n        bytes32 orderId = keccak256(abi.encode(order));\r\n        // 安全检查看用户是否重复上架\r\n        require(listingOrders[orderId].seller == address(0), \"MKT: order already listed\");\r\n        //上架\r\n        listingOrders[orderId] = order;\r\n        //上架后进行一个反向绑定，方便检索订单\r\n        _lastIds[nft][tokenId] = orderId; \r\n        //触发事件\r\n        emit List(nft, tokenId, orderId, msg.sender, payToken, price, deadline);\r\n    }\r\n    //取消订单\r\n    function cancel(bytes32 orderId) external {\r\n        //给一个orderId查出seller\r\n        address seller = listingOrders[orderId].seller;\r\n        // 检查seller是否空地址，是否拥有者\r\n        require(seller != address(0), \"MKT: order not listed\");\r\n        require(seller == msg.sender, \"MKT: only seller can cancel\");\r\n        //删除订单\r\n        delete listingOrders[orderId];\r\n        //记录删除事件\r\n        emit Cancel(orderId);\r\n    }\r\n    //普通用户根据id买，需要收费地址\r\n    function buy(bytes32 orderId) public payable {\r\n        _buy(orderId, feeTo);\r\n    }\r\n    //白名单用户根据ID买，不需要收费地址\r\n    function buy(bytes32 orderId, bytes calldata signatureForWL) external payable {\r\n        //验证白名单，需要白名单用户的signatureForWL\r\n        _checkWL(signatureForWL);\r\n        // 交易的手续费为0\r\n        _buy(orderId, address(0));\r\n    }\r\n    //用户买的实现\r\n    function _buy(bytes32 orderId, address feeReceiver) private {\r\n        // 0. 检查读取交易的订单\r\n        SellOrder memory order = listingOrders[orderId];\r\n\r\n        // 1. 看订单是否存在？\r\n        require(order.seller != address(0), \"MKT: order not listed\");\r\n        require(order.deadline \u003e block.timestamp, \"MKT: order expired\");\r\n\r\n        // 2. 删除订单信息 防止重入攻击\r\n        delete listingOrders[orderId];\r\n        // 3.  NFT 交货\r\n        IERC721(order.nft).safeTransferFrom(order.seller, msg.sender, order.tokenId);\r\n\r\n        // 4.  token 交钱\r\n        // fee 0.3% or 0 设置fee用的情况，三元函数计算交易的手续费\r\n        uint256 fee = feeReceiver == address(0) ? 0 : order.price * feeBP / 10000;\r\n        // 用户发送的以太币（ETH）数量是否正确\r\n        if (order.payToken == ETH_FLAG) {\r\n            //确保用户发送的 ETH 数量与订单价格一致\r\n            require(msg.value == order.price, \"MKT: wrong eth value\");\r\n        } else {\r\n            //订单中指定的支付代币不是 ETH，要求用户在调用此函数时不能发送 ETH\r\n            require(msg.value == 0, \"MKT: wrong eth value\");\r\n        }\r\n        // 5. 支付卖家\r\n        _transferOut(order.payToken, order.seller, order.price - fee);\r\n        // 6. 订单完成，如果费用大于0，将费用从订单中指定的支付代币转移给接收者\r\n        if (fee \u003e 0) _transferOut(order.payToken, feeReceiver, fee);\r\n        //触发 Sold 事件，记录订单ID、买家地址和交易费用。\r\n        emit Sold(orderId, msg.sender, fee);\r\n    }\r\n    //执行转账\r\n    function _transferOut(address token, address to, uint256 amount) private {\r\n        if (token == ETH_FLAG) {\r\n            // 如果是eth，使用call执行转账并检查成功\r\n            (bool success,) = to.call{value: amount}(\"\");\r\n            require(success, \"MKT: transfer failed\");\r\n        } else {\r\n            //如果是erc20代币，使用safeerc20转账\r\n            SafeERC20.safeTransferFrom(IERC20(token), msg.sender, to, amount);\r\n        }\r\n    }\r\n    // 定义一个白名单的哈希，这个白名单是线下就做好的\r\n    bytes32 constant WL_TYPEHASH = keccak256(\"IsWhiteList(address user)\");\r\n    // 做一个白名单验证\r\n    function _checkWL(bytes calldata signature) private view {\r\n        // 调用合约的账户地址（买家）msg.sender和WL_TYPEHASH编码，对编码后的数据进行哈希运算，使其符合 EIP-712 规范\r\n        bytes32 wlHash = _hashTypedDataV4(keccak256(abi.encode(WL_TYPEHASH, msg.sender)));\r\n        //使用 ECDSA 算法恢复签名者的地址\r\n        address signer = ECDSA.recover(wlHash, signature);\r\n        //检查签名者是否为白名单签名者\r\n        require(signer == whiteListSigner, \"MKT: not whiteListSigner\");\r\n    }\r\n\r\n    // 主要作用是设置或更改白名单签名者的地址\r\n    function setWhiteListSigner(address signer) external onlyOwner {\r\n        //检查传入的 signer 地址是否为零地址（address(0)）\r\n        require(signer != address(0), \"MKT: zero address\");\r\n        //检查传入的 signer 地址是否与当前的 whiteListSigner 相同\r\n        require(whiteListSigner != signer, \"MKT:repeat set\");\r\n        // signer：新设置的白名单签名者的地址，将 whiteListSigner 更新为新的 signer 地址\r\n        whiteListSigner = signer;\r\n        //触发 SetWhiteListSigner 事件，记录新的白名单签名者地址\r\n        emit SetWhiteListSigner(signer);\r\n    }\r\n    //用于设置或更改费用接收者的地址\r\n    function setFeeTo(address to) external onlyOwner {\r\n        //检查传入的 to 地址是否与当前的 feeTo 地址相同\r\n        require(feeTo != to, \"MKT:repeat set\");\r\n        //将 feeTo 更新为新的 to 地址\r\n        feeTo = to;\r\n        //触发 SetFeeTo 事件，记录新的费用接收者地址\r\n        emit SetFeeTo(to);\r\n    }\r\n    //事件记录了 NFT 的挂牌信息\r\n    event List(\r\n        address indexed nft,\r\n        uint256 indexed tokenId,\r\n        bytes32 orderId,\r\n        address seller,\r\n        address payToken,\r\n        uint256 price,\r\n        uint256 deadline\r\n    );\r\n    //事件记录了订单的取消信息\r\n    event Cancel(bytes32 orderId);\r\n    //事件记录了 NFT 的售出信息及相关费用\r\n    event Sold(bytes32 orderId, address buyer, uint256 fee);\r\n    //事件记录了费用接收者地址的变更\r\n    event SetFeeTo(address to);\r\n    //事件记录了白名单签名者地址的变更\r\n    event SetWhiteListSigner(address signer);\r\n}\r\n```\r\n\r\n### 5. 编写部署脚本\r\n\r\n在 `script` 目录下创建部署脚本 `DeployNFTMarket.s.sol`：\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.20;\r\nimport {Script, console} from \"forge-std/Script.sol\";\r\nimport {NFTMarket} from \"../src/NFTMarket.sol\";\r\n\r\ncontract DeployNFTMarket is Script {\r\n    function run() external {\r\n        address feeTo = 0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69; \r\n        address whiteListSigner = 0x65034a9364DF72534d98Acb96658450f9254ff59; \r\n\r\n        vm.startBroadcast();\r\n        \r\n        NFTMarket nftMarket = new NFTMarket();\r\n        \r\n        nftMarket.setFeeTo(feeTo);\r\n\r\n        nftMarket.setWhiteListSigner(whiteListSigner);\r\n        \r\n        console.log(\"NFTMarket deployed to:\", address(nftMarket));\r\n        \r\n        vm.stopBroadcast();\r\n    }\r\n}\r\n```\r\n\r\n### 6. 编译合约\r\n\r\n编译合约代码：\r\n\r\n```\r\nforge build\r\n```\r\n\r\n输出结果为：\r\n\r\n```\r\nyhb@yhbdeMacBook-Air MyNFTMarketProject % forge clean\r\nforge build\r\n\r\n[⠊] Compiling...\r\n[⠔] Compiling 50 files with Solc 0.8.25\r\n[⠒] Solc 0.8.25 finished in 1.48s\r\nCompiler run successful!\r\n```\r\n\r\n### \r\n\r\n### 7. 部署合约\r\n\r\n使用 Foundry 的 `forge script` 命令部署合约。首先，确保你有一个钱包私钥并导出到环境变量：\r\n\r\n```\r\nexport PRIVATE_KEY=your_PRIVATE_KEY\r\nexport RPC_URL=your_RPC_URL\r\n```\r\n\r\n然后运行部署脚本：\r\n\r\n```\r\nforge script script/DeployNFTMarket.s.sol --rpc-url ${RPC_URL}  --broadcast --verify -vvvv --private-key ${PRIVATE_KEY}\r\n```\r\n\r\n输出结果为：\r\n\r\n```\r\nyhb@yhbdeMacBook-Air MyNFTMarketProject % forge script script/DeployNFTMarket.s.sol --rpc-url ${RPC_URL} --broadcast --verify -vvvv --private-key ${PRIVATE_KEY}\r\n\r\n[⠊] Compiling...\r\nNo files changed, compilation skipped\r\nTraces:\r\n  [1462158] DeployNFTMarket::run()\r\n    ├─ [0] VM::startBroadcast()\r\n    │   └─ ← [Return] \r\n    ├─ [1372994] → new NFTMarket@0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99\r\n    │   ├─ emit OwnershipTransferred(previousOwner: 0x0000000000000000000000000000000000000000, newOwner: 0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69)\r\n    │   └─ ← [Return] 6733 bytes of code\r\n    ├─ [23907] NFTMarket::setFeeTo(0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69)\r\n    │   ├─ emit SetFeeTo(to: 0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69)\r\n    │   └─ ← [Stop] \r\n    ├─ [23910] NFTMarket::setWhiteListSigner(0x65034a9364DF72534d98Acb96658450f9254ff59)\r\n    │   ├─ emit SetWhiteListSigner(signer: 0x65034a9364DF72534d98Acb96658450f9254ff59)\r\n    │   └─ ← [Stop] \r\n    ├─ [0] console::log(\"NFTMarket deployed to:\", NFTMarket: [0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99]) [staticcall]\r\n    │   └─ ← [Stop] \r\n    ├─ [0] VM::stopBroadcast()\r\n    │   └─ ← [Return] \r\n    └─ ← [Stop] \r\n\r\n\r\nScript ran successfully.\r\n\r\n== Logs ==\r\n  NFTMarket deployed to: 0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99\r\n\r\n## Setting up 1 EVM.\r\n==========================\r\nSimulated On-chain Traces:\r\n\r\n  [1372994] → new NFTMarket@0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99\r\n    ├─ emit OwnershipTransferred(previousOwner: 0x0000000000000000000000000000000000000000, newOwner: 0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69)\r\n    └─ ← [Return] 6733 bytes of code\r\n\r\n  [25907] NFTMarket::setFeeTo(0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69)\r\n    ├─ emit SetFeeTo(to: 0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69)\r\n    └─ ← [Stop] \r\n\r\n  [25910] NFTMarket::setWhiteListSigner(0x65034a9364DF72534d98Acb96658450f9254ff59)\r\n    ├─ emit SetWhiteListSigner(signer: 0x65034a9364DF72534d98Acb96658450f9254ff59)\r\n    └─ ← [Stop] \r\n\r\n\r\n==========================\r\n\r\nChain 11155111\r\n\r\nEstimated gas price: 12.282704523 gwei\r\n\r\nEstimated total gas used for script: 2145763\r\n\r\nEstimated amount required: 0.026355772905386049 ETH\r\n\r\n==========================\r\n\r\n##### sepolia\r\n✅  [Success]Hash: 0xfef111d53d0a9c3e3304a7f40f56cab3bb21cd22e2b8156196950bcef7df4c64\r\nContract Address: 0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99\r\nBlock: 6344838\r\nPaid: 0.00948078701950216 ETH (1547032 gas * 6.12837163 gwei)\r\n\r\n\r\n##### sepolia\r\n✅  [Success]Hash: 0x5bb84c32803b8f510e67627f68e797721fc02eb0b728ba5c558f98ae50f4d20d\r\nBlock: 6344838\r\nPaid: 0.00029011098459257 ETH (47339 gas * 6.12837163 gwei)\r\n\r\n\r\n##### sepolia\r\n✅  [Success]Hash: 0x195615ec2d73f64441a773824684292f5b477d2cf89ac6a3d69f323ee73547e3\r\nBlock: 6344838\r\nPaid: 0.00029012936970746 ETH (47342 gas * 6.12837163 gwei)\r\n\r\n✅ Sequence #1 on sepolia | Total Paid: 0.01006102737380219 ETH (1641713 gas * avg 6.12837163 gwei)\r\n                                                                                                                                                                  \r\n\r\n==========================\r\n\r\nONCHAIN EXECUTION COMPLETE \u0026 SUCCESSFUL.\r\n##\r\nStart verification for (1) contracts\r\nStart verifying contract `0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99` deployed on sepolia\r\n\r\nSubmitting verification for [src/NFTMarket.sol:NFTMarket] 0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99.\r\n\r\nSubmitting verification for [src/NFTMarket.sol:NFTMarket] 0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99.\r\n\r\nSubmitting verification for [src/NFTMarket.sol:NFTMarket] 0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99.\r\n\r\nSubmitting verification for [src/NFTMarket.sol:NFTMarket] 0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99.\r\nSubmitted contract for verification:\r\n        Response: `OK`\r\n        GUID: `mv4uvpt29eawjruhy91egtzj1y1f5su1xyrwrwnemk8ps8kwbv`\r\n        URL: https://sepolia.etherscan.io/address/0x23225a72386c4cbcd5cb08c2e36f4fae56b40e99\r\nContract verification status:\r\nResponse: `NOTOK`\r\nDetails: `Pending in queue`\r\nContract verification status:\r\nResponse: `OK`\r\nDetails: `Pass - Verified`\r\nContract successfully verified\r\nAll (1) contracts were verified!\r\n\r\nTransactions saved to: /Users/yhb/MyNFTMarketProject/broadcast/DeployNFTMarket.s.sol/11155111/run-latest.json\r\n\r\nSensitive values saved to: /Users/yhb/MyNFTMarketProject/cache/DeployNFTMarket.s.sol/11155111/run-latest.json\r\n\r\n```\r\n\r\n好现在合约开源了合约地址是0x23225A72386c4CbcD5CB08c2E36f4FaE56B40E99， 使用 TheGraph 索引 NFTMarket 的上架List和成交Sold记录\r\n\r\n### 8.登录官网创建子图\r\n\r\n登录官网：https://thegraph.com/studio/，连接钱包，进行邮件验证。验证成功后，点击创建一个子图。\r\n\r\n\r\n![image-20240720205601356.png](https://img.learnblockchain.cn/attachments/2024/07/pwDVsHUJ669c5282ae7ae.png)\r\n\r\n创建成功后\r\n\r\n![image-20240720210013403.png](https://img.learnblockchain.cn/attachments/2024/07/lMDmAKNg669c5292ebed8.png)\r\n### 9. 安装 The Graph CLI\r\n\r\n首先，你需要安装 The Graph 的命令行工具：\r\n\r\n```\r\nnpm install -g @graphprotocol/graph-cli\r\n```\r\n\r\n网络不好运行，换一个命令：`yarn global add @graphprotocol/graph-cli`\r\n\r\n### 10. 初始化子图\r\n\r\n在你的项目目录中，使用 `graph init` 命令初始化一个新的子图：\r\n\r\n```\r\ngraph init --studio yhbnftmarket\r\n```\r\n\r\n输出结果为：\r\n\r\n```\r\nhb@yhbdeMacBook-Air MyNFTMarketProject % graph init --studio yhbnftmarket\r\n ›   Warning: In next major version, this flag will be removed. By default we \r\n ›   will deploy to the Graph Studio. Learn more about Sunrise of Decentralized\r\n ›    Data \r\n ›   https://thegraph.com/blog/unveiling-updated-sunrise-decentralized-data/\r\n ›   Warning: In next major version, this flag will be removed. By default we \r\n ›   will deploy to the Graph Studio. Learn more about Sunrise of Decentralized\r\n ›    Data \r\n ›   https://thegraph.com/blog/unveiling-updated-sunrise-decentralized-data/\r\n ›   Warning: In next major version, this flag will be removed. By default we \r\n ›   will stop initializing a Git repository.\r\n✔ Protocol · ethereum\r\n✔ Subgraph slug · yhbnftmarket\r\n✔ Directory to create the subgraph in · yhbnftmarket\r\n✔ Ethereum network · sepolia\r\n✔ Contract address · 0x23225a72386c4cbcd5cb08c2e36f4fae56b40e99\r\n✔ Fetching ABI from Etherscan\r\n✖ Failed to fetch Start Block: Failed to fetch contract creation transaction hash\r\n✔ Do you want to retry? (Y/n) · true\r\n✖ Failed to fetch Start Block: Failed to fetch contract creation transaction hash\r\n✔ Do you want to retry? (Y/n) · true\r\n✔ Fetching Start Block\r\n✖ Failed to fetch Contract Name: Failed to fetch contract source code\r\n✔ Do you want to retry? (Y/n) · true\r\n✖ Failed to fetch Contract Name: Failed to fetch contract source code\r\n✔ Do you want to retry? (Y/n) · true\r\n✖ Failed to fetch Contract Name: Failed to fetch contract source code\r\n✔ Do you want to retry? (Y/n) · true\r\n✖ Failed to fetch Contract Name: Failed to fetch contract source code\r\n✔ Do you want to retry? (Y/n) · true\r\n✔ Fetching Contract Name\r\n✔ Start Block · 6344838\r\n✔ Contract Name · NFTMarket\r\n✔ Index contract events as entities (Y/n) · true\r\n  Generate subgraph\r\n  Write subgraph to directory\r\n✔ Create subgraph scaffold\r\n✔ Initialize networks config\r\n✔ Initialize subgraph repository\r\n✔ Install dependencies with yarn\r\n✔ Generate ABI and schema types with yarn codegen\r\nAdd another contract? (y/n): \r\nSubgraph yhbnftmarket created in yhbnftmarket\r\n\r\nNext steps:\r\n\r\n  1. Run `graph auth` to authenticate with your deploy key.\r\n\r\n  2. Type `cd yhbnftmarket` to enter the subgraph.\r\n\r\n  3. Run `yarn deploy` to deploy the subgraph.\r\n\r\nMake sure to visit the documentation on https://thegraph.com/docs/ for further information.\r\n```\r\n\r\n初始化子图会做以下的工作\r\n\r\n#### 1. 配置 `subgraph.yaml`\r\n\r\n会生成 `subgraph.yaml` 文件，配置你的子图的程序结构。`subgraph.yaml` 文件示例：\r\n\r\n```\r\nspecVersion: 1.0.0\r\nindexerHints:\r\n  prune: auto\r\nschema:\r\n  file: ./schema.graphql\r\ndataSources:\r\n  - kind: ethereum\r\n    name: NFTMarket\r\n    network: sepolia\r\n    source:\r\n      address: \"0x23225a72386c4cbcd5cb08c2e36f4fae56b40e99\"\r\n      abi: NFTMarket\r\n      startBlock: 6344838\r\n    mapping:\r\n      kind: ethereum/events\r\n      apiVersion: 0.0.7\r\n      language: wasm/assemblyscript\r\n      entities:\r\n        - Cancel\r\n        - EIP712DomainChanged\r\n        - List\r\n        - OwnershipTransferred\r\n        - SetFeeTo\r\n        - SetWhiteListSigner\r\n        - Sold\r\n      abis:\r\n        - name: NFTMarket\r\n          file: ./abis/NFTMarket.json\r\n      eventHandlers:\r\n        - event: Cancel(bytes32)\r\n          handler: handleCancel\r\n        - event: EIP712DomainChanged()\r\n          handler: handleEIP712DomainChanged\r\n        - event: List(indexed address,indexed uint256,bytes32,address,address,uint256,uint256)\r\n          handler: handleList\r\n        - event: OwnershipTransferred(indexed address,indexed address)\r\n          handler: handleOwnershipTransferred\r\n        - event: SetFeeTo(address)\r\n          handler: handleSetFeeTo\r\n        - event: SetWhiteListSigner(address)\r\n          handler: handleSetWhiteListSigner\r\n        - event: Sold(bytes32,address,uint256)\r\n          handler: handleSold\r\n      file: ./src/nft-market.ts\r\n\r\n```\r\n\r\n#### 2. 定义 GraphQL 模式 (`schema.graphql`)\r\n\r\n创建或编辑 `schema.graphql` 文件，定义你的数据模型：\r\n\r\n```\r\ntype Cancel @entity(immutable: true) {\r\n  id: Bytes!\r\n  orderId: Bytes! # bytes32\r\n  blockNumber: BigInt!\r\n  blockTimestamp: BigInt!\r\n  transactionHash: Bytes!\r\n}\r\n\r\ntype EIP712DomainChanged @entity(immutable: true) {\r\n  id: Bytes!\r\n\r\n  blockNumber: BigInt!\r\n  blockTimestamp: BigInt!\r\n  transactionHash: Bytes!\r\n}\r\n\r\ntype List @entity(immutable: true) {\r\n  id: Bytes!\r\n  nft: Bytes! # address\r\n  tokenId: BigInt! # uint256\r\n  orderId: Bytes! # bytes32\r\n  seller: Bytes! # address\r\n  payToken: Bytes! # address\r\n  price: BigInt! # uint256\r\n  deadline: BigInt! # uint256\r\n  blockNumber: BigInt!\r\n  blockTimestamp: BigInt!\r\n  transactionHash: Bytes!\r\n}\r\n\r\ntype OwnershipTransferred @entity(immutable: true) {\r\n  id: Bytes!\r\n  previousOwner: Bytes! # address\r\n  newOwner: Bytes! # address\r\n  blockNumber: BigInt!\r\n  blockTimestamp: BigInt!\r\n  transactionHash: Bytes!\r\n}\r\n\r\ntype SetFeeTo @entity(immutable: true) {\r\n  id: Bytes!\r\n  to: Bytes! # address\r\n  blockNumber: BigInt!\r\n  blockTimestamp: BigInt!\r\n  transactionHash: Bytes!\r\n}\r\n\r\ntype SetWhiteListSigner @entity(immutable: true) {\r\n  id: Bytes!\r\n  signer: Bytes! # address\r\n  blockNumber: BigInt!\r\n  blockTimestamp: BigInt!\r\n  transactionHash: Bytes!\r\n}\r\n\r\ntype Sold @entity(immutable: true) {\r\n  id: Bytes!\r\n  orderId: Bytes! # bytes32\r\n  buyer: Bytes! # address\r\n  fee: BigInt! # uint256\r\n  blockNumber: BigInt!\r\n  blockTimestamp: BigInt!\r\n  transactionHash: Bytes!\r\n}\r\n\r\n```\r\n\r\n#### 3. 创建映射文件 (`mapping.ts`)\r\n\r\n在 `src` 目录中创建或编辑 `mapping.ts` 文件，处理 `Listed` 和 `Sold` 等等事件的逻辑：\r\n\r\n```\r\nimport {\r\n  Cancel as CancelEvent,\r\n  EIP712DomainChanged as EIP712DomainChangedEvent,\r\n  List as ListEvent,\r\n  OwnershipTransferred as OwnershipTransferredEvent,\r\n  SetFeeTo as SetFeeToEvent,\r\n  SetWhiteListSigner as SetWhiteListSignerEvent,\r\n  Sold as SoldEvent\r\n} from \"../generated/NFTMarket/NFTMarket\"\r\nimport {\r\n  Cancel,\r\n  EIP712DomainChanged,\r\n  List,\r\n  OwnershipTransferred,\r\n  SetFeeTo,\r\n  SetWhiteListSigner,\r\n  Sold\r\n} from \"../generated/schema\"\r\n\r\nexport function handleCancel(event: CancelEvent): void {\r\n  let entity = new Cancel(\r\n    event.transaction.hash.concatI32(event.logIndex.toI32())\r\n  )\r\n  entity.orderId = event.params.orderId\r\n\r\n  entity.blockNumber = event.block.number\r\n  entity.blockTimestamp = event.block.timestamp\r\n  entity.transactionHash = event.transaction.hash\r\n\r\n  entity.save()\r\n}\r\n\r\nexport function handleEIP712DomainChanged(\r\n  event: EIP712DomainChangedEvent\r\n): void {\r\n  let entity = new EIP712DomainChanged(\r\n    event.transaction.hash.concatI32(event.logIndex.toI32())\r\n  )\r\n\r\n  entity.blockNumber = event.block.number\r\n  entity.blockTimestamp = event.block.timestamp\r\n  entity.transactionHash = event.transaction.hash\r\n\r\n  entity.save()\r\n}\r\n\r\nexport function handleList(event: ListEvent): void {\r\n  let entity = new List(\r\n    event.transaction.hash.concatI32(event.logIndex.toI32())\r\n  )\r\n  entity.nft = event.params.nft\r\n  entity.tokenId = event.params.tokenId\r\n  entity.orderId = event.params.orderId\r\n  entity.seller = event.params.seller\r\n  entity.payToken = event.params.payToken\r\n  entity.price = event.params.price\r\n  entity.deadline = event.params.deadline\r\n\r\n  entity.blockNumber = event.block.number\r\n  entity.blockTimestamp = event.block.timestamp\r\n  entity.transactionHash = event.transaction.hash\r\n\r\n  entity.save()\r\n}\r\n\r\nexport function handleOwnershipTransferred(\r\n  event: OwnershipTransferredEvent\r\n): void {\r\n  let entity = new OwnershipTransferred(\r\n    event.transaction.hash.concatI32(event.logIndex.toI32())\r\n  )\r\n  entity.previousOwner = event.params.previousOwner\r\n  entity.newOwner = event.params.newOwner\r\n\r\n  entity.blockNumber = event.block.number\r\n  entity.blockTimestamp = event.block.timestamp\r\n  entity.transactionHash = event.transaction.hash\r\n\r\n  entity.save()\r\n}\r\n\r\nexport function handleSetFeeTo(event: SetFeeToEvent): void {\r\n  let entity = new SetFeeTo(\r\n    event.transaction.hash.concatI32(event.logIndex.toI32())\r\n  )\r\n  entity.to = event.params.to\r\n\r\n  entity.blockNumber = event.block.number\r\n  entity.blockTimestamp = event.block.timestamp\r\n  entity.transactionHash = event.transaction.hash\r\n\r\n  entity.save()\r\n}\r\n\r\nexport function handleSetWhiteListSigner(event: SetWhiteListSignerEvent): void {\r\n  let entity = new SetWhiteListSigner(\r\n    event.transaction.hash.concatI32(event.logIndex.toI32())\r\n  )\r\n  entity.signer = event.params.signer\r\n\r\n  entity.blockNumber = event.block.number\r\n  entity.blockTimestamp = event.block.timestamp\r\n  entity.transactionHash = event.transaction.hash\r\n\r\n  entity.save()\r\n}\r\n\r\nexport function handleSold(event: SoldEvent): void {\r\n  let entity = new Sold(\r\n    event.transaction.hash.concatI32(event.logIndex.toI32())\r\n  )\r\n  entity.orderId = event.params.orderId\r\n  entity.buyer = event.params.buyer\r\n  entity.fee = event.params.fee\r\n\r\n  entity.blockNumber = event.block.number\r\n  entity.blockTimestamp = event.block.timestamp\r\n  entity.transactionHash = event.transaction.hash\r\n\r\n  entity.save()\r\n}\r\n\r\n```\r\n\r\n#### 4. 生成 ABI 文件\r\n\r\n这里如果是合约开源，就在下面部署的时候会自动拉取，省去这个步骤。如果不是开源那就需要准备 ABI。你可以从编译后的合约 JSON 文件中提取 ABI放到一个目录中，比如：/Users/yhb/MyNFTMarketProject/yhbnftmarket/abis/NFTMarket.json\r\n\r\n```\r\n[\r\n  { \"inputs\": [], \"name\": \"ECDSAInvalidSignature\", \"type\": \"error\" },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"uint256\", \"name\": \"length\", \"type\": \"uint256\" }\r\n    ],\r\n    \"name\": \"ECDSAInvalidSignatureLength\",\r\n    \"type\": \"error\"\r\n  },\r\n  {\r\n    \"inputs\": [{ \"internalType\": \"bytes32\", \"name\": \"s\", \"type\": \"bytes32\" }],\r\n    \"name\": \"ECDSAInvalidSignatureS\",\r\n    \"type\": \"error\"\r\n  },\r\n  { \"inputs\": [], \"name\": \"InvalidShortString\", \"type\": \"error\" },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"address\", \"name\": \"owner\", \"type\": \"address\" }\r\n    ],\r\n    \"name\": \"OwnableInvalidOwner\",\r\n    \"type\": \"error\"\r\n  },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"address\", \"name\": \"account\", \"type\": \"address\" }\r\n    ],\r\n    \"name\": \"OwnableUnauthorizedAccount\",\r\n    \"type\": \"error\"\r\n  },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"address\", \"name\": \"token\", \"type\": \"address\" }\r\n    ],\r\n    \"name\": \"SafeERC20FailedOperation\",\r\n    \"type\": \"error\"\r\n  },\r\n  {\r\n    \"inputs\": [{ \"internalType\": \"string\", \"name\": \"str\", \"type\": \"string\" }],\r\n    \"name\": \"StringTooLong\",\r\n    \"type\": \"error\"\r\n  },\r\n  {\r\n    \"anonymous\": false,\r\n    \"inputs\": [\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"bytes32\",\r\n        \"name\": \"orderId\",\r\n        \"type\": \"bytes32\"\r\n      }\r\n    ],\r\n    \"name\": \"Cancel\",\r\n    \"type\": \"event\"\r\n  },\r\n  {\r\n    \"anonymous\": false,\r\n    \"inputs\": [],\r\n    \"name\": \"EIP712DomainChanged\",\r\n    \"type\": \"event\"\r\n  },\r\n  {\r\n    \"anonymous\": false,\r\n    \"inputs\": [\r\n      {\r\n        \"indexed\": true,\r\n        \"internalType\": \"address\",\r\n        \"name\": \"nft\",\r\n        \"type\": \"address\"\r\n      },\r\n      {\r\n        \"indexed\": true,\r\n        \"internalType\": \"uint256\",\r\n        \"name\": \"tokenId\",\r\n        \"type\": \"uint256\"\r\n      },\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"bytes32\",\r\n        \"name\": \"orderId\",\r\n        \"type\": \"bytes32\"\r\n      },\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"address\",\r\n        \"name\": \"seller\",\r\n        \"type\": \"address\"\r\n      },\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"address\",\r\n        \"name\": \"payToken\",\r\n        \"type\": \"address\"\r\n      },\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"uint256\",\r\n        \"name\": \"price\",\r\n        \"type\": \"uint256\"\r\n      },\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"uint256\",\r\n        \"name\": \"deadline\",\r\n        \"type\": \"uint256\"\r\n      }\r\n    ],\r\n    \"name\": \"List\",\r\n    \"type\": \"event\"\r\n  },\r\n  {\r\n    \"anonymous\": false,\r\n    \"inputs\": [\r\n      {\r\n        \"indexed\": true,\r\n        \"internalType\": \"address\",\r\n        \"name\": \"previousOwner\",\r\n        \"type\": \"address\"\r\n      },\r\n      {\r\n        \"indexed\": true,\r\n        \"internalType\": \"address\",\r\n        \"name\": \"newOwner\",\r\n        \"type\": \"address\"\r\n      }\r\n    ],\r\n    \"name\": \"OwnershipTransferred\",\r\n    \"type\": \"event\"\r\n  },\r\n  {\r\n    \"anonymous\": false,\r\n    \"inputs\": [\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"address\",\r\n        \"name\": \"to\",\r\n        \"type\": \"address\"\r\n      }\r\n    ],\r\n    \"name\": \"SetFeeTo\",\r\n    \"type\": \"event\"\r\n  },\r\n  {\r\n    \"anonymous\": false,\r\n    \"inputs\": [\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"address\",\r\n        \"name\": \"signer\",\r\n        \"type\": \"address\"\r\n      }\r\n    ],\r\n    \"name\": \"SetWhiteListSigner\",\r\n    \"type\": \"event\"\r\n  },\r\n  {\r\n    \"anonymous\": false,\r\n    \"inputs\": [\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"bytes32\",\r\n        \"name\": \"orderId\",\r\n        \"type\": \"bytes32\"\r\n      },\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"address\",\r\n        \"name\": \"buyer\",\r\n        \"type\": \"address\"\r\n      },\r\n      {\r\n        \"indexed\": false,\r\n        \"internalType\": \"uint256\",\r\n        \"name\": \"fee\",\r\n        \"type\": \"uint256\"\r\n      }\r\n    ],\r\n    \"name\": \"Sold\",\r\n    \"type\": \"event\"\r\n  },\r\n  {\r\n    \"inputs\": [],\r\n    \"name\": \"ETH_FLAG\",\r\n    \"outputs\": [{ \"internalType\": \"address\", \"name\": \"\", \"type\": \"address\" }],\r\n    \"stateMutability\": \"view\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"bytes32\", \"name\": \"orderId\", \"type\": \"bytes32\" },\r\n      { \"internalType\": \"bytes\", \"name\": \"signatureForWL\", \"type\": \"bytes\" }\r\n    ],\r\n    \"name\": \"buy\",\r\n    \"outputs\": [],\r\n    \"stateMutability\": \"payable\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"bytes32\", \"name\": \"orderId\", \"type\": \"bytes32\" }\r\n    ],\r\n    \"name\": \"buy\",\r\n    \"outputs\": [],\r\n    \"stateMutability\": \"payable\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"bytes32\", \"name\": \"orderId\", \"type\": \"bytes32\" }\r\n    ],\r\n    \"name\": \"cancel\",\r\n    \"outputs\": [],\r\n    \"stateMutability\": \"nonpayable\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [],\r\n    \"name\": \"eip712Domain\",\r\n    \"outputs\": [\r\n      { \"internalType\": \"bytes1\", \"name\": \"fields\", \"type\": \"bytes1\" },\r\n      { \"internalType\": \"string\", \"name\": \"name\", \"type\": \"string\" },\r\n      { \"internalType\": \"string\", \"name\": \"version\", \"type\": \"string\" },\r\n      { \"internalType\": \"uint256\", \"name\": \"chainId\", \"type\": \"uint256\" },\r\n      {\r\n        \"internalType\": \"address\",\r\n        \"name\": \"verifyingContract\",\r\n        \"type\": \"address\"\r\n      },\r\n      { \"internalType\": \"bytes32\", \"name\": \"salt\", \"type\": \"bytes32\" },\r\n      { \"internalType\": \"uint256[]\", \"name\": \"extensions\", \"type\": \"uint256[]\" }\r\n    ],\r\n    \"stateMutability\": \"view\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [],\r\n    \"name\": \"feeBP\",\r\n    \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" }],\r\n    \"stateMutability\": \"view\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [],\r\n    \"name\": \"feeTo\",\r\n    \"outputs\": [{ \"internalType\": \"address\", \"name\": \"\", \"type\": \"address\" }],\r\n    \"stateMutability\": \"view\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"address\", \"name\": \"nft\", \"type\": \"address\" },\r\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" },\r\n      { \"internalType\": \"address\", \"name\": \"payToken\", \"type\": \"address\" },\r\n      { \"internalType\": \"uint256\", \"name\": \"price\", \"type\": \"uint256\" },\r\n      { \"internalType\": \"uint256\", \"name\": \"deadline\", \"type\": \"uint256\" }\r\n    ],\r\n    \"name\": \"list\",\r\n    \"outputs\": [],\r\n    \"stateMutability\": \"nonpayable\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"address\", \"name\": \"nft\", \"type\": \"address\" },\r\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" }\r\n    ],\r\n    \"name\": \"listing\",\r\n    \"outputs\": [{ \"internalType\": \"bytes32\", \"name\": \"\", \"type\": \"bytes32\" }],\r\n    \"stateMutability\": \"view\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [{ \"internalType\": \"bytes32\", \"name\": \"\", \"type\": \"bytes32\" }],\r\n    \"name\": \"listingOrders\",\r\n    \"outputs\": [\r\n      { \"internalType\": \"address\", \"name\": \"seller\", \"type\": \"address\" },\r\n      { \"internalType\": \"address\", \"name\": \"nft\", \"type\": \"address\" },\r\n      { \"internalType\": \"uint256\", \"name\": \"tokenId\", \"type\": \"uint256\" },\r\n      { \"internalType\": \"address\", \"name\": \"payToken\", \"type\": \"address\" },\r\n      { \"internalType\": \"uint256\", \"name\": \"price\", \"type\": \"uint256\" },\r\n      { \"internalType\": \"uint256\", \"name\": \"deadline\", \"type\": \"uint256\" }\r\n    ],\r\n    \"stateMutability\": \"view\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [],\r\n    \"name\": \"owner\",\r\n    \"outputs\": [{ \"internalType\": \"address\", \"name\": \"\", \"type\": \"address\" }],\r\n    \"stateMutability\": \"view\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [],\r\n    \"name\": \"renounceOwnership\",\r\n    \"outputs\": [],\r\n    \"stateMutability\": \"nonpayable\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [{ \"internalType\": \"address\", \"name\": \"to\", \"type\": \"address\" }],\r\n    \"name\": \"setFeeTo\",\r\n    \"outputs\": [],\r\n    \"stateMutability\": \"nonpayable\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"address\", \"name\": \"signer\", \"type\": \"address\" }\r\n    ],\r\n    \"name\": \"setWhiteListSigner\",\r\n    \"outputs\": [],\r\n    \"stateMutability\": \"nonpayable\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [\r\n      { \"internalType\": \"address\", \"name\": \"newOwner\", \"type\": \"address\" }\r\n    ],\r\n    \"name\": \"transferOwnership\",\r\n    \"outputs\": [],\r\n    \"stateMutability\": \"nonpayable\",\r\n    \"type\": \"function\"\r\n  },\r\n  {\r\n    \"inputs\": [],\r\n    \"name\": \"whiteListSigner\",\r\n    \"outputs\": [{ \"internalType\": \"address\", \"name\": \"\", \"type\": \"address\" }],\r\n    \"stateMutability\": \"view\",\r\n    \"type\": \"function\"\r\n  }\r\n]\r\n\r\n```\r\n\r\n### 11. 验证子图\r\n\r\n对你的 Graph Studio 进行身份验证，使你能够部署和管理你的子图\r\n\r\n```\r\ngraph auth --studio 34e2cd6682e3a4554310ec8fa19e9b92\r\n```\r\n\r\n输出结果为：\r\n\r\n```\r\nhb@yhbdeMacBook-Air MyNFTMarketProject % graph auth --studio 34e2cd6682e3a4554310ec8fa19e9b92\r\n ›   Warning: In next major version, this flag will be removed. By default we \r\n ›   will deploy to the Graph Studio. Learn more about Sunrise of Decentralized\r\n ›    Data \r\n ›   https://thegraph.com/blog/unveiling-updated-sunrise-decentralized-data/\r\nDeploy key set for https://api.studio.thegraph.com/deploy/\r\n```\r\n\r\n进入文件夹，build项目，生成代码和构建子图项目是部署前的必要步骤\r\n\r\n```\r\ncd yhbnftmarket\r\ngraph codegen \u0026\u0026 graph build\r\n```\r\n\r\n输出结果为：\r\n\r\n```\r\nhb@yhbdeMacBook-Air yhbnftmarket % graph codegen \u0026\u0026 graph build\r\n  Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2\r\n  Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3\r\n  Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4\r\n  Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5\r\n  Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6\r\n  Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2\r\n  Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4\r\n✔ Apply migrations\r\n✔ Load subgraph from subgraph.yaml\r\n  Load contract ABI from abis/NFTMarket.json\r\n✔ Load contract ABIs\r\n  Generate types for contract ABI: NFTMarket (abis/NFTMarket.json)\r\n  Write types to generated/NFTMarket/NFTMarket.ts\r\n✔ Generate types for contract ABIs\r\n✔ Generate types for data source templates\r\n✔ Load data source template ABIs\r\n✔ Generate types for data source template ABIs\r\n✔ Load GraphQL schema from schema.graphql\r\n  Write types to generated/schema.ts\r\n✔ Generate types for GraphQL schema\r\n\r\nTypes generated successfully\r\n\r\n  Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2\r\n  Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3\r\n  Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4\r\n  Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5\r\n  Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6\r\n  Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2\r\n  Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4\r\n✔ Apply migrations\r\n✔ Load subgraph from subgraph.yaml\r\n  Compile data source: NFTMarket =\u003e build/NFTMarket/NFTMarket.wasm\r\n✔ Compile subgraph\r\n  Copy schema file build/schema.graphql\r\n  Write subgraph file build/NFTMarket/abis/NFTMarket.json\r\n  Write subgraph manifest build/subgraph.yaml\r\n✔ Write compiled subgraph to build/\r\n\r\nBuild completed: build/subgraph.yaml\r\n```\r\n\r\n然后部署子图：\r\n\r\n```\r\ngraph deploy --studio nftmarket-subgraph\r\n```\r\n\r\n这里就会遇见一个常见的 IPFS 上传错误，通常是由于网络连接问题或 IPFS 服务临时不可用导致的。有时，IPFS 服务会出现临时问题。稍等几分钟后重试部署命令：\r\n\r\n```\r\ngraph deploy --studio yhbnftmarket\r\n```\r\n\r\n如果问题仍然存在，可以尝试使用本地 IPFS 节点。首先，安装 IPFS 并启动本地节点：\r\n\r\n#### 安装 IPFS\r\n\r\n根据你的操作系统，安装 IPFS：\r\n\r\n- macOS：\r\n\r\n```\r\nbrew install ipfs\r\n```\r\n\r\n- Ubuntu：\r\n\r\n```\r\nsudo apt-get install ipfs\r\n```\r\n\r\n#### 启动 IPFS 节点\r\n\r\n安装完成后，启动 IPFS 节点：\r\n\r\n```\r\nipfs init\r\nipfs daemon\r\n```\r\n\r\n#### 配置 The Graph 使用本地 IPFS 节点\r\n\r\n在项目根目录下的 `subgraph.yaml` 文件中，添加或修改以下部分以指向本地 IPFS 节点：\r\n\r\n```\r\nipfs:\r\n  host: localhost\r\n  port: 5001\r\n  protocol: http\r\n```\r\n\r\n文件内容如下：\r\n\r\n```\r\nspecVersion: 1.0.0\r\nindexerHints:\r\n  prune: auto\r\nipfs:\r\n  host: localhost\r\n  port: 5001\r\n  protocol: http\r\n\r\nschema:\r\n  file: ./schema.graphql\r\ndataSources:\r\n  - kind: ethereum\r\n    name: NFTMarket\r\n    network: sepolia\r\n    source:\r\n      address: \"0x23225a72386c4cbcd5cb08c2e36f4fae56b40e99\"\r\n      abi: NFTMarket\r\n      startBlock: 6344838\r\n    mapping:\r\n      kind: ethereum/events\r\n      apiVersion: 0.0.7\r\n      language: wasm/assemblyscript\r\n      entities:\r\n        - Cancel\r\n        - EIP712DomainChanged\r\n        - List\r\n        - OwnershipTransferred\r\n        - SetFeeTo\r\n        - SetWhiteListSigner\r\n        - Sold\r\n      abis:\r\n        - name: NFTMarket\r\n          file: ./abis/NFTMarket.json\r\n      eventHandlers:\r\n        - event: Cancel(bytes32)\r\n          handler: handleCancel\r\n        - event: EIP712DomainChanged()\r\n          handler: handleEIP712DomainChanged\r\n        - event: List(indexed address,indexed uint256,bytes32,address,address,uint256,uint256)\r\n          handler: handleList\r\n        - event: OwnershipTransferred(indexed address,indexed address)\r\n          handler: handleOwnershipTransferred\r\n        - event: SetFeeTo(address)\r\n          handler: handleSetFeeTo\r\n        - event: SetWhiteListSigner(address)\r\n          handler: handleSetWhiteListSigner\r\n        - event: Sold(bytes32,address,uint256)\r\n          handler: handleSold\r\n      file: ./src/nft-market.ts\r\n\r\n```\r\n\r\n然后，重新尝试部署：\r\n\r\n```\r\ngraph deploy --studio yhbnftmarket --ipfs http://localhost:5001\r\n```\r\n\r\n还有远程部署\r\n\r\n```\r\ngraph deploy --studio yhbnftmarket --ipfs https://ipfs.infura.io:5001\r\n```\r\n\r\n还可以用到alchemy提供的部署服务进行部署。\r\n\r\n```\r\n  graph deploy yhbnftmarket \\\r\n  --version-label v0.0.1-new-version \\\r\n  --node https://subgraphs.alchemy.com/api/subgraphs/deploy \\\r\n  --deploy-key n61nhCdDwtnQA \\\r\n  --ipfs https://ipfs.satsuma.xyz\r\n```\r\n\r\n输出结果为：\r\n\r\n```\r\nb@yhbdeMacBook-Air yhbnftmarket %   graph deploy yhbnftmarket \\\r\n  --version-label v0.0.1-new-version \\\r\n  --node https://subgraphs.alchemy.com/api/subgraphs/deploy \\\r\n  --deploy-key n61nhCdDwtnQA \\\r\n  --ipfs https://ipfs.satsuma.xyz\r\n  Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2\r\n  Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3\r\n  Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4\r\n  Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5\r\n  Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6\r\n  Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2\r\n  Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4\r\n✔ Apply migrations\r\n✔ Load subgraph from subgraph.yaml\r\n  Compile data source: NFTMarket =\u003e build/NFTMarket/NFTMarket.wasm\r\n✔ Compile subgraph\r\n  Copy schema file build/schema.graphql\r\n  Write subgraph file build/NFTMarket/abis/NFTMarket.json\r\n  Write subgraph manifest build/subgraph.yaml\r\n✔ Write compiled subgraph to build/\r\n  Add file to IPFS build/schema.graphql\r\n                .. QmSj2ZesiRS6NqdD74sTywyfzCi4S2DQTri9VZDU6dqete\r\n  Add file to IPFS build/NFTMarket/abis/NFTMarket.json\r\n                .. QmZySeDjEzSo2c58rxjbBFTZpJWV7BDbSS1c3TR1f5z7ve\r\n  Add file to IPFS build/NFTMarket/NFTMarket.wasm\r\n                .. QmQYpHo72685u3ZEKVXJq3Nm2aGw6UfvnKYx2eZqfC2PfF\r\n✔ Upload subgraph to IPFS\r\n\r\nBuild completed: QmUHomL5Um5WdDhr3pCLydhKqU8MnZYoPHRx7WbJZC8Hc8\r\n\r\nDeployed to https://subgraphs.alchemy.com/subgraphs/6887/versions/23328\r\n\r\nSubgraph endpoints:\r\nQueries (HTTP):     https://subgraph.satsuma-prod.com/hongbins-team--360746/yhbnftmarket/version/v0.0.1-new-version/api\r\n\r\n```\r\n\r\n到这里就是显示部署成功了。\r\n\r\n### 14. 查询数据\r\n\r\n子图部署完成后，你可以在 https://subgraphs.alchemy.com/dashboard来查询数据。\r\n\r\n\r\n![image-20240721074539664.png](https://img.learnblockchain.cn/attachments/2024/07/kjcI5SHF669c52c801aae.png)\r\n\r\n\r\n![image-20240721074701949.png](https://img.learnblockchain.cn/attachments/2024/07/9ujLyBqE669c52d149315.png)\r\n\r\n打开查询界面https://subgraph.satsuma-prod.com/hongbins-team--360746/yhbnftmarket/playground，例如，查询所有的上架记录：\r\n\r\n```\r\n{\r\n  lists {\r\n    id\r\n    nft\r\n    tokenId\r\n    orderId\r\n    seller\r\n    payToken\r\n    price\r\n    deadline\r\n    blockNumber\r\n    blockTimestamp\r\n    transactionHash\r\n  }\r\n}\r\n\r\n```\r\n\r\n\r\n![image-20240721075004140.png](https://img.learnblockchain.cn/attachments/2024/07/cNDZSKVZ669c52dd5f00a.png)\r\n\r\n查询所有的成交记录：\r\n\r\n```\r\n{\r\n  solds {\r\n    id\r\n    orderId\r\n    buyer\r\n    fee\r\n    blockNumber\r\n    blockTimestamp\r\n    transactionHash\r\n  }\r\n}\r\n```\r\n\r\n\r\n![image-20240721075027929.png](https://img.learnblockchain.cn/attachments/2024/07/RQd6k8EN669c52e601e85.png)\r\n\r\n### 15.参考链接\r\n\r\n- The Graph Documentation\r\n- GraphQL Documentation\r\n\r\n通过以上步骤，你可以成功部署并使用 TheGraph 来索引和查询你的 NFTMarket 合约的上架 List 和成交 Sold 记录。","title":"使用 TheGraph 来索引和查询你的 NFTMarket 合约的上架 List 和成交 Sold 记录"},"history":null,"timestamp":1721521044,"version":1}