第十七課 【ERC721實(shí)踐】迷戀貓從玩耍到開發(fā)


CryptoKitties(中文名:迷戀貓)是一款在以太坊區(qū)塊鏈上的虛擬養(yǎng)貓游戲屏轰,一經(jīng)推出就以病毒式的快速擴(kuò)散杈笔,橫掃整個(gè)以太坊市場(chǎng)野哭。而這款可愛的游戲于2018年 2 月 16 日(農(nóng)歷大年初一)登陸 iOS國(guó)區(qū)挂滓,中文名稱的 “迷戀貓”妆档,皆因 “迷戀” 與 “密鏈” 同音膀懈,亦有 “加密區(qū)塊鏈” 的意思富岳,而且為了慶祝中國(guó)農(nóng)歷新年入篮,所有在節(jié)日期間出生的新貓咪都將會(huì)有中國(guó)的背景故事陈瘦。
迷戀貓是一款由 Axiom Zen 和以太坊智能合作開發(fā)的區(qū)塊鏈寵物養(yǎng)成游戲。在游戲中潮售,玩家使用以太幣(ETH)進(jìn)行電子貓的購(gòu)買痊项、喂食、照料與交配等酥诽,但其最為核心是玩法是將以太坊區(qū)塊鏈中的電子貓進(jìn)行出售线婚。撇開眾人對(duì)虛擬小貓的狂熱不談,畢竟絕對(duì)不只是貓很可愛這種原因盆均,這個(gè)游戲已造成過(guò)大流量塞弊,甚至因流量太大導(dǎo)致游戲困難,讓很多交易耗費(fèi)比原本更長(zhǎng)的時(shí)間,官方甚至為此讓生育費(fèi)漲價(jià)以提供礦工更高的誘因游沿。目前 CryptoKitties 已經(jīng)成為以太坊最流行的智能合約饰抒,數(shù)量約莫是第二名的兩倍。

輝哥系統(tǒng)整合出了ERC721從理論到實(shí)踐的文章诀黍,可按需查看:
(1)第十七課 【ERC721實(shí)踐】迷戀貓從玩耍到開發(fā)
(2)第三十三課 如何創(chuàng)建自己的ERC721非同質(zhì)化資產(chǎn)生物商店袋坑?
(3)第三十四課 采用TRUFFLE框架如何創(chuàng)建自己的ERC721非同質(zhì)化資產(chǎn)生物商店?
(4)第三十五課 如何配置Metadata以便裝飾你的ERC721非同質(zhì)化資產(chǎn)眯勾?

1. 迷戀貓注冊(cè)/玩法攻略

玩迷戀貓游戲枣宫,玩家需要在以太坊區(qū)塊鏈上下載到這款游戲的APP,游戲開始系統(tǒng)會(huì)贈(zèng)送玩家一只喵吃环。剛推出時(shí)是送貓的也颤,現(xiàn)在只有活動(dòng)時(shí)才贈(zèng)送。它讓你沉迷于吸貓郁轻,然后當(dāng)你無(wú)法自拔后翅娶,就會(huì)自愿掏出大把的以太幣去氪金了,滿滿的套路昂梦ā竭沫!

1.1 在谷歌瀏覽器中安裝MetaMask

1.1.1 下載安裝谷歌瀏覽器(Chrome)

百度搜索"谷歌瀏覽器"即可進(jìn)行下載安裝(谷歌瀏覽器可以完美支持第三方插件和擴(kuò)展程序)。

1.1.2 安裝MetaMask錢包插件

MetaMask是一款運(yùn)行穩(wěn)定的以太坊錢包骑篙,我們和其他交易所的區(qū)別之一在于在這里您可以自主管理您的錢包蜕提,并且錢包所有的資產(chǎn)變動(dòng)都會(huì)上區(qū)塊鏈確認(rèn),完全是開放和透明的靶端,而在其他交易所贯溅,您的錢包實(shí)際是平臺(tái)在管理,無(wú)法完全杜絕資金風(fēng)險(xiǎn)以及被限制充幣提幣等行為躲查。
1)國(guó)內(nèi)用戶可使用 MetaMask_v3.9.11.crx下載MetaMask并解壓它浅,然后在擴(kuò)展程序設(shè)置頁(yè)面chrome://extensions/將下載好的文件拖進(jìn)來(lái)即可安裝,安裝后點(diǎn)擊啟用镣煮,即可在瀏覽器右上角看到MetaMask圖標(biāo)姐霍。如下圖:

image

2)翻墻或者國(guó)外的用戶可以點(diǎn)擊 這里在谷歌插件商店進(jìn)行安裝。

image

1.2 創(chuàng)建MetaMask賬號(hào)

1)接下來(lái)典唇,點(diǎn)擊MetaMask圖標(biāo)創(chuàng)建自己的賬戶镊折。
2)對(duì)MetaMask錢包插件進(jìn)行設(shè)置,首先需要鏈接到主網(wǎng)介衔。點(diǎn)擊MetaMask界面的左上角恨胚,選擇Main Ethereum Network

image

1.3 購(gòu)買迷戀貓

打開官方網(wǎng)站(www.cryptokitties.co ),用MetaMask的錢包地址登錄炎咖,然后到市場(chǎng)上用ETH去購(gòu)買貓赃泡,新手登錄將免費(fèi)獲得一只寒波。需要擁有以太幣(ETH),用于養(yǎng)貓的花費(fèi)升熊,沒有的話要先去交易所買點(diǎn)以太幣ETH俄烁。
然后到你的貓咪個(gè)人頁(yè)面去喂養(yǎng)、培育级野、銷售它页屠。每一個(gè)貓咪都可以選擇扮演母親或者父親的角色,和其他異性貓咪組成家庭蓖柔,最后按繁殖按鈕生下小貓咪辰企。如果想和其他人的貓咪繁育,則需要支付其他玩家相應(yīng)的ETH况鸣。生下的小貓咪則可以去市場(chǎng)上交易牢贸。貓咪每多繁殖一次,培育小貓咪所需時(shí)間都會(huì)相應(yīng)增長(zhǎng)懒闷,目前官網(wǎng)上每5分鐘就誕生一個(gè)小貓咪。主人可以將自己的貓咪打扮各種風(fēng)格的花式貓栈幸。由于區(qū)塊鏈?zhǔn)欠稚⒌姆吖溃總€(gè)交易都是通過(guò)多臺(tái)計(jì)算機(jī)分配的,而不是中央服務(wù)器速址,所以玩家支付的ETH相當(dāng)于區(qū)塊鏈網(wǎng)絡(luò)中支持交易或合約的成本玩焰。
當(dāng)你已在瀏覽器安裝MetaMask插件并手握MetaMask賬號(hào)后,擼貓的一切準(zhǔn)備工作已經(jīng)完畢芍锚,登陸迷戀貓(CryptoKitties)官網(wǎng)昔园,準(zhǔn)備選購(gòu)你的第一只貓咪吧。

在以太貓這款游戲中并炮,玩家必須至少擁有一只貓才可進(jìn)行游戲默刚,而獲得貓主要可以通過(guò)以下三種方式:
1.測(cè)試玩家,獲贈(zèng)免費(fèi)的貓
2.普通玩家逃魄,在游戲內(nèi)市場(chǎng)購(gòu)買自己的第一只貓
3.普通玩家荤西,在Twitch、Reddit等平臺(tái)等待主播或其他玩家贈(zèng)送(適合體驗(yàn)游戲)
在這里我們僅介紹第2種伍俘,通過(guò)在市場(chǎng)中購(gòu)買貓的方式開始游玩:

image.png

當(dāng)您已在瀏覽器上安裝并登陸MetaMask錢包邪锌,迷戀貓官網(wǎng)會(huì)自動(dòng)同步您的登陸信息。在此過(guò)程中您可能會(huì)遇到賬號(hào)被鎖定的阻礙癌瘾,這是正趁俜幔現(xiàn)象,輸入密碼就可解鎖妨退。


初始頁(yè)面妇萄,左上為您的個(gè)人信息蜕企,右側(cè)頂部的三個(gè)標(biāo)簽欄依次為:“我的貓咪”(My Kitties)、“市場(chǎng)”(Marketplace)還有“邀請(qǐng)”(Invite)嚣伐。但由于上圖中的賬號(hào)未擁有貓糖赔,所以頁(yè)面中顯示的是市場(chǎng)推薦。
讓我們點(diǎn)進(jìn)“市場(chǎng)”看看具體情況轩端。
玩家可以在市場(chǎng)中交易或給自己的貓配種放典。頁(yè)面中上的四個(gè)標(biāo)簽分別為:“正在售賣”(For Sale)、“提供配種”(Siring)基茵、“零代貓”(Gen 0)以及“全部”(All Kitts)奋构。
顧名思義,通過(guò)“正在售賣”標(biāo)簽玩家可以快速查看可以購(gòu)買的對(duì)象拱层。
“提供配種”標(biāo)簽下顯示的全是可以用來(lái)與自己貓交配的“公貓”對(duì)象弥臼。
“零代貓”的概念比較特殊,在2018年11月前根灯,系統(tǒng)每隔15分鐘都會(huì)產(chǎn)出一批零代貓径缅,它們擁有最短的初始生殖間隔。新零代貓的初始價(jià)格比最近市場(chǎng)中售賣出的五只貓的價(jià)格平均值高50%烙肺。
通過(guò)“全部”標(biāo)簽纳猪,玩家可以查看游戲里的所有貓。無(wú)論是“市場(chǎng)”還是“我的貓咪”頁(yè)面桃笙,玩家都可通過(guò)右側(cè)下拉框簡(jiǎn)單篩選顯示規(guī)則氏堤。規(guī)則大體為“最新一代”、“最初始一代”以及“最便宜”和“最貴”搏明。
購(gòu)買貓時(shí)鼠锈,玩家除要選擇帶有“For sale”(正在售賣)標(biāo)簽的對(duì)象,還需仔細(xì)查該對(duì)象的各項(xiàng)屬性星著。
這里要注意幾點(diǎn)购笆,包括貓的代數(shù),貓的交配間隔以及它的外觀和屬性虚循。
通常來(lái)說(shuō)添诉,零代貓擁有最短的初始交配間隔梧疲,但隨著玩家讓其配種次數(shù)的增多岖是,貓咪的交配間隔會(huì)愈發(fā)延長(zhǎng)轴咱。



以今天創(chuàng)下新交易記錄的4號(hào)貓“Fluffy”為例,它的交配間隔已經(jīng)降至第二檔剪廉,意味著它只能每隔2-5分鐘交配一次娃循。


由玩家培育出的貓咪的交配間隔不會(huì)低于它的父母。目前最慢一檔的交配間隔為一周一次斗蒋。而僅在目前看來(lái)捌斧,交配間隔越短的貓咪在市場(chǎng)上的價(jià)值也就越高笛质。
已有不少玩家把迷戀貓當(dāng)成“生殖農(nóng)場(chǎng)”,所以如果您是初次進(jìn)入游戲捞蚂,一定要觀察購(gòu)買對(duì)象是否處在交配CD妇押,以及它的交配欲望。
除了交配相關(guān)姓迅,貓的外觀敲霍、性狀以及父母和子女可能都是買貓者要考慮在內(nèi)的問(wèn)題。


從眼睛到尾巴再到花色甚至是背景丁存,這些都是能夠直觀觀察到的肩杈。而同過(guò)“Cattributes”一欄,你還可以進(jìn)一步看到更多關(guān)于這只貓咪由基因排列體現(xiàn)出的性狀特征解寝。
題外話扩然,迷戀貓里的貓咪均由256位基因編碼組成,父母的基因在一定程度上會(huì)決定孩子的特征聋伦。零代貓由于是系統(tǒng)生成夫偶,無(wú)父母一欄。
看到了吧觉增,在迷戀貓里買貓的門道非常多兵拢,入坑需謹(jǐn)慎。
在選擇好想要購(gòu)買的貓咪后抑片,點(diǎn)擊“Buy now”按鈕并使用以太幣購(gòu)買貓咪卵佛。
以上就是就是以太坊買貓攻略杨赤。需要注意的是敞斋,游戲中玩家自行配種以及更多涉及市場(chǎng)的操作均需向平臺(tái)提交手續(xù)費(fèi)。所以不僅是想在迷戀貓里賺錢疾牲,就算想要維持持續(xù)運(yùn)作也不是一件容易事植捎。當(dāng)然,如果你手握一大堆以太幣那就簡(jiǎn)單了阳柔。

2. 謎戀貓?jiān)创a詳解

要想快速了解這個(gè)游戲的工作原理焰枢,最好的方法就是直接閱讀源代碼。
CryptoKitties 的源代碼大部分是開源的(也有小部分沒有開源舌剂,主要是是交配基因更新這部分)济锄。CryptoKitties 源碼大約有2000行,本文將主要講解其中相對(duì)重要的部分霍转。

點(diǎn)擊可查看CryptoKitties的智能合約和代碼

CryptoKitties游 戲的源代碼分成了一個(gè)個(gè)的子合約荐绝,而非一個(gè)包含所有邏輯的單一文件。
子合約通過(guò)下述方式繼承主合約:

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

最終應(yīng)用程序指向的合約是 KittyCore 合約避消,這個(gè)合約繼承了前面所有合約的屬性和方法低滩。接下來(lái)召夹,讓我們一個(gè)一個(gè)地來(lái)分析這些合約。

2.1 KittyAccessControl:誰(shuí)控制合約恕沫?

該合約負(fù)責(zé)管理各種不同的地址监憎,同時(shí)定義了各種僅限于特定角色執(zhí)行的限制性操作,這些角色被命名為“CEO”婶溯、“CFO”和“COO”鲸阔。
KittyAccessControl 合約主要功能是管理其他合約,所以它不涉及游戲的具體邏輯爬虱。該合約定義了以太坊地址“CEO”隶债、“CFO”和“COO”的使用方法,它們對(duì)該合約有著特殊的所有權(quán)以及控制權(quán)限跑筝。
KittyAccessControl 合約還定義了一些函數(shù)修飾符死讹,如 onlyCEO (該函數(shù)只有“CEO”才能執(zhí)行),同時(shí)該合約還定義了一些暫停/恢復(fù)合約的方法以及提現(xiàn)方法曲梗。

 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() 函數(shù)主要是方便開發(fā)人員進(jìn)行版本更新赞警,以防那些預(yù)見不到的 bug。但我同事 Luke 指出虏两,這個(gè)方法可以讓開發(fā)人員有權(quán)完全凍結(jié)合約愧旦,令所有人都無(wú)法轉(zhuǎn)讓、出售或繁殖他們的貓咪定罢!當(dāng)然笤虫,開發(fā)人員并不想這么做。但真正有趣的地方確實(shí)祖凫,大多數(shù)人僅僅因?yàn)樵撚螒蜻\(yùn)行在以太坊上琼蚯,就會(huì)以為它是一款完全是去中心化的游戲。
我們接著來(lái)看其他合約惠况。

2.1 KittyBase:數(shù)字貓是什么貓遭庶?

KittyBase 合約是我們定義最底層的代碼的地方,這些代碼會(huì)用到整個(gè)游戲的所有核心功能上稠屠,包括數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)峦睡、相關(guān)常量與數(shù)據(jù)類型,以及管理這些數(shù)據(jù)的內(nèi)部函數(shù)权埠。
KittyBase 合約定義了應(yīng)用程序的很多核心數(shù)據(jù)榨了。首先它將Kitty定義為結(jié)構(gòu)體類型:

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

從這里可以看到,一只貓咪的本質(zhì)就是一長(zhǎng)串的無(wú)符號(hào)整數(shù)攘蔽。

接下來(lái)一一介紹貓咪的每個(gè)屬性的具體參數(shù):

genes — 是256位的整數(shù)龙屉,主要作為貓的遺傳密碼,是決定貓外貌的核心數(shù)據(jù)秩彤;
birthTime — 貓出生時(shí)所打包的區(qū)塊的時(shí)間戳叔扼;
cooldownEndBlock — 貓可以再次繁殖的最小時(shí)間事哭;
matronId&sireId — 貓母親的ID號(hào)和貓父親的ID號(hào);
siringWithId — 如果貓懷孕了瓜富,則設(shè)置為父親的ID鳍咱,否則為零;
cooldownIndex — 貓繁殖所需的冷卻時(shí)間(貓需要等待多久才能繁殖)与柑;
generation — 貓的“世代號(hào)”(指明這是第幾代貓)谤辜。合約創(chuàng)造的第一只貓是0代,新一代貓的“世代號(hào)”是其父母中較大的一代再加1价捧。

需要注意的是丑念,在 CryptoKitties 游戲中,貓是無(wú)性別之分的结蟋,任意2只貓都可以一起繁殖脯倚。
KittyBase 合約聲明了一個(gè)“貓”結(jié)構(gòu)的數(shù)組,如下所示:

Kitty[] kitties;

該數(shù)組包含了所有貓咪的數(shù)據(jù)嵌屎,所以它就像一個(gè)貓咪數(shù)據(jù)庫(kù)一般推正。無(wú)論什么時(shí)候生成一個(gè)新的貓咪,它都會(huì)被添加到該數(shù)組內(nèi)宝惰,數(shù)組的索引就是貓咪的 ID 號(hào)植榕。下圖顯示的是創(chuàng)世貓,其 ID 號(hào)為“1”尼夺。

該合約中還包含有從貓咪 ID 號(hào)到其所有者地址的一個(gè)映射尊残,主要用于追蹤貓咪的所有者。

mapping (uint256 => address) public kittyIndexToOwner;

合約中還定義了其他一些映射淤堵,但此處不再深究每一個(gè)細(xì)節(jié)寝衫。
當(dāng)貓咪的所有者從一個(gè)人轉(zhuǎn)移到另外一個(gè)人時(shí),kittyIndexToOwner 映射就會(huì)更新粘勒,從而識(shí)別新的所有者竞端。

 /// @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);
}

現(xiàn)在我們來(lái)看看當(dāng)一個(gè)新的貓咪生成時(shí)都發(fā)生了些什么:

 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;
    }

從上面這段代碼中可以看出屎即,這個(gè)函數(shù)傳遞了母親和父親的 ID 號(hào)庙睡、貓咪的世代號(hào)、256位遺傳密碼和所有者的地址技俐。然后創(chuàng)建貓咪乘陪,并將其加入到 Kitty 數(shù)組中,最后調(diào)用 _transfer() 將其分配給新的所有者雕擂。
是不是很酷啡邑!現(xiàn)在我們已經(jīng)知道 CryptoKitties 游戲如何將一只貓咪定義為一種數(shù)據(jù)類型,如何將所有貓咪都存儲(chǔ)在區(qū)塊鏈中井赌,以及如何跟蹤這些貓咪的所有者谤逼。

2.3 KittyOwnership:作為通證的貓咪

CryptoKitties 游戲遵循 ERC721 通證標(biāo)準(zhǔn)贵扰,這是一種不可替代通證,它可以很好地追蹤數(shù)字收藏品的所有權(quán)流部,比如數(shù)字撲克牌或 MMORPG(大型多人在線角色扮演游戲)中的稀有物品戚绕。
關(guān)于可互換性的說(shuō)明:Ether 是可互換的通證,因?yàn)槿我?個(gè) ETH 都與其他5個(gè) ETH 一樣有著同樣的價(jià)值枝冀。但是像 CryptoKitties 這樣的通證是不可替代通證舞丛,每只貓咪的外觀都各不相同,它們不能生而相同果漾,所以無(wú)法相互交換球切。
你可以從合約的定義中看出,KittyOwnership 繼承的是 ERC721 合約绒障。

contract KittyOwnership is KittyBase, ERC721 {

而所有是 ERC721 通證都遵循同樣的標(biāo)準(zhǔn)吨凑。下一章節(jié)分析ERC721標(biāo)準(zhǔn)。

3. 非同質(zhì)化代幣ERC721分析

3.1 ERC721是什么

和ERC20一樣户辱,ERC721同樣是一個(gè)代幣標(biāo)準(zhǔn)怀骤,ERC721官方簡(jiǎn)要解釋是Non-Fungible Tokens,簡(jiǎn)寫為NFTs焕妙,多翻譯為非同質(zhì)代幣蒋伦。
ERC721 是由Dieter Shirley 在2017年9月提出。Dieter Shirley 正是謎戀貓CryptoKitties背后的公司Axiom Zen的技術(shù)總監(jiān)焚鹊。因此謎戀貓也是第一個(gè)實(shí)現(xiàn)了ERC721 標(biāo)準(zhǔn)的去中心化應(yīng)用痕届。ERC721號(hào)提議已經(jīng)被以太坊作為標(biāo)準(zhǔn)接受,但該標(biāo)準(zhǔn)仍處于終結(jié)Final階段末患。本文介紹的ERC721標(biāo)準(zhǔn)基于最新(2018/03/23)官方提議研叫。

那怎么理解非同質(zhì)代幣呢?
非同質(zhì)代表獨(dú)一無(wú)二,謎戀貓為例璧针,每只貓都被賦予擁有基因嚷炉,是獨(dú)一無(wú)二的(一只貓就是一個(gè)NFTs),貓之間是不能置換的探橱。這種獨(dú)特性使得某些稀有貓具有收藏價(jià)值申屹,也因此受到追捧。
ERC20代幣是可置換的隧膏,且可細(xì)分為N份(1 = 10 * 0.1), 而ERC721的Token最小的單位為1哗讥,無(wú)法再分割。

如果同一個(gè)集合的兩個(gè)物品具有不同的特征胞枕,這兩個(gè)物品是非同質(zhì)的杆煞,而同質(zhì)是某個(gè)部分或數(shù)量可以被另一個(gè)同等部分或數(shù)量所代替。

非同質(zhì)性其實(shí)廣泛存在于我們的生活中,如圖書館的每一本决乎,寵物商店的每一只寵物队询,歌手所演唱的歌曲,花店里不同的花等等构诚,因此ERC721合約必定有廣泛的應(yīng)用場(chǎng)景娘摔。通過(guò)這樣一個(gè)標(biāo)準(zhǔn),也可建立跨功能的NFTs管理和銷售平臺(tái)(就像有支持ERC20的交易所和錢包一樣)唤反,使生態(tài)更加強(qiáng)大凳寺。

3.2 ERC721標(biāo)準(zhǔn)

ERC721最為一個(gè)合約標(biāo)準(zhǔn),提供了在實(shí)現(xiàn)ERC721代幣時(shí)必須要遵守的協(xié)議彤侍,要求每個(gè)ERC721標(biāo)準(zhǔn)合約需要實(shí)現(xiàn)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);
}

接口說(shuō)明:

  • balanceOf(): 返回由_owner 持有的NFTs的數(shù)量。
  • ownerOf(): 返回tokenId代幣持有者的地址盏阶。
  • approve(): 授予地址_to具有_tokenId的控制權(quán)晒奕,方法成功后需觸發(fā)Approval 事件。
  • setApprovalForAll(): 授予地址_operator具有所有NFTs的控制權(quán)名斟,成功后需觸發(fā)ApprovalForAll事件脑慧。
  • getApproved()、isApprovedForAll(): 用來(lái)查詢授權(quán)砰盐。
  • safeTransferFrom():
    轉(zhuǎn)移NFT所有權(quán)闷袒,一次成功的轉(zhuǎn)移操作必須發(fā)起 Transfer 事件。函數(shù)的實(shí)現(xiàn)需要做一下幾種檢查:
    1] 調(diào)用者msg.sender應(yīng)該是當(dāng)前tokenId的所有者或被授權(quán)的地址
    2] _from 必須是 _tokenId的所有者
    3] _tokenId 應(yīng)該是當(dāng)前合約正在監(jiān)測(cè)的NFTs 中的任何一個(gè)
    4] _to 地址不應(yīng)該為 0岩梳,如果_to 是一個(gè)合約應(yīng)該調(diào)用其onERC721Received方法, 并且檢查其返回值囊骤,如果返回值不為bytes4(keccak256("onERC721Received(address,uint256,bytes)"))則拋出異常。
  • transferFrom(): 用來(lái)轉(zhuǎn)移NFTs, 方法成功后需觸發(fā)Transfer事件冀值。調(diào)用者自己確認(rèn)_to地址能正常接收NFT也物,否則將丟失此NFT。此函數(shù)實(shí)現(xiàn)時(shí)需要檢查上面條件的前4條列疗。

3.3 ERC165 標(biāo)準(zhǔn)

ERC721標(biāo)準(zhǔn)同時(shí)要求必須符合ERC165標(biāo)準(zhǔn) 滑蚯,其接口如下:

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同樣是一個(gè)合約標(biāo)準(zhǔn),這個(gè)標(biāo)準(zhǔn)要求合約提供其實(shí)現(xiàn)了哪些接口抵栈,這樣再與合約進(jìn)行交互的時(shí)候可以先調(diào)用此接口進(jìn)行查詢告材。
interfaceID為函數(shù)選擇器,計(jì)算方式有兩種竭讳,如:bytes4(keccak256('supportsInterface(bytes4)'));ERC165.supportsInterface.selector创葡,多個(gè)函數(shù)的接口ID為函數(shù)選擇器的異或值浙踢。

3.4 可選實(shí)現(xiàn)接口:ERC721Metadata

ERC721Metadata 接口用于提供合約的元數(shù)據(jù):name , symbol 及 URI(NFT所對(duì)應(yīng)的資源)绢慢。
其接口定義如下:

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);
}

接口說(shuō)明:

  • name(): 返回合約名字,盡管是可選,但強(qiáng)烈建議實(shí)現(xiàn)胰舆,即便是返回空字符串骚露。
  • symbol(): 返回合約代幣符號(hào),盡管是可選缚窿,但強(qiáng)烈建議實(shí)現(xiàn)棘幸,即便是返回空字符串。
  • tokenURI(): 返回_tokenId所對(duì)應(yīng)的外部資源文件的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調(diào)用蹋嵌,以便在應(yīng)用層做相應(yīng)的查詢和展示。

3.5 可選實(shí)現(xiàn)接口:ERC721Enumerable

ERC721Enumerable的主要目的是提高合約中NTF的可訪問(wèn)性葫隙,其接口定義如下:

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);
}

接口說(shuō)明:

  • totalSupply(): 返回NFT總量
  • tokenByIndex(): 通過(guò)索引返回對(duì)應(yīng)的tokenId栽烂。
  • tokenOfOwnerByIndex(): 所有者可以一次擁有多個(gè)的NFT, 此函數(shù)返回_owner擁有的NFT列表中對(duì)應(yīng)索引的tokenId。

3.6 補(bǔ)充說(shuō)明

NTF IDs

NTF ID恋脚,即tokenId腺办,在合約中用唯一的uint265進(jìn)行標(biāo)識(shí),每個(gè)NFT的ID在智能合約的生命周期內(nèi)不允許改變糟描。推薦的實(shí)現(xiàn)方式有:
1. 從0開始怀喉,每新加一個(gè)NFT,NTF ID加1
2. 使用sha3后uuid 轉(zhuǎn)換為 NTF ID

與ERC-20的兼容性

ERC721標(biāo)準(zhǔn)盡可能遵循 ERC-20 的語(yǔ)義船响,但由于同質(zhì)代幣與非同質(zhì)代幣之間的根本差異磺送,并不能完全兼容ERC-20。

交易灿意、挖礦估灿、銷毀

在實(shí)現(xiàn)transter相關(guān)接口時(shí)除了滿足上面的的條件外,我們可以根據(jù)需要添加自己的邏輯缤剧,如加入黑名單等馅袁。
同時(shí)挖礦、銷毀盡管不是標(biāo)準(zhǔn)的一部分荒辕,我們可以根據(jù)需要實(shí)現(xiàn)汗销。

4. 彩貝社區(qū)介紹

彩貝鏈?zhǔn)加诓守惿鐓^(qū),它致力于為全世界的繪畫創(chuàng)作者提供最好用的繪畫工具抵窒,建立最繁榮活躍的繪畫興趣社區(qū)弛针,并通過(guò)區(qū)塊鏈社區(qū)方式,構(gòu)建全球統(tǒng)一的數(shù)字版權(quán)交易標(biāo)準(zhǔn)和交易支付工具李皇,跨越各國(guó)版權(quán)交易法律及支付結(jié)算的鴻溝削茁,成為全球商業(yè)級(jí)的作品發(fā)表、存儲(chǔ)、檢索和版權(quán)交易服務(wù)茧跋,數(shù)字藝術(shù)作品的創(chuàng)作生態(tài)慰丛、版權(quán)交易及衍生品服務(wù)平臺(tái)。

彩貝DAPP是彩貝鏈生態(tài)第一個(gè)落地去中心化應(yīng)用瘾杭,它是全球領(lǐng)先的數(shù)字繪畫社區(qū)畫吧的國(guó)際區(qū)塊鏈版本诅病。畫吧已有注冊(cè)用戶600萬(wàn),原創(chuàng)數(shù)字繪畫資產(chǎn)900萬(wàn)幅粥烁,畫吧完全自主開發(fā)全球領(lǐng)先的數(shù)字繪畫工具集贤笆,每幅數(shù)字繪畫全過(guò)程全平臺(tái)矢量回放,可實(shí)現(xiàn)2D讨阻,3D數(shù)字資產(chǎn)高效率高質(zhì)量生成苏潜。彩貝DAPP利用彩貝鏈基礎(chǔ)設(shè)施,降低版權(quán)交易各國(guó)差異性和支付方式差異性变勇,大幅提高交易效率恤左,降低交易成本,同時(shí)通過(guò)彩貝鏈代幣經(jīng)濟(jì)激勵(lì)體系搀绣,進(jìn)一步提升彩貝社區(qū)全球參與者活躍度飞袋。

彩貝生態(tài)鏈的數(shù)字繪畫等資產(chǎn)是滿足ERC721標(biāo)準(zhǔn)的一種原創(chuàng)數(shù)字資產(chǎn)。
彩貝鏈創(chuàng)新點(diǎn)

5. 參考文檔

1) 區(qū)塊鏈游戲玩法及攻略:謎戀貓CryptoKitties(以太貓)為例
2) 謎戀貓?jiān)创a詳解:手把手教你開發(fā)自己的養(yǎng)成類區(qū)塊鏈游戲
3) 迷戀貓官網(wǎng)
4) 剖析非同質(zhì)化代幣ERC721-全面解析ERC721標(biāo)準(zhǔn)
5) 迷戀貓智能合約和源碼
6) ERC721接口文檔
7)OpenZeppelin實(shí)現(xiàn)的ERC721合約參考
8) CryptoKittes(加密貓链患,謎戀貓)智能合約結(jié)構(gòu)和源碼解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末巧鸭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子麻捻,更是在濱河造成了極大的恐慌纲仍,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贸毕,死亡現(xiàn)場(chǎng)離奇詭異郑叠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)明棍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門乡革,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人摊腋,你說(shuō)我怎么就攤上這事沸版。” “怎么了兴蒸?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵邦邦,是天一觀的道長(zhǎng)洞渤。 經(jīng)常有香客問(wèn)我昼弟,道長(zhǎng),這世上最難降的妖魔是什么笑撞? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮区宇,結(jié)果婚禮上娃殖,老公的妹妹穿的比我還像新娘值戳。我一直安慰自己议谷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布堕虹。 她就那樣靜靜地躺著卧晓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赴捞。 梳的紋絲不亂的頭發(fā)上逼裆,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音赦政,去河邊找鬼胜宇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛恢着,可吹牛的內(nèi)容都是我干的桐愉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼掰派,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼从诲!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起靡羡,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤系洛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后略步,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體描扯,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年趟薄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荆烈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竟趾,死狀恐怖憔购,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岔帽,我是刑警寧澤玫鸟,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站犀勒,受9級(jí)特大地震影響屎飘,放射性物質(zhì)發(fā)生泄漏妥曲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一钦购、第九天 我趴在偏房一處隱蔽的房頂上張望檐盟。 院中可真熱鬧,春花似錦押桃、人聲如沸葵萎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)羡忘。三九已至,卻和暖如春磕昼,著一層夾襖步出監(jiān)牢的瞬間卷雕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工票从, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漫雕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓峰鄙,卻偏偏與公主長(zhǎng)得像浸间,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子先馆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容