ERC721标准合约接口事件和方法分析

Posted FeelTouch Labs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ERC721标准合约接口事件和方法分析相关的知识,希望对你有一定的参考价值。

目录

ERC721简介

ERC721标准

IERC721接口定义

IERC721接口函数分析

safeTransferFrom与transferFrom的核心异同点

必需实现ERC165标准

可选实现标准

ERC721Metadata

ERC721Enumerable

NTF TokenId生成

ERC721实现案例

参考


ERC721简介

ERC721 是由Dieter Shirley 在2017年9月提出。Dieter Shirley 正是谜恋猫CryptoKitties背后的公司Axiom Zen的技术总监。因此谜恋猫也是第一个实现了ERC721 标准的去中心化应用。ERC721号提议已经被以太坊作为标准接受,但该标准仍处于草稿阶段。本文介绍的ERC721标准基于最新(2018/03/23官方提议。

ERC721是一个代币标准,ERC721官方定义为Non-Fungible Tokens,简写为NFTs,即非同质代币。

其显示意义在于非同质性其实广泛存在于我们的生活中,如图书馆的每一本,宠物商店的每一只宠物,歌手所演唱的歌曲,花店里不同的花等等,因此ERC721合约必定有广泛的应用场景。通过这样一个标准,也可建立跨功能的NFTs管理和销售平台(就像有支持ERC20的交易所和钱包一样),使生态更加强大。

ERC721标准

IERC721接口定义

ERC721做为一个合约标准,提供了在实现ERC721代币时必须要遵守的协议,要求每个ERC721标准合约需要实现ERC721及ERC165接口,接口定义如下:

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either approve or setApprovalForAll.
     * - If `to` refers to a smart contract, it must implement IERC721Receiver-onERC721Received, which is called upon a safe transfer.
     *
     * Emits a Transfer event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either approve or setApprovalForAll.
     * - If `to` refers to a smart contract, it must implement IERC721Receiver-onERC721Received, which is called upon a safe transfer.
     *
     * Emits a Transfer event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use safeTransferFrom whenever possible.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either approve or setApprovalForAll.
     *
     * Emits a Transfer event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an Approval event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call transferFrom or safeTransferFrom for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an ApprovalForAll event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See setApprovalForAll
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);

IERC721接口函数分析

1. Event事件

event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) 当任何NFT的所有权更改时(不管哪种方式,最终都会调用该事件),就会触发此事件。

event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 当更改或确认NFT的授权地址时触发。approved参数表示被授权可以转移NFT的地址。approved为零地址表示取消授权的地址;发生 Transfer 事件时,该NFT的授权地址(如果有)被重置为“无”(零地址)。需要指出的是Approval需要花费Gas。

event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 所有者启用或禁用操作员时触发。即指定或取消可管理整个NFTs的operator操作员。(操作员可管理所有者所持有的NFTs)

function balanceOf(address owner) external view returns (uint256 balance); 统计owner 地址所持有的NFTs数量

function ownerOf(uint256 tokenId) external view returns (address owner); 返回指定NFT的所有者

function safeTransferFrom(address from,address to,uint256 tokenId,bytes calldata data) external; 将NFT的所有权从一个地址转移到另一个地址,data : 附加额外的参数(没有指定格式),传递给接收者。

function safeTransferFrom(address from,address to,uint256 tokenId) external; 将NFT的所有权从一个地址转移到另一个地址,功能同上,不带data参数。

function transferFrom(address from,address to,uint256 tokenId) external; 转移所有权 – 调用者负责确认_to是否有能力接收NFTs,否则可能永久丢失。一般不建议使用该方法转移NFT

function approve(address to, uint256 tokenId) external; 更改或确认NFT的授权地址

function setApprovalForAll(address operator, bool _approved) external; 启用或禁用第三方(操作员)管理 msg.sender 所有资产,触发 ApprovalForAll 事件

function getApproved(uint256 tokenId) external view returns (address operator); 获取单个NFT的授权地址,也反映出一个tokenId只能同时授权给一个operator。

function isApprovedForAll(address owner, address operator) external view returns (bool); 查询一个地址是否是另一个地址的授权操作员

safeTransferFrom与transferFrom的核心异同点

safeTransferFrom函数的实现需要做一下几种检查:

  1. 调用者msg.sender应该是当前tokenId的所有者或被授权的地址
  2. _from 必须是 _tokenId的所有者
  3. _tokenId 应该是当前合约正在的NFTs 中的任何一个
  4. _to 地址不应该为 0
  5. 如果_to 是一个合约应该调用其onERC721Received方法, 并且检查其返回值,如果返回值不为bytes4(keccak256("onERC721Received(address,uint256,bytes)"))抛出异常。
    一个可接收NFT的合约必须实现ERC721TokenReceiver接口:
interface ERC721TokenReceiver 
        /// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
        function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4);
    

transferFrom需调用者自己确认_to地址能正常接收NFT,否则将丢失此NFT。此函数实现时需要检查上面条件的前4条。

必需实现ERC165标准

ERC721标准同时要求必须符合ERC165标准 ,其接口如下:

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.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);

这个标准要求合约提供其实现了哪些接口,这样再与合约进行交互的时候可以先调用此接口进行查询。
interfaceID为函数选择器,计算方式有两种,如:bytes4(keccak256('supportsInterface(bytes4)'));ERC165.supportsInterface.selector,多个函数的接口ID为函数选择器的异或值。

可选实现标准

ERC721Metadata

ERC721Metadata 接口用于提供合约的元数据:name , symbol 及 URI(NFT所对应的资源)。
其接口定义如下:

interface ERC721Metadata /* is ERC721 */ 
    function name() external pure returns (string _name);
    function symbol() external pure returns (string _symbol);
    function tokenURI(uint256 _tokenId) external view returns (string);

接口说明:

  • name(): 返回合约名字,尽管是可选,但强烈建议实现,即便是返回空字符串。
  • symbol(): 返回合约代币符号,尽管是可选,但强烈建议实现,即便是返回空字符串。
  • tokenURI(): 返回_tokenId所对应的外部资源文件的URI(通常是IPFS或HTTP(S)路径)。外部资源文件需要包含名字、描述、图片,其格式的要求如下:

    "title": "Asset Metadata",
    "type": "object",
    "properties": 
        "name": 
            "type": "string",
            "description": "Identifies the asset to which this NFT represents",
        ,
        "description": 
            "type": "string",
            "description": "Describes the asset to which this NFT represents",
        ,
        "image": 
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive.",
        
    

tokenURI通常是被web3调用,以便在应用层做相应的查询和展示。

ERC721Enumerable

ERC721Enumerable的主要目的是提高合约中NTF的可访问性,其接口定义如下:

interface ERC721Enumerable /* is ERC721 */ 
    function totalSupply() external view returns (uint256);
    function tokenByIndex(uint256 _index) external view returns (uint256);
    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);

接口说明:

  • totalSupply(): 返回NFT总量
  • tokenByIndex(): 通过索引返回对应的tokenId。
  • tokenOfOwnerByIndex(): 所有者可以一次拥有多个的NFT, 此函数返回_owner拥有的NFT列表中对应索引的tokenId。

NTF TokenId生成

tokenId,在合约中用唯一的uint265进行标识,每个NFT的ID在智能合约的生命周期内不允许改变。推荐的实现方式有:

  1. 从0开始,每新加一个NFT,NTF ID加1
  2. 使用sha3后uuid 转换为 NTF ID

ERC721实现案例

openzeppelin智能合约库来写一个ERC721合约,来实现NFT的铸造、销毁和URL设定等功能。

// SPDX-License-Identifier: MIT;

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";


contract TestNFT is ERC721,ERC721Enumerable, ERC721URIStorage 

    using Counters for Counters.Counter;
    Counters.Counter private _tokenId;

    constructor() ERC721("The First NFT","FFT") 

    function mint(address _recipient, string memory _tokenUrl) public returns(uint _mintTokenId)
        require(bytes(_tokenUrl).length > 0,"The _tokenUrl must be have");
        _tokenId.increment();
        uint newTokenId = _tokenId.current();
        _mint(_recipient, newTokenId);
        _setTokenURI(newTokenId, _tokenUrl);
        return newTokenId;
    


    function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        override(ERC721, ERC721Enumerable)
    
        super._beforeTokenTransfer(from, to, tokenId);
    

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) 
        super._burn(tokenId);
    

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    
        return super.tokenURI(tokenId);
    

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    
        return super.supportsInterface(interfaceId);
    

合约和方法解析:

@openzeppelin/contracts/token/ERC721/ERC721.sol 该合约实现了我们上面列出的IERC721接口中的方法

@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol 该合约为一个枚举合约,包含了按索引获取到对应的代币,可以提供NFTs的完整列表,以便NFT可被发现。

@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol 该合约帮我们实现了TOKEN的URI的设定和读取方法

@openzeppelin/contracts/utils/Counters.sol 该合约帮我们实现了合约的自增ID

constructor() 我们在构造方法中实现了设定该合约生成的NFT的名称的设定(The First NFT)、简写的设定(FFT)

mint() 为_recipient地址铸造一个NFT,并将其元数据URL设置为_tokenUrl

_beforeTokenTransfer() 重写_beforeTokenTransfer方法

_burn() 销毁某个NFT

tokenURI() 获取指定NFT的URL信息

supportsInterface() 重写supportsInterface方法

参考

EIPs/eip-165.md at master · ethereum/EIPs · GitHub

EIPs/eip-721.md at master · ethereum/EIPs · GitHub

科普 | ERC协议终极解读:ERC-721

剖析非同质化代币ERC721-全面解析ERC721标准 - 走看看

solidity实现智能合约教程(2)-ERC721合约_后端常规开发人员的博客-CSDN博客_erc721智能合约

,让你了解ERC-1155 多代币标准协议

文章目录

ERC1155 介绍

用于多种代币管理的合约标准接口。 单个部署的合约可以包括同质化代币、非同质化代币或其他配置(如半同质化代币)的任何组合。

多代币标准

ERC1155 的显着特点是它使用单个智能合约一次代表多个代币。这就是为什么它的balanceOf功能不同于 ERC20 和 ERC777 的原因:它有一个额外的id参数,用于您要查询余额的代币的标识符。

这类似于 ERC721 做事的方式,但在该标准中,代币id没有平衡的概念:每个代币都是不可替代的,存在或不存在。ERC721balanceOf函数是指一个账户有多少不同的代币,而不是每个有多少。另一方面,在 ERC1155 账户中,每个代币都有不同的余额id,不可替代的代币是通过简单地铸造其中一个来实现的。

这种方法可以为需要多个代币的项目节省大量气体。无需为每种代币类型部署新合约,单个 ERC1155 代币合约可以保存整个系统状态,从而降低部署成本和复杂性。

前提条件

为了更好地理解后面的内容,需要首先了解以下三方面知识:

代币标准:

以下是以太坊上最受欢迎的一些代币标准:

  • ERC20:可替代资产最广泛使用的代币标准,尽管受到其简单性的限制。

  • ERC721:不可替代代币的实际解决方案,通常用于收藏品和游戏。

  • ERC777:更丰富的可替代代币标准,支持新的用例并建立在过去的学习基础上。向后兼容 ERC20。

  • ERC1155:一种新的多代币标准,允许单个合约代表多个可替代和不可替代的代币,以及批量操作以提高气体效率。

ERC-20

ERC-20 提供了一个同质化代币的标准,换句话说,每个代币与另一个代币(在类型和价值上)完全相同。 例如,一个 ERC-20 代币就像以太币一样,意味着一个代币会并永远会与其他代币一样。

详情可了解:https://eips.ethereum.org/EIPS/eip-20

ERC-721

ERC-721 为 NFT 引入了一个标准,换言之, 这种类型的代币是独一无二的,并且可能与来自同一智能合约的另一代币有不同的价值,也许是因为它的年份、稀有性、甚至是它的观感。 稍等,看起来怎么样呢?

是的。 所有 NFTs 都有一个 uint256 变量,名为 tokenId,所以对于任何 ERC-721 合约,这对值contract address, tokenId 必须是全局唯一的。 也就是说,dApp 可以有一个“转换器”,该转换器使用 tokenId 输入和输出一些非常有趣的事物的图像, 例如僵尸、武器、技能或非常可爱的猫咪!

详情可了解:https://eips.ethereum.org/EIPS/eip-721

构建 ERC1155 代币合约

我们将使用 ERC1155 来跟踪我们游戏中的多个项目,每个项目都有自己独特的属性。我们将所有项目铸造给合约的部署者,然后我们可以将其转移给玩家。玩家可以自由地保留他们的代币或在他们认为合适的时候与其他人交易,就像他们对区块链上的任何其他资产一样!

为简单起见,我们将在构造函数中铸造所有项目,但您可以在合约中添加铸造功能,以便按需铸造给玩家。

请注意,对于我们的游戏物品,黄金是可替代的代币,而雷神之锤是不可替代的代币,因为我们只铸造了一个。

ERC1155合同包括可选的扩展IERC1155MetadataURI。这就是uri函数的来源:我们使用它来检索元数据 uri。

另请注意,与 ERC20 不同,ERC1155 缺少decimals字段,因为每个令牌都是不同的并且无法分区。

ERC-1155 的功能和特点:

  • 批量传输:通过一次合约调用传输多种资产。
  • 批量余额:在一次调用中获取多个资产的余额。
  • 批量审批:审批同一地址的所有代币。
  • Hook:接收代币的钩子函数。
  • 支持非同质化代币:如果供应量仅为 1,将其作为非同质化代币处理。
  • 安全转账规则:安全转账规则集。

批量传输

批量传输与常规 ERC-20 传输非常相似。 让我们看看常规的 ERC-20 与ERC-1155有什么区别:

// ERC-20
function transferFrom(address from, address to, uint256 value) external returns (bool);

// ERC-1155
function safeBatchTransferFrom(
    address _from,
    address _to,
    uint256[] calldata _ids,
    uint256[] calldata _values,
    bytes calldata _data
) external;

ERC-1155 中唯一的区别是我们将数值作为数组传递,我们也传递了数组 id。 例如,给出 ids=[3, 6, 13] 和 values=[100, 200, 5],传输结果将是

  1. 将 id 3 的 100 个代币从 _from 传输到 _to。
  2. 将 id 6 的 200 个代币从 _from 传输到 _to。
  3. 将 id 13 的 5 个代币从 _from 转移到 _to。

在 ERC-1155 中,我们只有 transferFrom,没有 transfer。 要像常规的 transfer一样使用它,只需将 “from” 地址设为调用该函数的地址。

例如以下操作:

我们可以将物品转移到玩家账户:

> NFTV2.safeTransferFrom(deployerAddress, playerAddress, 2, 1, "0x0")
> NFTV2.balanceOf(playerAddress, 2)
1
> NFTV2.balanceOf(deployerAddress, 2)
0

我们也可以批量转账到玩家账户,获取批量余额:

> NFTV2.safeBatchTransferFrom(deployerAddress, playerAddress, [0,1,3,4], [50,100,1,1], "0x0")
> NFTV2.balanceOfBatch([playerAddress,playerAddress,playerAddress,playerAddress,playerAddress], [0,1,2,3,4])
[50,100,1,1,1]

批量余额

相应的 ERC-20 balanceOf 调用同样具有支持批处理的相应函数。 同样,使用ERC-20 与 ERC-1155 做一个对比:

// ERC-20
function balanceOf(address owner) external view returns (uint256);

// ERC-1155
function balanceOfBatch(
    address[] calldata _owners,
    uint256[] calldata _ids
) external view returns (uint256[] memory);

调用余额查询更简单的是,我们可以在单次调用中获取多个余额。 参数中传递所有者账户数组和代币的 id 数组。

例如,对于给出的 _ids=[3, 6, 13] 和 _owners=[0xbeef…, 0x1337…, 0x1111…],返回值将为:

[
    balanceOf(0xbeef...),
    balanceOf(0x1337...),
    balanceOf(0x1111...)
]

当然,我们也可以查询单个地址及token的余额

> NFTV2.balanceOf(deployerAddress,3)
1000000000

批量审批

// ERC-1155
function setApprovalForAll(
    address _operator,
    bool _approved
) external;

function isApprovedForAll(
    address _owner,
    address _operator
) external view returns (bool);

审批过程与 ERC-20 略有不同。 这里不是批准特定金额,而是通过 setApprovalForall 函数设置操作帐户为已批准或未批准。

查看当前的审批状态可以通过 isApprovedForall 完成。 如你所见,要么全部批准,要么不批准。 不能定义要批准代币的数量,甚至代币类型。

这是考虑到简洁性而故意设计的。 你只能批准一个地址的所有代币。

接收钩子

function onERC1155BatchReceived(
    address _operator,
    address _from,
    uint256[] calldata _ids,
    uint256[] calldata _values,
    bytes calldata _data
) external returns(bytes4);

基于 EIP-165 的协议支持,ERC-1155 只支持智能合约的接收钩子函数。 钩子函数必须返回一个事先预定义的 4 字节值,这个值被指定为:

bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))

当接收合约返回这一值时,意味着合约知道如何处理 ERC-1155 代币并接受转账。 太好了,代币不会再卡在合约中了!

使用时的一个关键区别safeTransferFrom是令牌转移到其他合约可能会恢复并显示以下消息:

ERC1155: transfer to non ERC1155Receiver implementer

这是一件好事!这意味着接收合约尚未将自己注册为了解 ERC1155 协议,因此禁用向其传输以防止代币被永久锁定。例如,Golem 合约目前持有超过 35万个GNT代币,价值数万美元,并且缺乏将它们从那里取出的方法。几乎每个 ERC20 支持的项目都会发生这种情况,通常是由于用户错误。

为了让我们的合约接收 ERC1155 代币,我们可以继承ERC1155Holder为我们处理注册的便利合约。尽管我们需要记住实现功能以允许将代币从我们的合约中转移出来:

支持非同质化代币

当供应量仅为 1 时,代币本质上就是一个非同质化的代币 (NFT)。 按照 ERC-721 的标准,您可以定义一个元数据网址。 客户端可以读取并修改网址,请参阅这里。

安全转账规则

在前面的解释中,我们已经提到过一些安全转账规则。 现在我们来看一下最重要的规则:

  1. 调用者必须获得批准才能从 _from 的账户地址消费代币,或者调用者账户地址必须与 _from 的账户地址相同。
  2. 在以下情况下,转账调用将回退
  • _to 地址为 0;
  • _ids 的长度与 _values 的长度不同;
  • _ids 中代币持有者的任何余额低于发送给接收者的相应 _value 金额。
  • 出现任何其他错误。

注意:包括钩子在内的所有批处理函数也均作为非批处理的版本存在。 这样做是为了提高燃料效率,考虑到只转移一种资产可能仍然是最常用的方式。 简洁起见,我们没有在这里介绍这些非批处理的版本,包括安全转账规则。 名称是相同的,只需移除 ‘Batch’。

参考文档:
ERC-1155 Github 代码库:https://github.com/enjin/erc-1155
ERC-1155 Openzepelin 文档:https://docs.openzeppelin.com/contracts/3.x/erc1155
EIP-1155 多代币标准:https://eips.ethereum.org/EIPS/eip-1155
EIP-1155 以太坊官网文档:https://ethereum.org/zh/developers/docs/standards/tokens/erc-1155/

以上是关于ERC721标准合约接口事件和方法分析的主要内容,如果未能解决你的问题,请参考以下文章

初探区块链数字加密资产标准ERC721

剖析非同质化代币ERC721-全面解析ERC721标准

科普 | 三分钟搞懂 ERC-20 和 ERC-721 的不同

暂停交易?ERC20合约整数溢出安全漏洞案例技术分析(一)

暂停交易?ERC20合约整数溢出安全漏洞案例技术分析一

ERC721标准与加密猫