在上篇文章里,我们展示了如何发行 token,以及如何通过上传打卡数据来获得 token。在实际情况中,我们一般会设置一些打卡任务,比如每个月必须跑多少次,或者是爬几次山之类的,在本文里,此类情况称之为运动挑战。在完成了运动挑战后,可以给用户发放奖章,就像各类运动 App 里设计的那样。
这个奖章的需求就很适合用 NFT 来完成。NFT,全称非同质化代币,即每个代币都是唯一的。类比下现金,你手里的一块钱和别人手里的一块钱,即使不是同一个硬币,但是都是一块钱,是可以等价交换的,这个就是 token 的概念。但是你手里的一张毕业证,和你同学的毕业证,虽然都是毕业证,但是它们是不可同等交换的。所以即使对于一个项目,它一次发售了 1 个 NFT,但是因为每个都不一样,但是反映到价格上是不一样的。
所以 NFT 就很适合用来代替权益证明,证件啊、奖章啊之类的。只是现阶段大都玩成了小图片,[摊手]
当用户完成一项挑战之后,要给 ta 发放一枚独特的奖章,每个人的都不一样。类似于 Apple Watch 的奖章体系,虽然奖章的证明是一样的,但是奖章的背面是有用户的名字的。
然后,还可以设计几个不同类别的奖项。如跑步的、登山的、游泳的,等等。根据完成的不同挑战给到不同的奖章。进一步还可以更个性化一些,比如对于登山,春天的和秋天的,是不是奖章的样式可以有所区分,等等。
对于大部分的 NFT,就是一张图片,然后这张图片存储到 IPFS 之类的去中心化存储上。IPFS 可以理解为百度网盘,可以用来存储各类文件。除了图片本身,同时还会有一个 NFT 元数据的配置文件,里面会标记好如图片地址、分类属性等信息,通过解析这个配置文件,就可以解析出 NFT。这些文件是存储在 IPFS 上的,所以不用担心删除、丢失等问题。
然后,只需要在需要给用户发放奖章的时候,将用户的地址和奖章的 ID 做好绑定关系,同时将奖章 ID 和对应的配置文件绑定好关系,就可以了。这个关系就是通过合约存储在区块链上,所以,实现起来并不复杂。
稍微复杂一些的,应该就在于挖出什么样的图片了。有些项目是提前将图片全部按照程序生成好,然后挖的时候开盲盒;或者也可以在挖的时候再去随机生成。同时只要将属性细化,准备对应的模版,通过预言机来获取当前的天气、位置等信息,生成的结果可以更加的个性化。
和 Token 类似,NFT 的规范是 ERC721,里面也定义了一系列需要实现的接口,只要实现了这些接口,就是 NFT。
它包括:
function balanceOf(address _owner) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
function approve(address _approved, uint256 _tokenId) external payable;
function setApprovalForAll(address _operator, bool _approved) external;
function getApproved(uint256 _tokenId) external view returns (address);
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
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);
和 Token 的比较类似,关于 Token 的详细规范要求,可以看上篇文章。同时,这里面没有列出如 name、symbol 的属性要求,这两个属性的实现和 Token 一致,本文不做赘述。本文重点放在 NFT 和用户的关系绑定上
首先,定义好保存绑定关系的字段
mapping(address => uint256) private balances;
mapping(uint256 => address) private owners;
mapping(uint256 => string) private metas;
NFT 的 ID 必须是唯一的,因为合约地址是唯一的,所以只需要通过合约地址和 NFT 地址,就能标记出全网唯一。这个 ID 可以是 UUID,可以是 Sha,也可以更简单的使用 1、2、3、4 这样的数字,在本文里,我们使用不断增长数字来标记 ID,每个用户挖出 NFT 后,就将这个 ID 加 1。
一个用户有多少个 NFT,在 balances 里保存。对于一个给定的 NFT ID,它属于谁,在 owners 里保存,它的属性数据保存在 metas 里。对应的数据获取接口如下:
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
function balanceOfMe() public view returns (uint256) {
return balances[msg.sender];
}
function ownerOf(uint256 tokenId) public view returns (address) {
return owners[tokenId];
}
function tokenUri(uint256 _tokenId) public view returns (string memory) {
return metas[_tokenId];
}
设置属性数据的接口如下:
function _setTokenUri(uint256 _tokenId, string memory _tokenUri) public {
metas[_tokenId] = _tokenUri;
}
挖 NFT,简单来说其实就是生成一个新的奖章 ID,给它绑定好属性属性,然后将这个 ID 和用户的地址做好绑定
function _mintNFT(string memory _tokenUri) public returns (uint256) {
require(bytes(_tokenUri).length != 0);
_curItemId += 1;
owners[_curItemId] = msg.sender;
metas[_curItemId] = _tokenUri;
balances[msg.sender] += 1;
return _curItemId;
}
具体生成外部的属性信息的过程就不做展示了,可以在合约里完成,也可以在外部程序上完成。在用户完成了挑战后,来调用上面这个接口就可以了
接下来就是 NFT 的转移,转移的过程简单来说就是将卖家的 balance 减 1,将买家的 balance 加 1,将这个 NFT 的 ID 的绑定关系有卖家转绑为买家
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
function transferFrom(address _from, address _to, uint256 _tokenId) public {
require(owners[_tokenId] == _from, "no permission");
require(_to != address(0));
balances[_from] -= 1;
balances[_to] += 1;
owners[_tokenId] = _to;
emit Transfer(_from, _to, _tokenId);
}
对于现在的 NFT 项目,一般都会设置一个总供给,只需要在挖的接口里做好校验即可,同时还可以使用一个字段来标记当前还有多少未挖出,用来在前端用户界面上进行展示。
上述的示例中,并未完全完成 ERC721 的接口要求,但参照上面的实现方法,也是可以很容易的实现。和 Token 的实现一样,一般项目中我们不会手动的来实现这些接口,可以通过使用 OpenZepplion 来快速实现。
对于 NFT 项目,编写合约并不是难事儿,完全自动化的完成都是可行的,更重要的还在于产品的运营能力,市面上这么多的 NFT,为什么价格差异巨大?有的可以一路上涨,而有的则是破发。合约上并没有什么差异,还在于运营的能力。
最后,希望后面能看到更多 NFT 不同的玩法,如果只是玩小图片,那从业者的想象力太有限了。
