第2节:ERC721(非同质化Token)

对于Opeasea而言,测试网目前仅支持Rinkeby,即部署在Rinkeby网络上的nft合约会自动展示在opensea中。

  1. 举例教程:https://mp.weixin.qq.com/s/T9GEgaqubHAftpMsdLAMIg
  2. ipfs服务:https://app.pinata.cloud/pinmanager
  3. 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,

  1. json文件上传后的哈希为:QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB,这个哈希用于作为tokenuri,在合约内部会和baseUrl组装起来。
  2. 图片的哈希为:QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
{
  "description": "this is a nft1155 metadata json desc",
  "image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
  "name": "duke nft"
}

总结

  1. 在页面展示时,url为:https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
  2. 实际上的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就是由这个合约开发的

优化原理:点击查看