ERC721 是一个以太坊智能合约标准,用来定义每个代币都是唯一的、不可互换的资产。这和常见的代币标准 ERC20(同质化代币) 相对,ERC20的每个 token 是可互换、可拆分的,类似法币.
ERC721核心特性
ERC721 是一个 NFT 标准,必须实现以下接口:
功能 | 函数 | 说明 |
---|---|---|
查询余额 | balanceOf(address owner) | 获取某个地址拥有的 NFT 数量 |
查询拥有者 | ownerOf(uint256 tokenId) | 获取某个 token 的拥有者 |
授权转移 | approve(address to, uint256 tokenId) | 授权他人管理自己的某个 NFT |
查看授权 | getApproved(uint256 tokenId) | 查看某个 token 的授权地址 |
批量授权 | setApprovalForAll(address operator, bool approved) | 批量授权管理所有 NFT |
检查批量授权 | isApprovedForAll(address owner, address operator) | 检查是否授权了某人管理所有 NFT |
转移 NFT | transferFrom(address from, address to, uint256 tokenId) | 转账(不校验接收者) |
安全转账 | safeTransferFrom(…) | 转账 + 检查接收者是否能接收 ERC721 |
事件支持
标准中规定必须发出以下事件:
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
元数据扩展接口
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function tokenURI(uint256 tokenId) external view returns (string memory);
安全接收检查(必需)
function onERC721Received(...) external returns (bytes4);
防止 NFT 转账到不会处理的合约地址,避免被锁死。
在https://docs.openzeppelin.com/contracts/5.x/wizard上新建一个ERC721合约,我们通过编写单元测试脚本来测试NFT标准接口.
describe("test MyNFT contract", function () {
let myNftContract: any;
let deployer: any;
let user: any;
let user2: any;
beforeEach(async function () {
await deployments.fixture(["MyNFT"]);
const { firstAccount,secondAccount,thirdAccount } = await getNamedAccounts();
deployer = firstAccount;
user = secondAccount;
user2 = thirdAccount;
const deployment = await deployments.get("MyNFT");
myNftContract = await ethers.getContractAt("MyNFT", deployment.address);
})
})
查询余额 balanceOf(address owner)
// 查询 某个账户 balanceOf
it("should get the correct balanceOf", async function () {
const uri = "ipfs://QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JY";
const tx = await myNftContract.safeMint(deployer, uri);
await tx.wait();
const uri1 = "ipfs://QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JY";
const tx1 = await myNftContract.safeMint(deployer, uri1);
await tx1.wait();
const balance = await myNftContract.balanceOf(deployer);
expect(balance).to.equal(2);
})
查询拥有者ownerOf(uint256 tokenId)
it("should get the correct ownerOf", async function () {
const uri = "ipfs://QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZ";
const tx = await myNftContract.safeMint(user, uri);
await tx.wait();
const owner = await myNftContract.ownerOf(0);
await expect(owner).to.equal(user);
})
授权转移 approve(address to, uint256 tokenId)
// approve
it("should get the correct approve", async function () {
const uri = "ipfs://QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZ";
await myNftContract.safeMint(deployer, uri);
await myNftContract.approve(user, 0);
const approved = await myNftContract.getApproved(0);
await expect(approved).to.equal(user);
// test user 转移nft
const etherUser = await ethers.getSigner(user);
const tx3 = await myNftContract.connect(etherUser).transferFrom(deployer, user2, 0);
await tx3.wait();
const owner = await myNftContract.ownerOf(0);
await expect(owner).to.equal(user2);
})
setApprovalForAll
批量授权管理所有 NFT
// setApprovalForAll
it('should setApprovalForAll', async function () {
const uri = "ipfs://QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4J";
// 铸造3个NFT
await myNftContract.safeMint(deployer, uri);
await myNftContract.safeMint(deployer, uri);
await myNftContract.safeMint(deployer, uri);
// 授权 user 管理他所有的NFT
const tx = await myNftContract.setApprovalForAll(user, true);
await tx.wait();
// 验证授权关系
const approved = await myNftContract.isApprovedForAll(deployer, user);
await expect(approved).to.be.true;
// user 转移 tokenId 0 给 user2
const etherUser = await ethers.getSigner(user);
const tx2 = await myNftContract.connect(etherUser).transferFrom(deployer, user2, 0);
await tx2.wait();
// user 转移tokenId 1 给 user2
const tx3 = await myNftContract.connect(etherUser).transferFrom(deployer, user2, 1);
await tx3.wait();
// 验证转移结果
const owner0 = await myNftContract.ownerOf(0);
const owner1 = await myNftContract.ownerOf(1);
const owner2 = await myNftContract.ownerOf(2);
await expect(owner0).to.equal(user2);
await expect(owner1).to.equal(user2);
await expect(owner2).to.equal(deployer);
})
发表评论