NFT 这么火,你知道 ERC721 么

如果对币圈稍微有些关注的人,这几个月一定被 NFT 刷屏了。面对一张张卖出天价的 NFT,绝大多数人都无法理解,其实对于 NFT,贵的不是那张图,而是大家的共识。

这篇文章暂时不讨论 NFT 的价值问题,而是来起底一下 NFT 的技术支撑。

目前绝大多数的 NFT 资产都在以太坊发行,占了主流资产的 90% 以上。目前 NFT 的发行标准最主流的是 ERC721,这已经成为事实上的标准。

ERC 是什么

在开始说 ERC721 之前,需要先说明一下 ERC,以太坊还在不断的发展,包括协议和各类标准,就像互联网行业的 RFC 一样,每个人都可以都对以太坊的发展提出自己的意见。每个意见都被称之为 EIP(Ethereum Improvement Proposals),直接在这个 GitHub 仓库(https://github.com/ethereum/EIPs)中提交。

EIP 的完整流程如下:

  • Draft:由作者提交的建议,还在做主要的修改
  • Review:建议已经基本完成,可以进行 EIP 评审
  • Last Call:评审完成,这个建议有可能成为最终版
  • Accepted:这个建议在等待以太坊开发者的实现或者部署
  • Final:EIP 会成为一个以太坊的标准

大多数的 EIP 都会止步于 Review 阶段。

ERC(Ethereum Request for Comment) 用于记录以太坊上的各种开发标准和协议,部分 EIP 会成为 ERC。 ERC 都是 EIP,反之则不对。

ERC 721 也是通过这个流程提出来的。

ERC 721 的全称是非同质化代币标准(Non-Fungible Token Standard)。以太币和在以太坊网络上流通的一些代币称之为同质化代币。这些同质化代币发行的标准是 ERC 20 标准。如果代币不实现这些标准,那么就无法在以太坊网络中流通。

从流通的角度来说,ERC 721 和 ERC 20 的实现都是以太坊网络中的代币。它们最大的区别在于,ERC 721 的每一个代币都是独一无二的,有着自己的属性,相互之间是不等价的。而 ERC 20 的每一枚代币都是相同的,是等价的。

ERC 721 协议详解

ERC 721 其实是定义了一系列的接口,如果写过 Java 的人会发现,这个接口的形式与 Java 的非常类似。下面接口定义使用的以太坊的智能合约语言 Solidity:

pragma solidity ^0.4.20;

interface ERC721 /* is ERC165 */ {
    
    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);

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

interface ERC165 {
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

interface ERC721TokenReceiver {
  
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
}

interface ERC721Metadata /* is ERC721 */ {
   
    function name() external view returns (string _name);

    function symbol() external view returns (string _symbol);

    function tokenURI(uint256 _tokenId) external view returns (string);
}

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

balanceOf 方法用来判断一个地址下有多少个 NFT,ownerOf 方法用来判断一个 NFT 是不是属于一个地址。transferFrom 和 safeTransferFrom 都是用于 NFT 的转账,但是 safeTransferFrom 在把 NFT 转到零地址时会报错。零地址就是以太坊中的黑洞,任何转入其中的资产都无法取出。

approve 类的地址就是把自己的 NFT 托管给其他管理,这点要注意,千万随意在陌生的网站上执行力 approve 操作,特别是 setApprovalForAll 方法,否则自己的 NFT 别人就尅随意操控了。

如果要你要发行自己的 NFT,使用 solidity 实现之后,发布到以太坊上,就发行成功了。在 ERC 721 中,推荐把上面的所有接口都实现。

在 ERC 721 中,基本包含了 ERC 20 的所有的接口,非同质化代币虽然是独一无二的,但也需要能够转账等代币的基础特性。

相比于 ERC 20,ERC 721 最大的不同是 有了 ERC721Metadata 这个接口,这个接口可以用来标识每一个非同质化代币的属性,也就是 NFT 的元数据。每个非同质化代币的具体属性都通过 tokenURI 这个方法来返回。我们在 OpenSea 上看到的那些 NFT,都是通过调用这个方法获取 NFT 的详情。

实现 ERC721 时,必须实现 ERC165 协议,这个用来检测当前的合约的是否实现了某个接口,在以太坊中,每一个 interface 都有自己的 interfaceId,比如 ERC165 的是 0x01ffc9a7,ERC 721 的是 0x80ac58cd,ERC721Metadata 的是 0x780e9d63,ERC721Enumerable 的是 0x5b5e139f。

ERC721TokenReceiver 用于在调用 transfer 方法之后的回调,如果传的值对不上,就会导致这次 transfer 失败。

ERC721Enumerable 接口中则实现了 NFT 的一些不变的属性,比如总供应量,通过代币的序号来获取 NFT,列举某个地址下的所有 NFT。

ERC 721 实例

一起来看一下 Meebits 的智能合约,合约的地址可以在 https://etherscan.io/address/0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7#code 这里看到。这就是以太坊上应用的神奇之处,所有的代码都是公开的。

Meebits 是 Larva Labs 发行的一组 NFT,总数2 万个。

代码的总行数只有 679 行,逻辑都还是比较简单的。比如我们上面说到标识 NFT 元数据的 tokenURI 方法,Meebits 是这样实现的:

function tokenURI(uint256 _tokenId) external view validNFToken(_tokenId) returns (string memory) {
    return string(abi.encodePacked("https://meebits.larvalabs.com/meebit/", toString(_tokenId)));
}

实际上是指向了另外一个地址,我我们在这个 url 后面随机输入一个编号,就可以得到一个 Meebits 的元数据:

// https://meebits.larvalabs.com/meebit/1
{
  "name": "Meebit #1",
  "description": "Meebit #1",
  "image": "http://meebits.larvalabs.com/meebitimages/characterimage?index\u003d1\u0026type\u003dfull\u0026imageType\u003djpg",
  "attributes": [
    {
      "trait_type": "Type",
      "value": "Human"
    },
    {
      "trait_type": "Hair Style",
      "value": "Bald"
    },
    {
      "trait_type": "Hat",
      "value": "Backwards Cap"
    },
    {
      "trait_type": "Hat Color",
      "value": "Gray"
    },
    {
      "trait_type": "Shirt",
      "value": "Skull Tee"
    },
    {
      "trait_type": "Overshirt",
      "value": "Athletic Jacket"
    },
    {
      "trait_type": "Overshirt Color",
      "value": "Red"
    },
    {
      "trait_type": "Pants",
      "value": "Cargo Pants"
    },
    {
      "trait_type": "Pants Color",
      "value": "Camo"
    },
    {
      "trait_type": "Shoes",
      "value": "Workboots"
    }
  ]
}

这里我们重点来看几个上面的没有讲到的地方,上面的 ERC 721 中的方法是我们全都都要实现的,但是只实现上面的那些代码却不够,因为在一个 NFT 项目中,发行出来之后, NFT 是要拿出去售卖的,而不是靠 transfer 来手动转出。meebits 公开售卖的方法如下:

function mint() external payable reentrancyGuard returns (uint) {
    require(publicSale, "Sale not started.");
    require(!marketPaused);
    require(numSales < SALE_LIMIT, "Sale limit reached.");
    uint salePrice = getPrice();
    require(msg.value >= salePrice, "Insufficient funds to purchase.");
    if (msg.value > salePrice) {
        msg.sender.transfer(msg.value.sub(salePrice));
    }
    beneficiary.transfer(salePrice);
    numSales++;
    return _mint(msg.sender, 0);
}

在这个方法中,限制了开售的时间和每次购买的个数,如果还未开售,或者超过了限购次数就会购买失败。当然,如果你账户中的钱不够,也会购买失败。购买收到的钱都会转入到当前的合约账户下。

售卖 NFT 的钱自然不能永远放在合约账户中,而是要提出来。所以还需要一个提款的方法:

function withdraw(uint amount) external reentrancyGuard {
    require(amount <= ethBalance[msg.sender]);
    ethBalance[msg.sender] = ethBalance[msg.sender].sub(amount);
    (bool success, ) = msg.sender.call{value:amount}("");
    require(success);
    emit Withdraw(msg.sender, amount);
}

这个方法也很简单,也许有人会有疑问,这个方法谁都可以调用,那岂不是谁都可以来把钱提走了其实并不是,如果是当前合约之外的账号过来提钱,那么都只能提到 0,因为 ethBalance 数组中并没有其他账号的钱,所以只能是当前合约账号才能把钱提出来,这点设计也很巧妙。

ERC 721 不是终点

ERC 721 虽然现在很受欢迎,但满足不了所有的场景。比如在游戏场景中,很多的装备一次可能会发行多个,多达几千种装备类型,如果为每种装备发行一个 NFT,那就要发行几千个合约,这个代价很大,也会对以太坊的资源造成很大的浪费。

ERC 1155 的目标就是解决这个问题,可以在一个合约中定义多种 NFT,也可以为一种 NFT 定义多个数量,后续我们再详细展开 ERC 1155。

文 / Rayjun

[1] https://eips.ethereum.org/EIPS/eip-20

[2] https://eips.ethereum.org/EIPS/eip-721

[3] https://eips.ethereum.org/EIPS/eip-1155

[4]https://eips.ethereum.org/EIPS/eip-165

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容