开发工具:Hardhat 或 Truffle(编译、部署合约),OpenZeppelin(安全合约模板)
代码实现
1. MUSD 代币合约
基于 ERC-20 标准,使用 OpenZeppelin 模板。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MUSD is ERC20 {
constructor() ERC20("Mall USD", "MUSD") {
_mint(msg.sender, 1000000 * 10**18); // 初始发行 100万 MUSD
}
// 商城合约可以调用此函数铸造更多 MUSD 作为奖励
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
2. NFT 商品合约
基于 ERC-721,每个商品是一个独特的 NFT。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract ProductNFT is ERC721 {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
mapping(uint256 => string) private _tokenURIs;
constructor() ERC721("ProductNFT", "PNFT") {}
function mintNFT(address to, string memory tokenURI) public returns (uint256) {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(to, newItemId);
_setTokenURI(newItemId, tokenURI);
return newItemId;
}
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal {
_tokenURIs[tokenId] = _tokenURI;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
return _tokenURIs[tokenId];
}
}
3. 商城合约
核心逻辑,处理上架和购买。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./ProductNFT.sol";
import "./MUSD.sol";
contract Mall {
ProductNFT public productNFT;
IERC20 public usdc; // USDC 合约地址
MUSD public musd;
struct Item {
uint256 tokenId;
address seller;
uint256 price; // 以 USDC 为单位
bool isSold;
}
mapping(uint256 => Item) public items;
uint256 public itemCount;
event ItemListed(uint256 tokenId, address seller, uint256 price);
event ItemSold(uint256 tokenId, address buyer, uint256 price);
constructor(address _productNFT, address _usdc, address _musd) {
productNFT = ProductNFT(_productNFT);
usdc = IERC20(_usdc);
musd = MUSD(_musd);
}
// 商家上架商品
function listItem(string memory tokenURI, uint256 price) external {
uint256 tokenId = productNFT.mintNFT(address(this), tokenURI);
itemCount++;
items[tokenId] = Item(tokenId, msg.sender, price, false);
emit ItemListed(tokenId, msg.sender, price);
}
// 用户用 USDC 购买商品
function buyItem(uint256 tokenId) external {
Item storage item = items[tokenId];
require(!item.isSold, "Item already sold");
require(usdc.transferFrom(msg.sender, item.seller, item.price), "USDC transfer failed");
item.isSold = true;
productNFT.transferFrom(address(this), msg.sender, tokenId);
// 奖励买家 1 MUSD
musd.mint(msg.sender, 1 * 10**18);
emit ItemSold(tokenId, msg.sender, item.price);
}
}
4. 前端实现(React + Ethers.js)
以下是简化的前端代码,假设已配置好 MetaMask 和合约地址。
import { ethers } from "ethers";
import React, { useState } from "react";
import MallABI from "./abis/Mall.json"; // 商城合约 ABI
import USDCABI from "./abis/USDC.json"; // USDC ABI
const MallDApp = () => {
const [provider, setProvider] = useState(null);
const [account, setAccount] = useState(null);
const mallAddress = "0xYourMallContractAddress";
const usdcAddress = "0xYourUSDCContractAddress"; // 测试网 USDC 地址
// 连接钱包
const connectWallet = async () => {
const provider = new ethers.BrowserProvider(window.ethereum);
const accounts = await provider.send("eth_requestAccounts", []);
setProvider(provider);
setAccount(accounts[0]);
};
// 商家上架商品
const listItem = async () => {
const signer = await provider.getSigner();
const mallContract = new ethers.Contract(mallAddress, MallABI, signer);
const tokenURI = "ipfs://your-ipfs-hash"; // 商品元数据
const price = ethers.parseUnits("10", 6); // 假设 10 USDC,6 位小数
await mallContract.listItem(tokenURI, price);
};
// 用户购买商品
const buyItem = async (tokenId) => {
const signer = await provider.getSigner();
const mallContract = new ethers.Contract(mallAddress, MallABI, signer);
const usdcContract = new ethers.Contract(usdcAddress, USDCABI, signer);
// 授权 USDC
const price = items[tokenId].price; // 从状态或合约获取价格
await usdcContract.approve(mallAddress, price);
await mallContract.buyItem(tokenId);
};
return (
<div>
<button onClick={connectWallet}>连接钱包</button>
<button onClick={listItem}>上架商品</button>
<button onClick={() => buyItem(1)}>购买商品 (ID: 1)</button>
</div>
);
};
export default MallDApp;
部署与测试
- 编译与部署:
- 用 Hardhat 编译合约:npx hardhat compile。
- 部署到测试网:编辑 hardhat.config.js,添加网络配置,然后运行 npx hardhat run scripts/deploy.js --network sepolia。
- 测试流程:
- 用 MetaMask 连接测试网,获取测试 USDC(可用 faucet)。
- 商家调用 listItem,检查 NFT 是否铸造。
- 用户授权 USDC 并调用 buyItem,验证 NFT 和 USDC 转移。