第2节:ERC721(非同质化Token)
对于Opeasea而言,测试网目前仅支持Rinkeby,即部署在Rinkeby网络上的nft合约会自动展示在opensea中。
- 举例教程:https://mp.weixin.qq.com/s/T9GEgaqubHAftpMsdLAMIg
- ipfs服务:https://app.pinata.cloud/pinmanager
- EIP-721: https://eips.ethereum.org/EIPS/eip-721
标准接口
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
interface IERC721 is IERC165 {
// 3 EVENTS
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);
// 9 REQUIRED FUNCTIONS
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool _approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
// 3 OPTIONAL FUNCTIONS
function name() external view returns (string _name);
function symbol() external view returns (string _symbol);
function tokenURI(uint256 _tokenId) external view returns (string);
}
部署
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
/*
1. 一个ERC721合约也是一个集合,可以有N多个token,但是每一个都是不一样的,有唯一的id
2. 每个tokenid可以关联一个URI,一般是.json文件,里面有三个字段,进而变成一个独一无二的展示,所以是NFT,三个字段为:
- description:nft描述
- url:nft图片存储在ipfs上的哈希值
- name:nft名字
*/
contract SimpleCollectible is ERC721URIStorage {
uint256 public tokenCounter;
constructor () public ERC721 ("Dogie", "DOG"){
tokenCounter = 0;
}
function createCollectible(string memory tokenURI) public returns (uint256) {
uint256 newItemId = tokenCounter;
_safeMint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
tokenCounter = tokenCounter + 1;
return newItemId;
}
function _baseURI() internal view override returns (string memory) {
return "https://gateway.pinata.cloud/ipfs/";
}
//1. createCollectible(QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta)
//2. tokenURI(0)
//3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
}
接受者为合约时
to如果是合约地址,则to合约必须实现onERC721Received接口,因为有回调校验,如果to是EOA则不需要实现该接口。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
contract ERC721Holder is IERC721Receiver {
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}
完整代码
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
// 里面继承了IERC721Receiver.sol接口
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
/*
1. 一个ERC721合约也是一个集合,可以有N多个token,但是每一个都是不一样的,有唯一的id
2. 每个tokenid可以关联一个图片(URI),进而变成一个独一无二的展示,所以是NFT
*/
contract SimpleCollectible is ERC721URIStorage {
uint256 public tokenCounter;
constructor () public ERC721 ("Dogie", "DOG"){
}
function createCollectible(string memory tokenURI) public returns (uint256) {
uint256 newItemId = tokenCounter;
_safeMint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
tokenCounter = tokenCounter + 1;
return newItemId;
}
// 注释掉
// function _baseURI() internal view override returns (string memory) {
// return "https://gateway.pinata.cloud/ipfs/";
// }
}
contract ERC721Holder is IERC721Receiver {
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}
测试
// QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB是json文件的hash
//1. createCollectible(QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB)
//2. tokenURI(0)
//3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB
其中的hash值为这个nft图片的描述文件的哈希:metadata.json,
- json文件上传后的哈希为:QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB,这个哈希用于作为tokenuri,在合约内部会和baseUrl组装起来。
- 图片的哈希为:QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
{
"description": "this is a nft1155 metadata json desc",
"image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
"name": "duke nft"
}
总结
- 在页面展示时,url为:https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
- 实际上的tokenuri为:https://gateway.pinata.cloud/ipfs/QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB
授权比较
- ERC721
// 对单个id进行approve
function approve(address to, uint256 tokenId) public virtual override;
// 对某个地址授权所有的token
function setApprovalForAll(address operator, bool approved) public virtual override;
- ERC1155
// 每个对单个id的授权!!
// what -》不支持!
// 对某个地址授权所有的token
function setApprovalForAll(address operator, bool approved) external;
ERC721A
ERC721A是一套实现了IERC721接口的合约,它的优势是在批量mint时,可以节约gas,知名NFT项目Azuki就是由这个合约开发的
优化原理:点击查看