看完这篇!新手也能写nft合约! -pa旗舰厅官网
在《》中我们提到,2021年被称为nft“元年”。在短时间内,nft已不再局限于加密世界的投机价值,其释放的潜力吸引了越来越多的国际品牌,例如耐克将鞋子作为nft专利,允许用户“繁殖”不同的鞋子来创造属于自己的定制运动鞋;其他诸如美国国家篮球协会(nba)、路易威登(lv)等国际知名品牌均在加速布局,可以说nft正在为艺术收藏、音乐、游戏、体育、时尚圈等赋予新的价值加持。
nft应用场景丰富,初学者怎么入门?本文将帮助开发小白了解nft合约的编写。原文见公众号:qtech
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 idtoownermapping (uint256 => address) internal idtoapprovalmapping (address => uint256) private ownertonftokencountmapping (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) → uint256function balanceofbatch(address[] accounts, uint256[] ids) → uint256[]function setapprovalforall(address operator, bool approved)function isapprovedforall(address account, address operator) -> boolfunction 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等便捷的智能合约研发设施,可充分赋能智能合约研发、部署、调用、升级等全生命周期管理流程,加速相关区块链应用的落地。
在《》中我们提到,2021年被称为nft“元年”。在短时间内,nft已不再局限于加密世界的投机价值,其释放的潜力吸引了越来越多的国际品牌,例如耐克将鞋子作为nft专利,允许用户“繁殖”不同的鞋子来创造属于自己的定制运动鞋;其他诸如美国国家篮球协会(nba)、路易威登(lv)等国际知名品牌均在加速布局,可以说nft正在为艺术收藏、音乐、游戏、体育、时尚圈等赋予新的价值加持。
nft应用场景丰富,初学者怎么入门?本文将帮助开发小白了解nft合约的编写。原文见公众号:qtech
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 idtoownermapping (uint256 => address) internal idtoapprovalmapping (address => uint256) private ownertonftokencountmapping (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) → uint256function balanceofbatch(address[] accounts, uint256[] ids) → uint256[]function setapprovalforall(address operator, bool approved)function isapprovedforall(address account, address operator) -> boolfunction 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等便捷的智能合约研发设施,可充分赋能智能合约研发、部署、调用、升级等全生命周期管理流程,加速相关区块链应用的落地。
本文链接:
转载请注明文章出处