# 使用 Dune 分析 MimicShhans 数据

By [issmall](https://paragraph.com/@icatsay) · 2022-07-11

---

前言
==

[MimicShhans](https://opensea.io/collection/mimicshhans) 是近期爆火的一款 NFT，由韩国的一位艺术家使用 [OpenSea 的 ERC-1155 公共合约](https://etherscan.io/address/0x495f947276749ce646f68ac8c248420045cb7b5e)发行，计划发行总量为 10020，根据[官网](https://www.mimicshhans.com/)的铸造计划共分为三个阶段：

*   阶段一：共计铸造 700 个 Mimic Shhans NFT （已完成）
    
*   阶段二：每天铸造不定数量的 NFT，共计 9000 个，另外保留 1000 个 （已完成）
    
*   阶段三：20 个手绘版作为奖励（进行中）
    

由于 MimicShhans 使用的公共合约发行，所以目前市面上绝大多数的 NFT 分析平台都不支持分析其数据，所以对于 NFT 的持仓分布等情况一直是黑盒的。

项目起飞之前，我“零元购”了 80 个 MimicShhans NFT，由于担心成为一个冤种，我开始研究该 NFT 的链上数据及 Dune 清洗的数据，并通过 [Dune 看板](https://dune.com/issmall/mimicshhans)将其展示出来。

调研
==

由于之前工作的原因，很早就接触了 Redash，而 Dune 正是基于 Redash 二次开发的，所以上手基本没有压力。但是由于 Dune 的数据表数量过于庞大并且相关资料并不详细，于是我开始通过关键词寻找可能满足我需求的数据表。

首先，通过 pg\_tables 查询可能的模式（schema），关键词锁定为 opensea 和 nft：

![](https://storage.googleapis.com/papyrus_images/ae5fc2a200fb4d4f73d1efd111c9503547ab58982e121187399ccd180655c0f9.png)

幸运的是结果并不多，挨个看了一下模式名称，从中筛出 nft 和 opensea 两个模式。

然后，通过 pg\_tables 查看这两个模式所包含的数据表，为什么用 pg\_tables 查看呢？因为左侧的查询实在很不方便。

![](https://storage.googleapis.com/papyrus_images/55c77918bf761eaa9dcb478d98ddd1ac5e816c37e022f340083a87c98f96919e.png)

从结果可以看出 opensea 模式下的表基本是合约函数调用记录，而 nft 模式下只有 5 个表。“嫌疑人”范围锁定在了 opensea 模式下的某个函数调用表及 nft 模式下的 trades、trades\_v2\_beta 和 wyvern\_data 表。

由于该 NFT 是从 OpenSea 发行的，于是我先从 opensea 模式下手。前文提到过，我“零元购”了一批 NFT，于是我翻看 OpenSea 的记录，此处以 [MimicShhans #8320](https://opensea.io/assets/ethereum/0x495f947276749ce646f68ac8c248420045cb7b5e/78691630486237452253407982334196552834116754498908990625475385096473963134977) 为例。

![](https://storage.googleapis.com/papyrus_images/e75eb30c61c0aa07471734dc4edd6369654e409c241362b9625eb5175e8b8144.png)

共有 4 条动态，其中，Minted 和 List 是没有上链的，所以没有超链接；而 Transfer 和 Sale 其实对应了同一笔交易，[点击超链接打开 Etherscan 查看交易详情](https://etherscan.io/tx/0x77d939cf10c6c736a6c2ff6c518bbaaa54c170043ffb6f5d3f2c0a95e3ab7b4f)。

![](https://storage.googleapis.com/papyrus_images/b8e0ecccfd7b2c6b4bb112114689d15c18f527b2b8c553e04054cd0554cdd541.png)

点击 “Click to see More” 查看更多细节信息：

![](https://storage.googleapis.com/papyrus_images/6e58182ba9bff7e4144ce798aa7688dc90f40efec2cc39ae0787b2bd0a7db8f9.png)

此处可以看到这笔交易调用了合约的 atomicMatch\_ 函数，于是我回到 Dune 查找 opensea 模式下关于 atomicMatch\_ 函数相关的数据表：

![](https://storage.googleapis.com/papyrus_images/c4766910f4f99e4bc1aa38af168f948cfafc66ac9379f8d0ded6dff76375fa6d.png)

只查到了 opensea.WyvernExchange\_call\_atomicMatch\_ 这个表，通过左侧查询框查询该表结构如下：

![](https://storage.googleapis.com/papyrus_images/02b0ef1433725b8e06f5a3efff47e084d0aff00ebb65518d1213be3faa39792c.png)

仅仅从表结构其实并不能很好的确定里面的内容，于是我查询了几条数据样本查看：

![](https://storage.googleapis.com/papyrus_images/2ea6c21b4d594da63f9f55ddacacb0bf68f9e09ff52d16f0e46deb8cd999bf9b.png)

简单看了下数据，基本是没有解析的状态，如果不进行二次清洗、加工，不太可能拿来做看板的数据源。

于是我继续查看 nft 模式下的表，既然有 v2 那我就直接先拿 v2 版本下手了，于是我查看了 nft.trades\_v2\_beta 的数据结构：

![](https://storage.googleapis.com/papyrus_images/92abf8b1af7bbce2f59643944b87c85cef31bbf68064c4b9b33c0cb86f6d8169.png)

单从数据结构上就觉得是它了，因为从字段名上可以看到 buyer、seller、nft\_token\_id 等解析后的数据。

查询了几条数据作为样本：

![](https://storage.googleapis.com/papyrus_images/1719341d1b9d55ab106597d686b9c03f699947afe431a943d35643c4abcb12c7.png)

其中一笔的 tx\_hash 为：\\x0008512e06c1fd26a7484484400d07ee179fd95651ee9405c4d577828a50440c

打开 Etherscan 查看交易详情：

![](https://storage.googleapis.com/papyrus_images/4e01d45b973d2810c4c9a62bcc8e308fb4054deb4c93e4b51f9ee163d72d873c.png)

对照 Dune 的查询结果，可以确定：

*   seller 是 Tokens Transferred 对应的 From 地址；
    
*   buyer 是 To 地址；
    
*   nft\_token\_id 就是 Token ID；
    
*   original\_amount 对应的是 Value，也就是转账的 Ether 数量，而 Ether 是 Ethereum 的主链币，并没有合约，所以 original\_currency\_contract 对应的 0x0；
    
*   nft\_contract\_address 对应的就是合约地址。
    

有了上述的调研之后，回过头来看我的购买记录，对应的数据如下：

*   seller：0xadf9e26e125d64dd1df6482d8f3a62f704122cc3
    
*   buyer：0xd89e373dc88503fdb3bbe34021fc13a6c9a2fcaa
    
*   nft\_token\_id：78691630486237452253407982334196552834116754498908990625475385096473963134977
    
*   nft\_contract\_address：0x495f947276749ce646f68ac8c248420045cb7b5e
    

根据以上信息构造查询：

![](https://storage.googleapis.com/papyrus_images/67a836f77f58b22846c4fbfc196eae053389bf0e55ef64cd7eda6e32b0c649ad.png)

查询到这条记录，说明之前的推测是正确的，使用 nft.trades\_v2\_beta 可以查到 Mimic Shhans NFT 的交易数据。

由于该 NFT 是通过公共合约（0x495f947276749ce646f68ac8c248420045cb7b5e）创建的，所以统计该合约的所有数据肯定是不准的。

于是我寄希望于通过 Token ID 获得一些线索，这里 Token ID 是个十进制的长整数（78691630486237452253407982334196552834116754498908990625475385096473963134977），我试着将其转换为十六进制：

![](https://storage.googleapis.com/papyrus_images/8a1eec40c9225ae8adbc40cbe4bc3d4e5bded8b5c791cb045f7050d58f3916cf.png)

从中可以看到前 42 位字符（去掉0x是40位字符）刚好跟 seller 的地址一致，应该是发行人地址， 而后面 0 隔开的两个数字可能跟编号、系列有关（仅仅是推测）。

于是构造查询：

![](https://storage.googleapis.com/papyrus_images/93e8c583e7e4ff85aaeb510496fb3e083e68a02473e1b95b3571a48625b63b7a.png)

获得 9040 条记录(其中有 32 条是重复数据)，对 nft\_token\_id 进行去重后总计 9008 条记录：

![](https://storage.googleapis.com/papyrus_images/9b0a4f8d0d19a7e69b5a10bd5be25247d2629f4792156f2956f4858d446a8ad4.png)

这时由于还没有详尽的调研，所以对于 9008 这个数字我是持怀疑态度的，但是由于前几天刚刚发行了 10008 号 NFT，所以数据上似乎又有一丝的合理性。于是我翻看了[官网](https://www.mimicshhans.com/)，最终找到了铸造计划：

![](https://storage.googleapis.com/papyrus_images/581c8b93e2ec2e360b30f594e4a901f13c754c5bc273496e64ba80bc09b9c215.png)

也就是证明了 9008 这个数字的合理性。

于是回过头去 OpenSea 查看 [MimicShhans #9001](https://opensea.io/assets/ethereum/0x495f947276749ce646f68ac8c248420045cb7b5e/24618594739772325081071012251968077253983773594612099830659053360218080542721) 的动态：

![](https://storage.googleapis.com/papyrus_images/47c8cb13dfec638945239bd8f64ee0aedeb9b0fe93587b9ea5c26293f1bdc06e.png)

没有链接，也就是没上链的状态，随后我又查看了 [MimicShhans #10000](https://opensea.io/assets/ethereum/0x495f947276749ce646f68ac8c248420045cb7b5e/80745571867532754198199170403581894626192094270854931108918001384499366068225) 的动态：

![](https://storage.googleapis.com/papyrus_images/d711dea32c7a5fff40b68c3b321c2c5342d350bb3f121194908779fe4829334e.png)

也是没有上链的状态，随后我又用二分法查了一下，9001-9500 由 [Shhan\_Crowned](https://opensea.io/Shhan_Crowned?tab=created)  铸造，9501-10000 由 [june0929](https://opensea.io/june0929?tab=created) 铸造。

这时我才意识到走了弯路，如果一开始就点开 [MimicShhans #8320](https://opensea.io/assets/ethereum/0x495f947276749ce646f68ac8c248420045cb7b5e/78691630486237452253407982334196552834116754498908990625475385096473963134977) 动态里面 [MimicShhans](https://opensea.io/MimicShhans) 的超链接就可以看到：

![](https://storage.googleapis.com/papyrus_images/fcb4e87ca8b4a2d20bfe7e9c83af39a8ac094de6bdbd55543dffe86747054a20.png)

也就是说，初始 seller 就是艺术家的地址，而 Token ID 又是根据艺术家地址计算得出的。但是，如果作者有多个系列的作品，Token ID 就会混在一起。

不过在 MimicShhans 系列当前铸造的 NFT 里面暂时没有这个问题，因为这个地址只铸造了 MimicShhans 系列的 NFT。

至于 9001-10000 段的 NFT，我分析了几个 Token ID，没有发现如何跟作者的其他作品拆分开，也许就是中心化存储进行的归类吧。

实施
==

通过上述调研，已经得到了当前已经上链的 NFT 的 Token ID，所以可以构造查询获得 MimicShhans 的所有交易数据：

![](https://storage.googleapis.com/papyrus_images/c65084a8611172a5672fd9f2960451039a415c8d1ac33e3b263c1c7422f3aaa4.png)

为了方便使用，可以通过 with as 语句构造一个查询子句，减少重复查询和重复代码：

![](https://storage.googleapis.com/papyrus_images/f78ae42904dc585f1358356a2d2bf5ab54fa5f26dd685e3316ef48a3b3fbc394.png)

之后可以将 mimicshhans\_trades 看作是一个数据表来使用，例如：交易额：

![](https://storage.googleapis.com/papyrus_images/9926e38d684e992d40b0d314994cb064205b32a41481ff8ee2d0d7c7ee333c5b.png)

关于看板的其他 SQL 语句，可以在[这里](https://dune.com/browse/queries?user=issmall)找到优化后的代码，此处就不一一列举了。

优化
==

SQL 逻辑虽然写完了，但是运行效率是真的差，每个 SQL 的执行时间都在 1 分钟到 3 分钟，对于一个分析师来说是无法忍受的，于是我开始着手优化 SQL，首先查看 nft.trades\_v2\_beta 的索引有哪些：

![](https://storage.googleapis.com/papyrus_images/35e4039ed1f6cbd9ff4454b65593fa4c715c85a66ce9e64d607460d355ebc0a4.png)

从中可以看到：

*   nft\_trades\_v2\_beta\_platform\_tx\_hash\_evt\_index\_trade\_id\_uniq\_idx (platform, tx\_hash, evt\_index, trade\_id)
    
*   nft\_trades\_v2\_beta\_platform\_tx\_hash\_trace\_address\_trade\_id\_uniq (platform, tx\_hash, trace\_address, trade\_id)
    
*   nft\_trades\_v2\_beta\_block\_time\_idx (block\_time)
    
*   nft\_trades\_v2\_beta\_seller\_idx (seller)
    
*   nft\_trades\_v2\_beta\_buyer\_idx (buyer)
    
*   nft\_trades\_v2\_beta\_nft\_project\_name\_nft\_token\_id\_block\_time\_idx (nft\_project\_name, nft\_token\_id, block\_time)
    
*   nft\_trades\_v2\_beta\_block\_time\_platform\_seller\_buyer\_nft\_project (block\_time, platform, seller, buyer, nft\_project\_name, nft\_token\_id)
    

之前看板查询中使用到了 nft\_contract\_address、seller、nft\_token\_id 这些字段，其中，子句：

![](https://storage.googleapis.com/papyrus_images/0f1e3c62ae325a40e7edd9c5a4aa86014fdd737d4f28f8304224d326ac5cb138.png)

已经使用了索引 nft\_trades\_v2\_beta\_seller\_idx，基本没有优化的空间了。

而外层的查询使用了 nft\_contract\_address、nft\_token\_id 字段，可以试着往索引 nft\_trades\_v2\_beta\_nft\_project\_name\_nft\_token\_id\_block\_time\_idx 上靠。根据之前的查询，可以获得 nft\_project\_name 为 OpenSea Collection，如果想使用索引就必须满足 nft\_project\_name = ‘OpenSea Collection’ 的合约地址只能是 0x495f947276749ce646f68ac8c248420045cb7b5e，于是，我花几分钟时间证明了自己的猜想：

![](https://storage.googleapis.com/papyrus_images/47d2d3bbaed94c2f9856c01f3574a2e154f94b39797358cf634349d4d2d95a9c.png)

更改后的代码及执行效果：

![](https://storage.googleapis.com/papyrus_images/df7baa737537c59c19bdf6a156b5ed7ffdf3a35a8a636f21f90b8941d9102c6e.png)

之前需要执行两分钟的代码，现在只需要两秒钟就可以达到同样的效果。

工具
==

在使用 Dune 的过程中发现很多不方便的地方，于是我顺便做了一个[工具看板](https://dune.com/issmall/dune-developer-tools)。

*   上面的两个输入框（Schema Keyword、Table Keyword）用于模糊查找模式及表
    
*   下面的两个输入框（Schema Name、Table Name）用于精确查找表的结构及索引
    

通过这个看板可以快速的找到所需要的模式及表，以及翻看表结构及索引。

成品
==

以上便是我制作看板的大致过程，如果你只想看数据，可以通过这个链接查看：

[https://dune.com/issmall/mimicshhans](https://dune.com/issmall/mimicshhans)

广告
==

你可以通过这些方式找到我：[https://linktr.ee/issmall](https://linktr.ee/issmall)

---

*Originally published on [issmall](https://paragraph.com/@icatsay/dune-mimicshhans)*
