NFT 这么火,你还不懂 ERC721 吗?

什么是非同质化代币(NFT)?

在了解 ERC721 规范之前,我们需要弄懂 同质化代币非同质化代币的含义。

  • 同质化代币(FT):可以简单类比游戏中的金币 or 点券,我拥有的 100点券和你拥有的 100 点券 本质上并没有什么区别,都可以购买皮肤。
  • 非同质化代币(NFT):唯一标识具体某一个人或者物品,例如:我拥有齐白石的《群虾图》真品,全球仅此一份。

什么是 ERC721 规范?

ERC721 规范是为了实现 非同质化代币(NFT)而创建的标准;换言之,实现了 ERC721 规范的代币,每一份都是独一无二的存在。

1. 合约结构

我们已经了解了 ERC721 规范的意义,那么我们就来看看它的结构~

ERC721合约规范

如上图所示,我们可以清晰的看到 ERC721 的继承关系;从上往下来分析一下各个合约接口/抽象合约的功能:

  • IERC165:要求合约提供其实现了哪些接口;在与合约进行交互的时候可以先调用此接口进行查询,了解合约具体实现了哪些接口。
  • ERC165:抽象合约,官方对 IERC165 提供的默认实现。
  • IERC721:发行 NFT 的标准合约规范;定义了 NFT 合约的各类行为接口,诸如:转移、授权…。
  • ERC721:官方对 IERC721 提供的默认实现。
  • IERC721Metadata:定义合约的元数据信息;诸如:合约名字、标志、以及每个代币的 tokenURI。
  • Context:抽象合约,上下文;主要对 msg对象做一些封装;例如: msg.Sender代表当前调用合约的用户。
  • IERC721Enumerable:提高合约的可访问性(非必须实现,但是一般我们的合约都会实现以提高可访问性);主要提供:当前发行 NFT 总量、通过索引获取用户所拥有的TokenID…
  • ERC721Enumerable: 抽象合约,官方对 IERC721Enumerable 提供的默认实现。
  • Ownable:主要提供合约 owner 的校验以及 owner 的转移;例如合约中的有些函数必须得有owner 进行调用。
  • IERC721Receiver:当进行 NFT 转移的时候,如果接收的地址是一个 合约地址的话,那么接收的合约必须实现该接口。
  • Address:Addresssolidity 特有的一种类型,项目中的 libarary Address 主要是对该类型做了一些简易工具的封装。
  • Strings:同上,工具类的存在,提供将 uint256 转换成 string 的能力。

接下来我们就来看看合约的源码~

1.1 IERC165 接口合约

IERC165 是一个合约标准,这个标准要求合约提供其实现了哪些接口,这样在与合约进行交互的时候可以先调用此接口进行查询。

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
   
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     * 判断合约是否实现 接口ID 对应接口
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

1.2 ERC165 抽象合约

ERC165 是官方为 IERC165 提供的默认实现。

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
   
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
   
        return interfaceId == type(IERC165).interfaceId;
    }
}

1.3 IERC721 接口合约

pragma solidity ^0.8.0;
import "./utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
   
        
    // ============================事件==========================================
    // 代币转移事件,当发生代币转移时触发
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    // 代币授权事件,当 owner 对代币授权于 approved 时触发
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    // 代币全量授权事件
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    
    // ============================函数==========================================
    // 查看owner账号余额(拥有的代币数)
    function balanceOf(address owner) external view returns (uint256 balance);
    // 获取 tokenID 对应的代币的 owner 地址
    function ownerOf(uint256 tokenId) external view returns (address owner);
    // 安全转换方法
    function safeTransferFrom(address from, address to, uint256 tokenId) external;
    // 转移方法(开发中使用上者更多,more safe..)
    function transferFrom(address from, address to, uint256 tokenId) external;
    // 授权,owner调用该函数,将tokenID对应的代币授予 to 账号行使权
    function approve(address to, uint256 tokenId) external;
    // 获取tokenID代币对应的被授权账号地址
    function getApproved(uint256 tokenId) external view returns (address operator);
    // 全量授权,owner调用即将自己所有的代币行使权授予 operator 账号
    function setApprovalForAll(address operator, bool _approved) external;
    // 判断owner是否对账号 operator 进行全量授权
    function isApprovedForAll(address owner, address operator) external view returns (bool);
    // 安全转移,携带回调的数据
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
}

1.4 ERC721 实现合约

官方对 IERC721 接口合约提供的默认实现,我们编写的 NFT 合约一般继承于该合约;在其基础上编写自己的合约逻辑。官方实现主要提供了如下能力:

  • 基础的查询能力,诸如代币的owner、合约的名字、标志…
  • 代币的授权/回收、转移
  • 代币的生成/销毁(注意:销毁函数无检查所属权,需要我们自行检查)
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.0;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
   
    // Address和Strings可理解为工具类,这里是为了安全 or 操作便捷考虑。
    using Address for address;
    using Strings for uint256;

    // 合约名字 for IERC721Metadata 中的定义
    string private _name;
    // 合约标志 for IERC721Metadata 中的定义 
    string private _symbol;

    // map(tokenID, address),可理解为map结构,key为tokenID,value为该tokenID对应的owner地址
    mapping(uint256 => address) private _owners;
    // map(address, uint256), key为用户地址,value为该地址拥有的token数量
    mapping(address => uint256) private _balances;
    // map(uint256, address),key为代币ID,value为代币所授权地址(一个token只能授予一个账号行使权)
    mapping(uint256 => address) private _tokenApprovals;
    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    /**
     * @dev 构造函数,初始化合约的 name 和 symbol
     */
    constructor(string memory name_, string memory symbol_) {
   
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     * 判断合约是否实现了 interfaceID 接口
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
   
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId 
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值