如何在 Polygon 区块链上创建 NFT 交易市场

·

在数字化浪潮中,非同质化代币(NFT)已成为数字资产领域的重要组成部分。NFT 交易市场作为用户买卖独特数字资产的平台,支持数字艺术品、收藏品、游戏道具等多种形态。本文将引导你通过 Hardhat 框架,在 Polygon Mumbai 测试网络上创建并部署一个 NFT 交易市场智能合约,并利用 Ethers.js 进行测试与交互。

准备工作

在开始之前,请确保你的开发环境已满足以下要求:

项目初始化

首先,通过以下命令设置 Hardhat 环境:

mkdir marketplace-hardhat
cd marketplace-hardhat
npm install --save-dev hardhat
npx hardhat

选择默认配置创建 JavaScript 项目,并安装必要的依赖库:

npm install @openzeppelin/contracts dotenv [email protected]
npm install --save-dev @nomiclabs/hardhat-etherscan

配置区块链节点

为部署和交互智能合约,你需要连接至 Polygon Mumbai 测试网络的全节点。虽然可以自行搭建节点,但使用专业服务能获得更优的网络性能和稳定性。👉 获取高速区块链节点服务

创建账户后,选择 Polygon 链及 Mumbai 测试网络,保存提供的 HTTP Provider URL 以备后续使用。

同时,通过测试网水龙头获取 MATIC 测试代币,用于支付交易费用。

创建 NFT 交易市场智能合约

智能合约是 NFT 交易市场的核心,需具备以下功能:

合约代码结构

contracts 目录下创建 marketplace.sol 文件,并写入以下代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract Marketplace is ReentrancyGuard, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private marketplaceIds;
    Counters.Counter private totalMarketplaceItemsSold;

    mapping(uint => Listing) private marketplaceIdToListingItem;

    struct Listing {
        uint marketplaceId;
        address nftAddress;
        uint tokenId;
        address payable seller;
        address payable owner;
        uint listPrice;
    }

    event ListingCreated(
        uint indexed marketplaceId,
        address indexed nftAddress,
        uint indexed tokenId,
        address seller,
        address owner,
        uint listPrice
    );

    function createListing(
        uint tokenId,
        address nftAddress,
        uint price
    ) public nonReentrant {
        require(price > 0, "List price must be 1 wei >=");
        marketplaceIds.increment();
        uint marketplaceItemId = marketplaceIds.current();
        marketplaceIdToListingItem[marketplaceItemId] = Listing(
            marketplaceItemId,
            nftAddress,
            tokenId,
            payable(msg.sender),
            payable(address(0)),
            price
        );
        IERC721(nftAddress).transferFrom(msg.sender, address(this), tokenId);
        emit ListingCreated(
            marketplaceItemId,
            nftAddress,
            tokenId,
            msg.sender,
            address(0),
            price
        );
    }

    function buyListing(uint marketplaceItemId, address nftAddress)
        public
        payable
        nonReentrant
    {
        uint price = marketplaceIdToListingItem[marketplaceItemId].listPrice;
        require(
            msg.value == price,
            "Value sent does not meet list price for NFT"
        );
        uint tokenId = marketplaceIdToListingItem[marketplaceItemId].tokenId;
        marketplaceIdToListingItem[marketplaceItemId].seller.transfer(msg.value);
        IERC721(nftAddress).transferFrom(address(this), msg.sender, tokenId);
        marketplaceIdToListingItem[marketplaceItemId].owner = payable(msg.sender);
        totalMarketplaceItemsSold.increment();
    }

    function getMarketItem(uint marketplaceItemId)
        public
        view
        returns (Listing memory)
    {
        return marketplaceIdToListingItem[marketplaceItemId];
    }

    function getMyListedNFTs() public view returns (Listing[] memory) {
        uint totalListingCount = marketplaceIds.current();
        uint listingCount = 0;
        uint index = 0;

        for(uint i = 0; i < totalListingCount; i++) {
            if(marketplaceIdToListingItem[i + 1].owner == msg.sender) {
                listingCount += 1;
            }
        }
        Listing[] memory items = new Listing[](listingCount);
        for(uint i = 0; i < totalListingCount; i++) {
            if(marketplaceIdToListingItem[i + 1].owner == msg.sender) {
                uint currentId = marketplaceIdToListingItem[i + 1].marketplaceId;
                Listing memory currentItem = marketplaceIdToListingItem[currentId];
                items[index] = currentItem;
                index += 1;
            }
        }
        return items;
    }
}

创建测试用 NFT 合约

contracts 目录下创建 NFT.sol 文件,写入以下 ERC-721 标准合约代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract NFT is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    constructor() ERC721("YOUR_NFTS_NAME", "YOUR_NFTS_SYMBOL") {}

    function safeMint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }
}

请将 YOUR_NFTS_NAMEYOUR_NFTS_SYMBOL 替换为你自定义的 NFT 名称和符号。

编译与测试

运行以下命令编译合约:

npx hardhat compile

编译成功后,在 test 目录下创建 marketplace-test.js 文件,编写测试用例验证合约功能,例如 NFT 上架、购买及资金验证等场景。运行测试:

npx hardhat test test/marketplace-test.js

确保所有测试用例通过后再进行部署。

部署至 Mumbai 测试网络

创建 .env 文件配置环境变量,包括私钥、Polygonscan API 密钥及 RPC 节点 URL。随后更新 hardhat.config.js 文件,配置网络参数和验证设置。

使用部署脚本 deploy.js 执行合约部署:

npx hardhat run --network mumbai scripts/deploy.js

部署成功后,记录合约地址并前往 Polygonscan 验证交易详情。

与合约交互

通过 Hardhat 脚本与已部署的合约进行交互,包括铸造 NFT、批准市场合约、上架 NFT 及模拟购买流程。编写 interact.js 脚本并执行:

npx hardhat run scripts/interact.js --network mumbai

观察交易哈希和执行结果,确保各项功能正常运作。

合约验证与公开查询

使用 Hardhat 验证命令将合约源代码公开至 Polygonscan:

npx hardhat verify --network mumbai <合约地址>

验证成功后,即可在区块浏览器中查询合约状态和交易记录,确认 NFT 所有权转移等操作。

常见问题

问:为什么需要两个账户进行测试?
答:双账户测试能模拟真实市场中的买卖双方行为,确保合约在转账、权限控制等环节的正确性。

问:如何处理合约升级或功能扩展?
答:可通过代理合约模式或模块化设计实现升级,但需注意状态迁移和兼容性问题。👉 学习智能合约进阶方法

问:如何优化交易中的 Gas 费用?
答:合理设置 Gas 价格上限、优化数据结构及减少冗余计算均可有效降低成本,尤其在网络拥堵时更为重要。

问:是否支持 ERC-1155 标准的多类别 NFT?
答:当前合约针对 ERC-721 设计,但可通过扩展映射结构和修改交易逻辑来支持多标准代币。

问:如何添加平台手续费或 royalties 机制?
答:可在 buyListing 函数中引入手续费计算逻辑,并将部分资金转入指定地址,实现收益分成。

总结

通过本指南,你已掌握了在 Polygon 网络上构建 NFT 交易市场的全流程。从智能合约编写、测试到部署与交互,每一步都是构建去中心化应用的重要基石。尝试在此基础上添加佣金机制、多链支持或高级交易功能,进一步拓展平台潜力。期待看到你的创新成果!