原文链接:NFT 及 OpenSea 交易背后的技术分享

作者:Xing

Web3Caff 经作者授权发布

这段时间 NFT 市场火爆,本来准备与大家分享的去中心化存储项目改为了这一篇关于 NFT 及 OpenSea 的学习分享了。

最近购入了 NFT 的各位可以看看你花大价钱购买的 NFT 的背后究竟是个什么东西,OpenSea 是如何实现对 NFT 进行买卖交易的,这篇文章可以作为一个入门了解。也许这篇文章还是你成为币圈所谓「科学家」的启蒙教程。

从本人第一次购买 NFT 的故事讲起

鄙人在今年 1 月 3 日的时候,从推特上看见有消息说周杰伦发行了他的 NFT PhantaBear,所以本着粉丝的心态去了解一下。当时发现小熊的地板价只有 0.4ETH,所以立即花了 2ETH 购入 4 只小熊。(后来被 @BTCdayu 传授经验应该买稀有属性的,所以之后又购入了两只稀有小熊)

之后两天鄙人在推特上看见很多人说 PhantaBear 因为周董发表申明切割,所以项目不值得投资,故本人在推特上发表了自己的观点力挺 PhantaBear,同时公开打赌 PhantaBear 一年之后如果年化收益不到 300% 的话(2E,打赌时小熊地板价在 0.6E 多点),愿意赠送一只小熊。结果没想到才过了两三天我自己打了自己的脸,关于我的观点与公开赌注大家可以参考这条推特

总的来说就是在用户心智中能够占据第一的项目,都是比较好的标的。比特币是整个区块链行业的开创者,所以是最值得投资的标的;以太坊占据了拥有智能合约功能的第二代区块链中的第一名,所以也是值得投资的(虽然我也没有持仓);CryptoPunks 是第一个 NFT 项目,所以也很值得投资。(18 年的时候有个加密猫游戏,我印象中那个好像是 NFT 始祖,但貌似现在没人提了?)

而 PhantaBear 是华人圈的第一个 NFT 项目,同时又是伴随我成长的天王带货的,所以无论是理性上还是感性上都必须拿下。

很多时候自己在 crypto 行业中的投资,更看中的是这个项目背后的故事及其意义,而一些 utility token 才会去分析是否有用,是否解决了行业的痛点或者问题。

自购入了人生中的第一个 NFT 之后,上周整周时间都在刷推特、混小熊的 discord(修炼升级、偶尔会有明星聊天)、刷 floor price,把原本安排写作的时间都浪费掉了。

所以这周开始摒弃各种刷 social network,学习研究 OpenSea 平台上交易 NFT 的技术细节并记录分享,毕竟热闹繁华过后还是需要积累沉淀一些东西。

这篇文章鄙人会先讲一讲 NFT 的合约(ERC721)以及 NFT 在 OpenSea 上是如何实现交易的,这两部分的内容比较偏技术,希望可以耐心看完。

前面废话太多,下面正式开始。

NFT 与 ERC721

NFT 的名词定义就不说了,网上有很多资料大家可以自己搜索。

对于 NFT 来说,实际上就是 Ethereum 协会定义的一个规范,也就是 ERC721,其作用是与 Fungible Token 的 ERC20 规范一样,通过这个规范来统一接口,使得在 Token 或者 NFT 上可以衍生出各种各样的 DAPP 生态,比如各种 Swap、借贷等等各类 DAPP。

上面说的可能有点抽象,打个广告举个例子:鄙人为了学习写合约,练手撸了一个去中心化的红包 DAPP,实际上就是把微信红包这个产品挪到了区块链上,不同的是红包 DAPP 塞进红包里的是各种 Token。这个红包 DAPP 之所以支持所有 Token,正是因为适配了所有 Token 都遵循的 ERC20 规范。

NFT 的 ERC721 规范是从 ERC20 衍生出来了,有很多相同方法,其下规范方法如下:

// 查詢 NFT 中某個 owner 擁有的數量 balanceOf(owner) // 查詢 NFT 中某個編號屬於的是哪個人,例如查詢某個編號的猴屬於哪個 ownerownerOf(tokenId) // 返回 NFT 的名字 name() // 返回 NFT 的符號 symbol() // NFT 總發行量 totalSupply() // 返回某個 NFT 的 URI,這個 URI 就是這個 NFT 的一切描述信息 tokenURI(tokenId) // 按 index 序號返回該 owner 的所有持有 NFT 的編號 tokenOfOwnerByIndex(owner, index) // 按 index 序號返回 NFT 的編號 tokenByIndex(index) // 允許 to 這個地址可以轉移他的 tokenId 編號的 NFT(攸關你的 NFT 安全!)approve(to, tokenId) // 查詢 tokenId 編號的 NFT 授權給了誰(查詢誰可以轉走你的 NFT!)getApproved(tokenId) // 授權或者取消授權 operator 這個地址轉移你這一 Collection 下的所有 NFT(攸關你的 NFT 安全!)setApprovalForAll(operator, approved) // 查詢某個 operator 是否有權轉移某個 owner 的這一 Collection 中的所有 NFT(查詢某個地址是否可以轉走你這個 Collection 的所有 NFT!)isApprovedForAll(owner, operator) // 將 from 這個地址的 tokenID 編號的 NFT 轉給 to 這個地址(需要授權才行)transferFrom(from, to, tokenId)

以上就是几个主要的 ERC721 规范的方法,虽然写了注释,但是我猜测大家还是一脸懵。

所以下面给大家通过例子来说明,从 NFT 的起源 Mint 开始。

Mint 铸造

下面这段代码就是 OpenZeppelin 上一个简单的如何铸造 NFT 的代码:

// 鑄造 NFT 的方法,只需要傳入鑄造給誰,再加上這個 NFT 的 tokenURI 即可 function awardItem(address player, string memory tokenURI)    public    returns (uint256){    _tokenIds.increment();    uint256 newTokenId = _tokenIds.current();     // 將這個新的 tokenId 與 player 綁定    _mint(player, newTokenId);     // 將 tokenURI 與這個 newTokenId 綁定    _setTokenURI(newTokenId, tokenURI);     return newItemId;}

铸造 NFT 实际上就是往 NFT 的合约里写入了两个信息:

  • tokenId 及其 owner
  • tokenId 及其 tokenURI

有人说 NFT 又有图片,又有各种属性,怎么这么简单了就铸造出来了呢?

没错,就这么简单!

接下来我们用 CryptoApes 这个 NFT 举例给大家看看我们大家看到的各种眼花缭乱的 NFT 是怎么展示出来的?

Play with NFT contract

我们先来看看如何通过简单的几行代码查询 CryptoApes NFT 的基本信息:

截图中可以看到,通过向 CryptoApes 合约调用上述 ERC721 规范的方法 name(),symbol(),totalSupply(),就能拿到这个 NFT 的名称、符号和总数分别是:CryptoApes,CRAP,6969。

CryptoApes 的合约地址以及 NFT 编号可以在 OpenSea 中的 URL 拿到,见下面截图:

可以看到 OpenSea 的 NFT 链接 https://opensea.io/assets/0x29714cafe792ef8b8c649451d13c89e21a0d7f5b/24,assets 后的第一个地址就是该 NFT Collection 的合约地址,合约地址后的数字就是该 NFT 编号

下面我们还是通过几行简单代码调用合约,查询一下这只编号为 24 的 CryptoApes 的信息:

可以看到我们通过 ERC721 规范中的 ownerOf()方法,就能查到这只 CryptoApes 的拥有者(没错,就是本人)。

另外通过 balanceOf()方法就能查到本人总共拥有了 4 只 CryptoApes。

值得注意的是截图中的 tokenURI()方法,通过它就能获取这个 24 号 NFT 的 tokenURI。这个 tokenURI 非常重要!因为里面存储了关于这只猴的所有描述信息(Metadata)。

24 号猴的 tokenURI 是:ipfs://QmWGAFtzyzB6A6gYMnb6838hysHuT2rcV8B98Gmj4T4pyY/24.json,说明是存储在 IPFS 这个分布式存储上的 json 文件,我们继续通过这个 IPFS 的网关地址https://ipfs.io/ipfs/QmWGAFtzyzB6A6gYMnb6838hysHuT2rcV8B98Gmj4T4pyY/24.json)访问获取里面的内容:

上面截图中就是这只 24 号猴的所有信息了,包括名字、图片、描述、编号、创建时间、还有背景颜色、毛颜色、嘴巴衣服等各种各样的属性。(大家点开大图就能看见)

其中的图片信息又指向了另外一个 IPFS 地址:ipfs://QmZFnUm3bjSyEPrvxEa3fR9eUxnkfQeLmPTzDhAmCWtbMZ/24.png,通过这个地址https://ipfs.io/ipfs/QmZFnUm3bjSyEPrvxEa3fR9eUxnkfQeLmPTzDhAmCWtbMZ/24.png)就能看到 24 号猴的图片了,这也是 OpenSea 平台中给大家展示的图片样子。

大家可以看到 NFT 的所有关键信息都是存在 tokenURI 这个链接中的,这个链接中的数据包括了你 NFT 的编号、属性、图片或视频,所以 tokenURI 这个链下数据能不能正常访问,会不会被篡改非常重要!!!

这里 CryptoApes 的 TokenURI 用的是 IPFS 协议,IPFS 协议可以保证不会被篡改。但如果是 HTTP 协议则有被篡改的可能,虽然使用 HTTP 协议可以通过 checksum 校验数据等方式验证,但这并不优雅,并且也不是 ERC721 的协议规范。

这里插一句,鄙人原本打算写一篇介绍去中心化存储的文章的,因为感觉这个领域的重要性被大家严重忽视了,它也是区块链生态的基础设施,重要性并不比被热捧的各类公链项目低。使用 IPFS 协议存储内容虽然不会被篡改,但是 IPFS 还存在它自身的问题,这里就不展开了。

接着说,上面我们查到了编号 24 的猴的所有关键信息。那如果想把所有 6969 只 CryptoApes 的信息查找出来可以吗?

当然可以!使用上述 ERC721 规范中的「tokenByIndex()」方法进行遍历就行,有兴趣的朋友可以自行试试。

实际上 OpenSea 支持所有的 ERC721 的 NFT 的信息展示,就是通过上述方法去链上抓取数据,并在 OpenSea 自己的系统中来建立起来所有资源信息,最后通过 Web 的形式展现到大家面前,方便大家浏览。

这里需要吐槽的是 OpenSea 网站的 robust 及 performance 做得实在太烂了,经常动不动就挂掉,所以鄙人也正着手搞一个类似的产品,从 blockchain 上同步 NFT 数据,以及从 OpenSea 上同步交易数据,存储后按用户需求展示给用户,提供稳定、高效以及易用的浏览及查询服务。(做得好的话未来想象空间也很大,哈哈)

上面通过脚本调用合约给大家展示的都是「」方法,「」方法的话就是在代码中加载钱包,签名之后就可以了。(所以挺简单的吧,大家也别被所谓「科学家」这个名字唬住了。简单学习一下编程,再熟悉下合约的调用,就可以通过程序操作多个钱包,去薅羊毛还是干点别的事情就都可以了

需要警惕的一些方法

ERC721 规范中有两个方法需要大家警惕一下,如果不小心也许就会丢掉你的 NFT。

第一个需要警惕的方法是「approve(to, tokenId)」,这个方法是授权「to」这个地址有权利可以转走你这个「tokenId」的 NFT。如果你在小狐狸中授权钓鱼网站调用了这个方法,最多会损失一个 NFT。

这个方法 ERC20 规范中也有,ERC20 是授权「to」地址最多可以使用多少数量的 token。鄙人的去中心化红包项目,就是在你发红包之前要求你使用「approve」授权一下,好让红包合约可以扣你的 token 并装入红包中给大家去抢。各类 Swap 的 DAPP 也是一样,需要先「approve」才可以进行 swap。

第二个需要警惕的方法是「setApprovalForAll(operator, approved)」,这个方法是授权「operator」这个地址可以转走你在这个 Collection 下所有的 NFT。如果你在小狐狸中授权钓鱼网站调用了这个方法,则可能丢失这个 Collection 下的所有 NFT。

在 OpenSea 平台中,如果我们「Sell」一个 NFT,小狐狸就会弹出这个方法的授权,见下面截图。

注意红框里的内容,就是向 CryptoApes 的合约地址调用「setApprovalForAll(operator, approved)」这个方法。授权之后,如果有人出价购买,OpenSea 的交易合约则可以把你的 NFT 直接转给买家,转给买家这个操作调用的是 ERC721 规范中的方法「transferFrom(from, to, tokenId)」,from 是你自己,to 是买家,tokenId 是这个 NFT 的编号。

所以大家在小狐狸中进行授权的时候,一定有安全意识,看见 approve 或者 setApprovalForAll 方法时一定要注意是不是正规的网站,合约地址是不是正确的地址。如果不小心授权错了,你的 NFT 就可能被转走。

也许有人会问:我只卖出一个 NFT,为什么不单独授权这一个出售的 NFT(approve),而是要授权所有的 NFT 呢(setApproveForAll)?

答:OpenSea 的解释是可以省 gas 费,一次授权后,再次卖出其余 NFT 的时就不需要因为再次授权而付更多的 gas 费用了。

也许有人会问:那我授权给了 OpenSea 所有 NFT 的转移权限,那 OpenSea 平台会不会悄悄转走我的 NFT 呢?

关于这个这个问题可以继续看后面。

OpenSea 买卖流程的背后

卖出 NFT 的背后

下图是在你卖出 NFT 时候的弹窗截图:

在 OpenSea 上进行卖出操作时,会弹出窗口第一步让你先初始化钱包(这个是一次性操作),在你付了 gas 费之后,OpenSea 的 Registry 合约会帮你创建一个钱包合约(实际上就是一个 Proxy 合约),大家如果在 etherscan 上查的话,可以看到一个 RegisterProxy 的操作,实际上调用的就是下面代码去创建了一个属于你个人钱包合约:

function registerProxy()
public
returns (OwnableDelegateProxy proxy)
{
require(proxies[msg.sender] == address(0));
// 創建一個新的代理合約
proxy = new OwnableDelegateProxy(msg.sender, delegateProxyImplementation, abi.encodeWithSignature("initialize(address,address)", msg.sender, address(this)));
proxies[msg.sender] = proxy;
return proxy;
}

为什么需要创建这个合约呢?主要目的是为了安全,因为第二步「Approve this item for sale」时需要授权一个合约地址可以转移你的 NFT(也就是上一段落说的 setApprovalForAll 方法),授权可以转移你 NFT 的地址,就是「Initialize your wallet」这一步所创建的钱包合约地址。

也就是说 OpenSea 不能直接转走你的 NFT,只有你在 OpenSea 上初次创建的这个钱包地址才可以转走。

在创建完钱包以及授权 NFT 之后,如果你去挂出同个 Collection 下的 NFT 进行卖出时,不需要额外的手续费,OpenSea 仅仅验证你的签名就可以挂出卖单了,这点就是被 OpenSea 宣传的「gas-free listing」。

所以讲到这里,也许有人又注意到了一个问题:为什么后续挂出卖单仅仅只需要签名,不需要 transaction 呢?OpenSea 的卖单信息只是存到它自己的中心化服务器上,没有存在链上吗?

答:是的,卖单信息只存在 OpenSea 的中心化服务器上,没有上链,具体可以参考这里 OpenSea 的解释。

在 OpenSea 上,大多数操作都是  脱链的,这意味着它们生成的订单存储在我们的系统中,并且可以通过来自另一个用户的匹配订单来完成。

当用户列出要出售的物品时,他们只需签署交换物品以进行付款的意图。此意图作为  卖单存储在 OpenSea 系统中,并且不会创建交易。

关于很多评论说 OpenSea 太过中心化这个问题,之后鄙人也会简单谈谈自己观点。

买入 NFT 的背后

在 OpenSea 买入的时候,买卖的撮合其实是发生在 OpenSea 的中心化系统中,匹配好订单后让用户调用 OpenSea 交易合约地址AtomicMatch 方法完成交易,这个方法里完成了一系列复杂操作,这里就不展开讨论了。

也就是说成交之后,成交订单的信息会上链,毕竟这涉及到了 Token 和 NFT 的转移。

OpenSea 使用的交易合约应该是 Wyvern 协议,实际上如果整个交易过程中只有最终交易数据才上链的话,可以不用这么复杂的合约,不过这应该是历史遗留包袱。

大家只需要知道这次买入交易成功的背后,会完成这两个步骤:

  1. 把 NFT 转给买家
  2. 把买家的钱转给卖家和 OpenSea(平台手续费)
其它交易方式

关于 Offer 报价以及 Auction 拍卖的交易方式鄙人没有试过,不过我认为跟 Listing 交易一样,在 offer 或者 bid 之前 approve 一下你的 WETH 就行,而 auction 拍卖订单和 Offer 报价订单的创建与撮合也应该还是通过 OpenSea 的中心化系统完成

关于 OpenSea 太过中心化的评论

鄙人在网络上看到很多声讨 OpenSea 太过中心化的评论,这段时间开始买 NFT 对 OpenSea 进行研究了之后才了解这些评论的根本原因,实际上 OpenSea 也是基于当时条件限制下的产物。

鄙人这里也为 OpenSea 的部分中心化技术方案辩驳几句:

  • 首先它降低了手续费,贵族链绝非浪得虚名的。如果所有数据信息上链那必然会导致交易的成本上升,更加提高了用户的交易门槛。(如果所有信息上链的话,我想唯一的好处就是鄙人不会每天收到批量 offer 的邮件通知了,毕竟每一次 offer 都需要燃烧 gas)
  • 其次 Etheruem 的 performance 不足以承载大量的 transaction,如果每次挂出卖单、修改卖单价格、每次 Offer 价格等所有信息都上链的话,那会更加进一步推高 Ethereum 的 gas 费用。
  • 最后关键信息上链也一定程度上确保了交易的安全和公开。比如你 NFT 和 WETH 的授权是给到你的钱包合约的,OpenSea 不直接触碰。OpenSea 也开放了它的 API,所有未上链的订单数据可以通过 API 获取,好心人可以通过 API 拿到数据后和链上最终的交易数据进行比对验证。(不过我猜测不存在这样的好心人吧)

最近这一年公链的迅猛发展,gas 费过高以及性能的问题以及得到了极大的缓解。如果 OpenSea 不思进取、不做改进的话绝对会被大多数用户抛弃,最后发展成为贵族 NFT 市场,比如前几天 LooksRare 平台也来空投抢用户了。当然这个问题也是 Ethereum 需要面对的。

关于 OpenSea 的交易背后本人有两个问题:

  1. 本人发现在 OpenSea 上 Cancel 一个 Listing 订单,也是需要写入区块链的。但我觉得直接在 OpenSea 的中心化系统中直接 Cancel 就好了,毕竟 Listing 的订单信息也没上链啊,为什么 Cancel Listing 订单的操作需要上链呢?这 gas 费不是白白浪费掉了吗?
  2. Listing 订单价格的修改,只能往低了修改,不能改高了,如果需要报更高价格的话需要先 Cancel Listing 订单,然后再重新挂一个,而 Cancel 的时候又得上链浪费一笔 gas 费用。同样 Listing 订单数据没在链上,为什么 List price 都可以往低了改,而不能直接改高呢?

这两个问题本人没太想明白为什么 OpenSea 这样做,因为个人觉得在中心化系统上取消订单和随意修改订单价格不是很简单的事情吗?

个人不怀好意的猜测是 OpenSea 系统做得太烂,不愿意让用户随意取消订单和修改价格,因为这样会导致系统的不稳定。所以 Cancel Listing 这个操作硬要上链,让用户消耗 gas,这样用户在挂卖单的时候就需要慎重考虑要不要挂,以及要挂的价格了。

也希望知道原因的朋友不吝赐教!

最后的最后

最近因为 PhantaBear 的关系对 NFT 着迷了,鄙人着迷到什么程度呢?我媳妇告诉我,我小学 2 年级的孩子都在问她同学有没有爆炸头熊的 NFT 了。。。

着迷到已经有点动摇 BTC Hodl 决心,盘算着是不是拿点 BTC 去换一个 CryptoPunks 的程度,毕竟有 NFT 第一的故事加持,非常想搞一枚。

关于 NFT 的胡思乱想

社会的经济活动实际上就是钱和资产相互兑换的过程。比如用钱购买衣服、鞋、车、房就是用钱购买了资产。同样也可以变卖衣服、鞋、车、房将资产换取钱,这就是基本的经济活动。这个交易过程的顺利执行其实是通过合同保证的(买衣服、鞋这类小商品虽然没有实体合同,但实际上存在虚拟合同),而社会法律强制确保了合同可以按预期执行(你要是收了钱但没给我东西,我就可以起诉你)。

你会看到在区块链的世界中是一模一样的,BTC、ETH 等各种币就是钱,而玲琅满目的 NFT 就是个各类资产。crypto 可以购买 NFT,NFT 可以变卖换成 crypto,这样就形成了未来元宇宙中的经济活动。交易通过智能合约来完成,而公链的共识机制又确保了智能合约能够以预期方式执行。

你会发现区块链行业的发展与现实社会极其相似,NFT 的出现也许不是偶然吧。

不过比较有意思的是,人类社会最早的经济活动只是以物换物,例如我用羊去换你的牛,因为以物换物的不便才出现了货币作为交换的媒介。所以现实社会是先出现资产,再出现货币。但区块链行业中先出现的是各类加密货币,然后才出现了代表资产的 NFT,与现实社会相反。

投资建议声明

以上文章内容全是鄙人胡说八道,不作为任何投资建议。

免责声明:作为区块链信息平台,本站所发布文章仅代表作者及嘉宾个人观点,与 Web3Caff 立场无关。文章内的信息均不构成任何投资建议及要约,并请您遵守所在国家或地区的相关法律法规。