第十七课 【ERC721实践】迷恋猫从玩耍到开发

96
笔名辉哥
23.6 2018.05.31 14:17* 字数 5682


CryptoKitties(中文名:迷恋猫)是一款在以太坊区块链上的虚拟养猫游戏,一经推出就以病毒式的快速扩散,横扫整个以太坊市场。而这款可爱的游戏于2018年 2 月 16 日(农历大年初一)登陆 iOS国区,中文名称的 “迷恋猫”,皆因 “迷恋” 与 “密链” 同音,亦有 “加密区块链” 的意思,而且为了庆祝中国农历新年,所有在节日期间出生的新猫咪都将会有中国的背景故事。
迷恋猫是一款由 Axiom Zen 和以太坊智能合作开发的区块链宠物养成游戏。在游戏中,玩家使用以太币(ETH)进行电子猫的购买、喂食、照料与交配等,但其最为核心是玩法是将以太坊区块链中的电子猫进行出售。撇开众人对虚拟小猫的狂热不谈,毕竟绝对不只是猫很可爱这种原因,这个游戏已造成过大流量,甚至因流量太大导致游戏困难,让很多交易耗费比原本更长的时间,官方甚至为此让生育费涨价以提供矿工更高的诱因。目前 CryptoKitties 已经成为以太坊最流行的智能合约,数量约莫是第二名的两倍。

辉哥系统整合出了ERC721从理论到实践的文章,可按需查看:
(1)第十七课 【ERC721实践】迷恋猫从玩耍到开发
(2)第三十三课 如何创建自己的ERC721非同质化资产生物商店?
(3)第三十四课 采用TRUFFLE框架如何创建自己的ERC721非同质化资产生物商店?
(4)第三十五课 如何配置Metadata以便装饰你的ERC721非同质化资产?

1. 迷恋猫注册/玩法攻略

玩迷恋猫游戏,玩家需要在以太坊区块链上下载到这款游戏的APP,游戏开始系统会赠送玩家一只喵。刚推出时是送猫的,现在只有活动时才赠送。它让你沉迷于吸猫,然后当你无法自拔后,就会自愿掏出大把的以太币去氪金了,满满的套路啊!

1.1 在谷歌浏览器中安装MetaMask

1.1.1 下载安装谷歌浏览器(Chrome)

百度搜索"谷歌浏览器"即可进行下载安装(谷歌浏览器可以完美支持第三方插件和扩展程序)。

1.1.2 安装MetaMask钱包插件

MetaMask是一款运行稳定的以太坊钱包,我们和其他交易所的区别之一在于在这里您可以自主管理您的钱包,并且钱包所有的资产变动都会上区块链确认,完全是开放和透明的,而在其他交易所,您的钱包实际是平台在管理,无法完全杜绝资金风险以及被限制充币提币等行为。
1)国内用户可使用 MetaMask_v3.9.11.crx下载MetaMask并解压,然后在扩展程序设置页面chrome://extensions/将下载好的文件拖进来即可安装,安装后点击启用,即可在浏览器右上角看到MetaMask图标。如下图:

image

2)翻墙或者国外的用户可以点击 这里在谷歌插件商店进行安装。

image

1.2 创建MetaMask账号

1)接下来,点击MetaMask图标创建自己的账户。
2)对MetaMask钱包插件进行设置,首先需要链接到主网。点击MetaMask界面的左上角,选择Main Ethereum Network

image

1.3 购买迷恋猫

打开官方网站(www.cryptokitties.co ),用MetaMask的钱包地址登录,然后到市场上用ETH去购买猫,新手登录将免费获得一只。需要拥有以太币(ETH),用于养猫的花费,没有的话要先去交易所买点以太币ETH。
然后到你的猫咪个人页面去喂养、培育、销售它。每一个猫咪都可以选择扮演母亲或者父亲的角色,和其他异性猫咪组成家庭,最后按繁殖按钮生下小猫咪。如果想和其他人的猫咪繁育,则需要支付其他玩家相应的ETH。生下的小猫咪则可以去市场上交易。猫咪每多繁殖一次,培育小猫咪所需时间都会相应增长,目前官网上每5分钟就诞生一个小猫咪。主人可以将自己的猫咪打扮各种风格的花式猫。由于区块链是分散的,每个交易都是通过多台计算机分配的,而不是中央服务器,所以玩家支付的ETH相当于区块链网络中支持交易或合约的成本。
当你已在浏览器安装MetaMask插件并手握MetaMask账号后,撸猫的一切准备工作已经完毕,登陆迷恋猫(CryptoKitties)官网,准备选购你的第一只猫咪吧。

在以太猫这款游戏中,玩家必须至少拥有一只猫才可进行游戏,而获得猫主要可以通过以下三种方式:
1.测试玩家,获赠免费的猫
2.普通玩家,在游戏内市场购买自己的第一只猫
3.普通玩家,在Twitch、Reddit等平台等待主播或其他玩家赠送(适合体验游戏)
在这里我们仅介绍第2种,通过在市场中购买猫的方式开始游玩:

image.png

当您已在浏览器上安装并登陆MetaMask钱包,迷恋猫官网会自动同步您的登陆信息。在此过程中您可能会遇到账号被锁定的阻碍,这是正常现象,输入密码就可解锁。


初始页面,左上为您的个人信息,右侧顶部的三个标签栏依次为:“我的猫咪”(My Kitties)、“市场”(Marketplace)还有“邀请”(Invite)。但由于上图中的账号未拥有猫,所以页面中显示的是市场推荐。
让我们点进“市场”看看具体情况。
玩家可以在市场中交易或给自己的猫配种。页面中上的四个标签分别为:“正在售卖”(For Sale)、“提供配种”(Siring)、“零代猫”(Gen 0)以及“全部”(All Kitts)。
顾名思义,通过“正在售卖”标签玩家可以快速查看可以购买的对象。
“提供配种”标签下显示的全是可以用来与自己猫交配的“公猫”对象。
“零代猫”的概念比较特殊,在2018年11月前,系统每隔15分钟都会产出一批零代猫,它们拥有最短的初始生殖间隔。新零代猫的初始价格比最近市场中售卖出的五只猫的价格平均值高50%。
通过“全部”标签,玩家可以查看游戏里的所有猫。无论是“市场”还是“我的猫咪”页面,玩家都可通过右侧下拉框简单筛选显示规则。规则大体为“最新一代”、“最初始一代”以及“最便宜”和“最贵”。
购买猫时,玩家除要选择带有“For sale”(正在售卖)标签的对象,还需仔细查该对象的各项属性。
这里要注意几点,包括猫的代数,猫的交配间隔以及它的外观和属性。
通常来说,零代猫拥有最短的初始交配间隔,但随着玩家让其配种次数的增多,猫咪的交配间隔会愈发延长。



以今天创下新交易记录的4号猫“Fluffy”为例,它的交配间隔已经降至第二档,意味着它只能每隔2-5分钟交配一次。


由玩家培育出的猫咪的交配间隔不会低于它的父母。目前最慢一档的交配间隔为一周一次。而仅在目前看来,交配间隔越短的猫咪在市场上的价值也就越高。
已有不少玩家把迷恋猫当成“生殖农场”,所以如果您是初次进入游戏,一定要观察购买对象是否处在交配CD,以及它的交配欲望。
除了交配相关,猫的外观、性状以及父母和子女可能都是买猫者要考虑在内的问题。


从眼睛到尾巴再到花色甚至是背景,这些都是能够直观观察到的。而同过“Cattributes”一栏,你还可以进一步看到更多关于这只猫咪由基因排列体现出的性状特征。
题外话,迷恋猫里的猫咪均由256位基因编码组成,父母的基因在一定程度上会决定孩子的特征。零代猫由于是系统生成,无父母一栏。
看到了吧,在迷恋猫里买猫的门道非常多,入坑需谨慎。
在选择好想要购买的猫咪后,点击“Buy now”按钮并使用以太币购买猫咪。
以上就是就是以太坊买猫攻略。需要注意的是,游戏中玩家自行配种以及更多涉及市场的操作均需向平台提交手续费。所以不仅是想在迷恋猫里赚钱,就算想要维持持续运作也不是一件容易事。当然,如果你手握一大堆以太币那就简单了。

2. 谜恋猫源码详解

要想快速了解这个游戏的工作原理,最好的方法就是直接阅读源代码。
CryptoKitties 的源代码大部分是开源的(也有小部分没有开源,主要是是交配基因更新这部分)。CryptoKitties 源码大约有2000行,本文将主要讲解其中相对重要的部分。

点击可查看CryptoKitties的智能合约和代码

CryptoKitties游 戏的源代码分成了一个个的子合约,而非一个包含所有逻辑的单一文件。
子合约通过下述方式继承主合约:

contract KittyAccessControl
contract KittyBase is KittyAccessControl
contract KittyOwnership is KittyBase, ERC721
contract KittyBreeding is KittyOwnership
contract KittyAuction is KittyBreeding
contract KittyMinting is KittyAuction
contract KittyCore is KittyMinting

最终应用程序指向的合约是 KittyCore 合约,这个合约继承了前面所有合约的属性和方法。接下来,让我们一个一个地来分析这些合约。

2.1 KittyAccessControl:谁控制合约?

该合约负责管理各种不同的地址,同时定义了各种仅限于特定角色执行的限制性操作,这些角色被命名为“CEO”、“CFO”和“COO”。
KittyAccessControl 合约主要功能是管理其他合约,所以它不涉及游戏的具体逻辑。该合约定义了以太坊地址“CEO”、“CFO”和“COO”的使用方法,它们对该合约有着特殊的所有权以及控制权限。
KittyAccessControl 合约还定义了一些函数修饰符,如 onlyCEO (该函数只有“CEO”才能执行),同时该合约还定义了一些暂停/恢复合约的方法以及提现方法。

 modifier onlyCLevel() {
     require(
         msg.sender == cooAddress ||
         msg.sender == ceoAddress ||
         msg.sender == cfoAddress
     );
     _;
 }

 //...some other stuff

// Only the CEO, COO, and CFO can execute this function:
function pause() external onlyCLevel whenNotPaused {
    paused = true;
}

pause() 函数主要是方便开发人员进行版本更新,以防那些预见不到的 bug。但我同事 Luke 指出,这个方法可以让开发人员有权完全冻结合约,令所有人都无法转让、出售或繁殖他们的猫咪!当然,开发人员并不想这么做。但真正有趣的地方确实,大多数人仅仅因为该游戏运行在以太坊上,就会以为它是一款完全是去中心化的游戏。
我们接着来看其他合约。

2.1 KittyBase:数字猫是什么猫?

KittyBase 合约是我们定义最底层的代码的地方,这些代码会用到整个游戏的所有核心功能上,包括数据存储结构、相关常量与数据类型,以及管理这些数据的内部函数。
KittyBase 合约定义了应用程序的很多核心数据。首先它将Kitty定义为结构体类型:

struct Kitty {
    uint256 genes;
    uint64 birthTime;
    uint64 cooldownEndBlock;
    uint32 matronId;
    uint32 sireId;
    uint32 siringWithId;
    uint16 cooldownIndex;
    uint16 generation;
}

从这里可以看到,一只猫咪的本质就是一长串的无符号整数。

接下来一一介绍猫咪的每个属性的具体参数:

genes — 是256位的整数,主要作为猫的遗传密码,是决定猫外貌的核心数据;
birthTime — 猫出生时所打包的区块的时间戳;
cooldownEndBlock — 猫可以再次繁殖的最小时间;
matronId&sireId — 猫母亲的ID号和猫父亲的ID号;
siringWithId — 如果猫怀孕了,则设置为父亲的ID,否则为零;
cooldownIndex — 猫繁殖所需的冷却时间(猫需要等待多久才能繁殖);
generation — 猫的“世代号”(指明这是第几代猫)。合约创造的第一只猫是0代,新一代猫的“世代号”是其父母中较大的一代再加1。

需要注意的是,在 CryptoKitties 游戏中,猫是无性别之分的,任意2只猫都可以一起繁殖。
KittyBase 合约声明了一个“猫”结构的数组,如下所示:

Kitty[] kitties;

该数组包含了所有猫咪的数据,所以它就像一个猫咪数据库一般。无论什么时候生成一个新的猫咪,它都会被添加到该数组内,数组的索引就是猫咪的 ID 号。下图显示的是创世猫,其 ID 号为“1”。

该合约中还包含有从猫咪 ID 号到其所有者地址的一个映射,主要用于追踪猫咪的所有者。

mapping (uint256 => address) public kittyIndexToOwner;

合约中还定义了其他一些映射,但此处不再深究每一个细节。
当猫咪的所有者从一个人转移到另外一个人时,kittyIndexToOwner 映射就会更新,从而识别新的所有者。

 /// @dev Assigns ownership of a specific Kitty to an address.
 function _transfer(address _from, address _to, uint256 _tokenId) internal {
    // Since the number of kittens is capped to 2^32 we can't overflow this
     ownershipTokenCount[_to]++;
     // transfer ownership
     kittyIndexToOwner[_tokenId] = _to;
     // When creating new kittens _from is 0x0, but we can't account that address.
     if (_from != address(0)) {
         ownershipTokenCount[_from]--;
        // once the kitten is transferred also clear sire allowances
        delete sireAllowedToAddress[_tokenId];
        // clear any previously approved ownership exchange
        delete kittyIndexToApproved[_tokenId];
    }
    // Emit the transfer event.
    Transfer(_from, _to, _tokenId);
}

现在我们来看看当一个新的猫咪生成时都发生了些什么:

 function _createKitty(
     uint256 _matronId,
     uint256 _sireId,
     uint256 _generation,
     uint256 _genes,
     address _owner
 )
     internal
     returns (uint)
{
    // These requires are not strictly necessary, our calling code should make
    // sure that these conditions are never broken. However! _createKitty() is already
    // an expensive call (for storage), and it doesn't hurt to be especially careful
    // to ensure our data structures are always valid.
    require(_matronId == uint256(uint32(_matronId)));
    require(_sireId == uint256(uint32(_sireId)));
    require(_generation == uint256(uint16(_generation)));

    // New kitty starts with the same cooldown as parent gen/2
    uint16 cooldownIndex = uint16(_generation / 2);
    if (cooldownIndex > 13) {
        cooldownIndex = 13;
    }

  Kitty memory _kitty = Kitty({
        genes: _genes,
        birthTime: uint64(now),
        cooldownEndBlock: 0,
        matronId: uint32(_matronId),
        sireId: uint32(_sireId),
        siringWithId: 0,
        cooldownIndex: cooldownIndex,
        generation: uint16(_generation)
    });
   uint256 newKittenId = kitties.push(_kitty) - 1;

    // It's probably never going to happen, 4 billion cats is A LOT, but
    // let's just be 100% sure we never let this happen.
    require(newKittenId == uint256(uint32(newKittenId)));

   // emit the birth event
    Birth(
        _owner,
        newKittenId,
       uint256(_kitty.matronId),
       uint256(_kitty.sireId),
       _kitty.genes
    );

   // This will assign ownership, and also emit the Transfer event as
   // per ERC721 draft
    _transfer(0, _owner, newKittenId);

   return newKittenId;
    }

从上面这段代码中可以看出,这个函数传递了母亲和父亲的 ID 号、猫咪的世代号、256位遗传密码和所有者的地址。然后创建猫咪,并将其加入到 Kitty 数组中,最后调用 _transfer() 将其分配给新的所有者。
是不是很酷!现在我们已经知道 CryptoKitties 游戏如何将一只猫咪定义为一种数据类型,如何将所有猫咪都存储在区块链中,以及如何跟踪这些猫咪的所有者。

2.3 KittyOwnership:作为通证的猫咪

CryptoKitties 游戏遵循 ERC721 通证标准,这是一种不可替代通证,它可以很好地追踪数字收藏品的所有权,比如数字扑克牌或 MMORPG(大型多人在线角色扮演游戏)中的稀有物品。
关于可互换性的说明:Ether 是可互换的通证,因为任意5个 ETH 都与其他5个 ETH 一样有着同样的价值。但是像 CryptoKitties 这样的通证是不可替代通证,每只猫咪的外观都各不相同,它们不能生而相同,所以无法相互交换。
你可以从合约的定义中看出,KittyOwnership 继承的是 ERC721 合约。

contract KittyOwnership is KittyBase, ERC721 {

而所有是 ERC721 通证都遵循同样的标准。下一章节分析ERC721标准。

3. 非同质化代币ERC721分析

3.1 ERC721是什么

和ERC20一样,ERC721同样是一个代币标准,ERC721官方简要解释是Non-Fungible Tokens,简写为NFTs,多翻译为非同质代币
ERC721 是由Dieter Shirley 在2017年9月提出。Dieter Shirley 正是谜恋猫CryptoKitties背后的公司Axiom Zen的技术总监。因此谜恋猫也是第一个实现了ERC721 标准的去中心化应用。ERC721号提议已经被以太坊作为标准接受,但该标准仍处于终结Final阶段。本文介绍的ERC721标准基于最新(2018/03/23)官方提议。

那怎么理解非同质代币呢?
非同质代表独一无二,谜恋猫为例,每只猫都被赋予拥有基因,是独一无二的(一只猫就是一个NFTs),猫之间是不能置换的。这种独特性使得某些稀有猫具有收藏价值,也因此受到追捧。
ERC20代币是可置换的,且可细分为N份(1 = 10 * 0.1), 而ERC721的Token最小的单位为1,无法再分割。

如果同一个集合的两个物品具有不同的特征,这两个物品是非同质的,而同质是某个部分或数量可以被另一个同等部分或数量所代替。

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

3.2 ERC721标准

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

pragma solidity ^0.4.20;

/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0x80ac58cd
interface ERC721 /* is ERC165 */ {
    /// @dev This emits when ownership of any NFT changes by any mechanism.
    ///  This event emits when NFTs are created (`from` == 0) and destroyed
    ///  (`to` == 0). Exception: during contract creation, any number of NFTs
    ///  may be created and assigned without emitting Transfer. At the time of
    ///  any transfer, the approved address for that NFT (if any) is reset to none.
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

    /// @dev This emits when the approved address for an NFT is changed or
    ///  reaffirmed. The zero address indicates there is no approved address.
    ///  When a Transfer event emits, this also indicates that the approved
    ///  address for that NFT (if any) is reset to none.
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

    /// @dev This emits when an operator is enabled or disabled for an owner.
    ///  The operator can manage all NFTs of the owner.
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    /// @notice Count all NFTs assigned to an owner
    /// @dev NFTs assigned to the zero address are considered invalid, and this
    ///  function throws for queries about the zero address.
    /// @param _owner An address for whom to query the balance
    /// @return The number of NFTs owned by `_owner`, possibly zero
    function balanceOf(address _owner) external view returns (uint256);

    /// @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) external view returns (address);

    /// @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,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, bytes data) external payable;

    /// @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 safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @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.
    /// @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) external payable;

    /// @notice Set or reaffirm the approved address for an NFT
    /// @dev The zero address indicates there is no approved address.
    /// @dev 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 _approved, uint256 _tokenId) external payable;

    /// @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 setApprovalForAll(address _operator, bool _approved) external;

    /// @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 getApproved(uint256 _tokenId) external view returns (address);

    /// @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 isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

接口说明:

  • balanceOf(): 返回由_owner 持有的NFTs的数量。
  • ownerOf(): 返回tokenId代币持有者的地址。
  • approve(): 授予地址_to具有_tokenId的控制权,方法成功后需触发Approval 事件。
  • setApprovalForAll(): 授予地址_operator具有所有NFTs的控制权,成功后需触发ApprovalForAll事件。
  • getApproved()、isApprovedForAll(): 用来查询授权。
  • safeTransferFrom():
    转移NFT所有权,一次成功的转移操作必须发起 Transfer 事件。函数的实现需要做一下几种检查:
    1] 调用者msg.sender应该是当前tokenId的所有者或被授权的地址
    2] _from 必须是 _tokenId的所有者
    3] _tokenId 应该是当前合约正在监测的NFTs 中的任何一个
    4] _to 地址不应该为 0,如果_to 是一个合约应该调用其onERC721Received方法, 并且检查其返回值,如果返回值不为bytes4(keccak256("onERC721Received(address,uint256,bytes)"))则抛出异常。
  • transferFrom(): 用来转移NFTs, 方法成功后需触发Transfer事件。调用者自己确认_to地址能正常接收NFT,否则将丢失此NFT。此函数实现时需要检查上面条件的前4条。

3.3 ERC165 标准

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

interface ERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    ///  uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

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

3.4 可选实现接口: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调用,以便在应用层做相应的查询和展示。

3.5 可选实现接口: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。

3.6 补充说明

NTF IDs

NTF ID,即tokenId,在合约中用唯一的uint265进行标识,每个NFT的ID在智能合约的生命周期内不允许改变。推荐的实现方式有:
1. 从0开始,每新加一个NFT,NTF ID加1
2. 使用sha3后uuid 转换为 NTF ID

与ERC-20的兼容性

ERC721标准尽可能遵循 ERC-20 的语义,但由于同质代币与非同质代币之间的根本差异,并不能完全兼容ERC-20。

交易、挖矿、销毁

在实现transter相关接口时除了满足上面的的条件外,我们可以根据需要添加自己的逻辑,如加入黑名单等。
同时挖矿、销毁尽管不是标准的一部分,我们可以根据需要实现。

4. 彩贝社区介绍

彩贝链始于彩贝社区,它致力于为全世界的绘画创作者提供最好用的绘画工具,建立最繁荣活跃的绘画兴趣社区,并通过区块链社区方式,构建全球统一的数字版权交易标准和交易支付工具,跨越各国版权交易法律及支付结算的鸿沟,成为全球商业级的作品发表、存储、检索和版权交易服务,数字艺术作品的创作生态、版权交易及衍生品服务平台。

彩贝DAPP是彩贝链生态第一个落地去中心化应用,它是全球领先的数字绘画社区画吧的国际区块链版本。画吧已有注册用户600万,原创数字绘画资产900万幅,画吧完全自主开发全球领先的数字绘画工具集,每幅数字绘画全过程全平台矢量回放,可实现2D,3D数字资产高效率高质量生成。彩贝DAPP利用彩贝链基础设施,降低版权交易各国差异性和支付方式差异性,大幅提高交易效率,降低交易成本,同时通过彩贝链代币经济激励体系,进一步提升彩贝社区全球参与者活跃度。

彩贝生态链的数字绘画等资产是满足ERC721标准的一种原创数字资产。
彩贝链创新点

5. 参考文档

1) 区块链游戏玩法及攻略:谜恋猫CryptoKitties(以太猫)为例
2) 谜恋猫源码详解:手把手教你开发自己的养成类区块链游戏
3) 迷恋猫官网
4) 剖析非同质化代币ERC721-全面解析ERC721标准
5) 迷恋猫智能合约和源码
6) ERC721接口文档
7)OpenZeppelin实现的ERC721合约参考
8) CryptoKittes(加密猫,谜恋猫)智能合约结构和源码解析

辉哥的技术投资之路
Web note ad 1