在《区块链杀手级应用落地畅想(上)》中我们提到,2021年被称为NFT“元年”。在短时间内,NFT已不再局限于加密世界的投机价值,其释放的潜力吸引了越来越多的国际品牌,例如耐克将鞋子作为NFT专利,允许用户“繁殖”不同的鞋子来创造属于自己的定制运动鞋;其他诸如美国国家篮球协会(NBA)、路易威登(LV)等国际知名品牌均在加速布局,可以说NFT正在为艺术收藏、音乐、游戏、体育、时尚圈等赋予新的价值加持。

NFT应用场景丰富,初学者怎么入门?本文将帮助开发小白了解NFT合约的编写。

原文链接:https://mp.weixin.qq.com/s?__biz=Mzg2MDA2NzQwNw==&mid=2247491091&idx=1&sn=0df3e490dc44bfcc2b8ce50d42c4d24f&chksm=ce2d5d33f95ad425ff3a2043729ad80133add7837b3b07d6de4affa4d4e4892c86ce76268213&token=291386684&lang=zh_CN#rd

NFT合约标准介绍

目前,NFT(Non-Fungible Tokens)最为主流有三种合约:ERC-721、ERC-1155和ERC-998。

在NFT的最初期,大家严格遵守NFT的定义规范,也就是ERC-721规范,早年非常火热的加密猫系列就是基于该规范开发的。从 ERC-721 协议标准来看,每一个基于ERC-721创建的NFT都是独一无二、不可复制的。用户可以在智能合约中编写一段代码来创建自己的NFT,该段代码遵循一个比较基础的通用模版格式,可通过该代码添加关于NFT的所有者名称、元数据或安全文件链接等细节。

ERC-721规范虽然可以很好的描述NFT,却存在着一些不足。例如,假设我想一次性铸造30个NFT,那么我就需要发起30次铸造NFT的交易,效率和用户体验并不友好。为此ERC-1155提出了“打包”的概念,可以将多个NFT封装成一个Collection,允许开发者在一个智能合约中实现无限数量的FT和NFT。正是由于“打包”的特性,相当于ERC-1155协议标准集成了ERC-20和ERC-721的能力,具有效率高、灵活性强等优势,目前已经为多款游戏提供了动力,例如游戏开发者可以在一个合约里定义多种物品(角色、武器、盔甲、药水、超能力)。

随着NFT概念的进一步火热,组合式NFT概念被提出。例如一个头像可以由眼睛、嘴巴和鼻子等元素组成,每个元素都是一个NFT或者FT,这些元素共同组成了一个独一无二的NFT头像。但是对于整个头像NFT而言,在过去传统合约中是没有所谓层级关系的,即鼻子部分并不知道自己属于哪个NFT,或者头像部分不知道自己是由哪些NFT或者FT组成的。为此,ERC-998便应运而生,也就是可组合Composable NFTs,缩写为CNFT,即一个ERC-998可以包含多个ERC-721和ERC-20形式的通证,而转移CNFT即是转移CNFT所拥有的整个层级结构和所属关系。

为帮助大家快速理解并入门,下文将先分析ERC-721和ERC-1155的合约设计理念,随后详细介绍如何编写ERC 721合约。

NFT合约设计理念

ERC-721

ERC-721作为最为基础的NFT合约,具有以下几个接口:

function balanceOf(address owner) -> uint256 balance
/// @notice Find the owner of an NFT
/// @dev NFTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an NFT
/// @return The address of the owner of the NFT

function ownerOf(uint256 tokenId) -> address owner
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless msg.sender is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if _from is
/// not the current owner. Throws if _to is the zero address. Throws if
/// _tokenId is not a valid NFT. When transfer is complete, this function
/// checks if _to is a smart contract (code size > 0). If so, it calls
/// onERC721Received on _to and throws if the return value is not
/// bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")).
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param data Additional data with no specified format, sent in call to _to

function safeTransferFrom(address from, address to, uint256 tokenId)
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to “”.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer

function transferFrom(address from, address to, uint256 tokenId)
/// @notice Change or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// Throws unless msg.sender is the current NFT owner, or an authorized
/// operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve

function approve(address to, uint256 tokenId)
/// @notice Enable or disable approval for a third party (“operator”) to manage
/// all of msg.sender’s assets
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval

function getApproved(uint256 tokenId) -> address operator
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if _operator is an approved operator for _owner, false otherwise

function setApprovalForAll(address operator, bool _approved)
/// @notice Get the approved address for a single NFT
/// @dev Throws if _tokenId is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return The approved address for this NFT, or the zero address if there is none

function isApprovedForAll(address owner, address operator) -> bool

其中每一个函数的意义可参见上述注释,接下来我们会简要分析底层存储逻辑:

mapping (uint256 => address) internal idToOwner
mapping (uint256 => address) internal idToApproval
mapping (address => uint256) private ownerToNFTokenCount
mapping (address => mapping (address => bool)) internal ownerToOperators
上述的4个mapping维护了整个合约的存储结构:

idToOwner维护了谁拥有什么通证,映射关系是通证ID到其所有者地址;
idToApproval维护了谁被授权操作某个通证,映射关系是通证ID到被授权操作的地址;
ownerToNFTokenCount维护了某个地址所拥有的nft总量,映射关系是用户地址到代表总量的整数;
ownerToOperators维护了某个地址是否授权给了另外一个地址;

一个主要的modifier是canOperate:

// 查看是否具备操作某个nft的权限
modifier canOperate(
uint256 _tokenId
)
{
// 找到对应token的所有者
address tokenOwner = idToOwner[_tokenId];
require(
// 需要操作者是所有者或者被所有者授权
tokenOwner == msg.sender || ownerToOperators[tokenOwner][msg.sender],
// 否则返回错误
NOT_OWNER_OR_OPERATOR
);
_;
}
同时,ERC-721还支持可选的实现项,metadata extension,主要用以返回NFT的描述信息。

ERC-1155
ERC-1155同上面的描述,因为实现了“打包”的功能,所以ERC-1155的大部分函数都支持batch的操作。相比于ERC-721,ERC-1155有很好的效率提升。

ERC-1155具有以下接口:

function balanceOf(address account, uint256 id) → uint256
function balanceOfBatch(address[] accounts, uint256[] ids) → uint256[]
function setApprovalForAll(address operator, bool approved)
function isApprovedForAll(address account, address operator) -> bool
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)
function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)
具体的接口也都比较明确,这里不再赘述。存储结构上,ERC-1155具有以下两个基本的结构:

mapping (uint256 => mapping(address => uint256)) internal balances;
mapping (address => mapping(address => bool)) internal operatorApproval;

balances维护了某个账户拥有的某个NFT总量,基本的映射逻辑是id=>(owner=>balances)

operatorApproval维护了某个账户是否已经被另一个账户授权,主要逻辑同上;

同样的,ERC-1155具备一个可选的扩展合约ERC1155Metadata_URI,主要是返回某个通证的uri json。

ERC-721合约编写
由于目前社区已经有大量开源的ERC-721标准模板可供参考,在编写大部分的NFT合约时完全可以借鉴通用模板。若标准模板无法满足全部需求时,可在外部新建一个属于自己的合约(内部实现相应的业务逻辑),并且对标准合约进行继承。

下面的示例将以某开源标准ERC-721合约作为基础模板,展示在趣链BaaS平台内的Web IDE内进行进一步的合约开发。

1)进入Web IDE:如下图,在nf-token-mock合约中定义了mint NFT的方法,我们进入该合约并执行编译操作。
在这里插入图片描述
2)编译合约:具体结果如下。

在这里插入图片描述

3)Web IDE模拟部署与执行:不同于以太坊在线IDE编辑器如Remix,趣链BaaS的Web IDE直接提供模拟部署和执行环境,无需用户使用Metamask的测试网账户,相当于省去了用户在Metamask导入一个测试网账户并拥有测试通证的步骤,也无需在每次调用中进行签名授权,可提升调试效率。

因此,如下图我们可选择NFTTokenMock合约进行模拟部署,该合约中封装了NFT mint等方法,我们先进行mint后,可进一步执行balanceof(查询余额)、Approve(授权)等操作。
在这里插入图片描述
4)mint(铸造):向0xd69e9413029e7Fc483eFB5cB1aBCE4Ec44437F2C地址铸造一个通证ID为166的NFT
在这里插入图片描述

5)balanceof(查询余额):查询0xd69e9413029e7Fc483eFB5cB1aBCE4Ec44437F2C地址共有几个NFT
在这里插入图片描述

相似的,您可以参照合约设计中提到的不同接口信息,调用函数执行Approve(授权)等操作。

6)合约安全检测:如前所述,上述合约是基于社区开源的合约文件,对于安全性未可知。因此我们可以借助趣链Web IDE的静态分析和形式验证等合约安全检测工具对合约进行检测,帮助最大化规避合约潜在漏洞造成的风险。
在这里插入图片描述
7)个性化完善合约功能:本例的合约已经封装了很多函数方法,但开发者还可以根据需求编写更多功能,在模拟执行时还可以使用Debug操作帮助调试。

8)合约编译文件集成至SDK: 做完以上所有调试并编译完成后,可将最终的合约编译文件集成至趣链BaaS提供的SDK中,由此可通过SDK进行NFT合约的部署、调用等管理操作。

9)SDK集成至区块链应用:最后,开发者还需要打通业务系统和链上智能合约的交互,只需要将对应的SDK集成至自己的区块链应用项目中即可。

【备注】

在步骤8中介绍的是通过SDK部署合约,对于初学者依然存在一定的学习门槛。如下图趣链BaaS提供了一键可视化部署合约实例的功能。在部署完成后,可直接通过趣链BaaS平台进行智能合约的可视化调用。

部署界面
在这里插入图片描述

合约实例管理界面
在这里插入图片描述

合约实例调用界面
在这里插入图片描述

总结
NFT历经十年发展,出现了ERC-721,ERC-1155和ERC-998等为典型的主流合约,技术架构日趋完善。初学者除了查阅一些开源项目了解通用NFT合约文件外,还可以借助诸如趣链Web IDE等便捷的智能合约研发设施,可充分赋能智能合约研发、部署、调用、升级等全生命周期管理流程,加速相关区块链应用的落地。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。