在数字化浪潮中,非同质化代币(NFT)已成为数字资产领域的重要组成部分。NFT 交易市场作为用户买卖独特数字资产的平台,支持数字艺术品、收藏品、游戏道具等多种形态。本文将引导你通过 Hardhat 框架,在 Polygon Mumbai 测试网络上创建并部署一个 NFT 交易市场智能合约,并利用 Ethers.js 进行测试与交互。
准备工作
在开始之前,请确保你的开发环境已满足以下要求:
- 已安装 Node.js 和 NPM
- 配备文本编辑器或 IDE(如 VSCode)
- 准备好支持多账户私钥访问的 Web3 钱包(例如 MetaMask)
- 获取 Mumbai 测试网络的 MATIC 代币(可通过测试网水龙头获得)
- 已安装 Hardhat 和 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 交易市场的核心,需具备以下功能:
- 存储 NFT 列表详情(包括代币 ID、合约地址、类型、价格及卖家地址)
- 支持用户上架 NFT(通过
createListing函数) - 支持用户购买已上架的 NFT(通过
buyNFT函数) - 作为中介促进买卖双方之间的 NFT 转移
- 提供查看已上架和已购买 NFT 的功能(通过
getMyListedNFTs和getMarketItem函数)
合约代码结构
在 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_NAME 和 YOUR_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 交易市场的全流程。从智能合约编写、测试到部署与交互,每一步都是构建去中心化应用的重要基石。尝试在此基础上添加佣金机制、多链支持或高级交易功能,进一步拓展平台潜力。期待看到你的创新成果!