赞
踩
读者可前往我的博客获得更好的阅读体验。
本文主要介绍如何通过免费且高效的的基于GraphQL的basement进行部分链上数据分析实战。本文不要求读者具有GraphQL
相关经验,但要求读者会使用Python
中的Pandas
库,这是本文主要使用的数据分析工具。换言之,本文适用于了解数据分析而不了解链上数据获取的读者。本文会涵盖以下内容:
GraphQL
检索数据基础入门Basement
的基础API实战在阅读本文前,读者最好安装一个支持GraphQL
请求方法的API调试工具,在此处,我个人使用的是Postman软件,但读者选择其他软件亦可。本文使用了新兴 Web3 链上数据API提供商basement,此处我们使用的是免费版,无需 API Key 等配置,具体限制参考下图:
关于Basement
的优势可参考Mirror 文章。
在进行第一个请求前,我们需要了解关于GraphQL
最基础的一些概念,首先GraphQL
实质上是一种类似SQL
的数据检索语言,当然其学习难度低于SQL
。当我们进行一次GraphQL
请求时,我们将GraphQL
语言编写的检索方法放在POST
的body
中,直接发送POST
请求到终端API节点即可。
我们给出一个来自GraphQL 官网的检索示例,索引请求如下:
{
hero {
name
}
}
返回如下:
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
由此可见,GraphQL
的检索是简单且易读的,基本遵从以下规则:
{
所需要的对象 {
对象属性
}
}
值得注意的是在现实世界中存在大量对象嵌套的情况,比如以下数据:
{
"data": {
"address": {
"address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"profile": {
"name": "vitalik.eth"
}
}
}
}
可抽象化为以下情况:
{
data对象 {
address 对象 {
address 属性
profile 对象 {
name 属性
}
}
}
}
关于如何获得这些信息,一个方法是查询文档,本次实战使用的Basement
在它的文档中给出了这些信息,如下图:
我们可以通过点击其中的蓝色链接确定每个Object
中的属性是否嵌套了另一个Object
。
显然,在以太坊区块链上遍历获得address
数据是不显示的,我们在此处需要引入一种方法筛选我们所需要的address
,这就是参数机制,读者可在此处找到相关文档。在Basement
中大量的对象必须与参数一同使用。在Basement
文档中参数被表示为Arguments
。
最后我们介绍一个执行检索的入口,即Query
对象,这些对象用于作为检索的入口与query
关键词配合。这些作为检索入口的Query
对象类型列表可以在此处文档找到。
有了上述简单的基础学习,我们就可以开始构建我们第一个GraphQL
索引,如下:
query Test {
address(address: "vitalik.eth") {
address
profile {
name
avatar
}
tokens(limit: 3) {
contract
name
tokenId
}
}
}
此处我们选择了入口检索对象为address
,此对象的参数仅有 1 个,即为address
,address
参数必须为字符串类型,可以为正常的 16 进制编码的以太坊地址,也可以是ENS地址。我任选了几个属于address
的属性进行检索,完整的属性列表可以在此处查询。
query
后的Test
是为本次查询所取的名字,并不重要,读者甚至可以删去此字段,直接使用query
关键词
请求的API的URL为https://beta.basement.dev/v2/graphql
,读者使用了Postman
进行请求的截图如下:
返回的结果如下:
{ "data": { "address": { "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "profile": { "avatar": "eip155:1/erc1155:0xb32979486938aa9694bfc898f35dbed459f44424/10063", "name": "vitalik.eth" }, "tokens": [ { "contract": "0x000386e3f7559d9b6a2f5c46b4ad1a9587d59dc3", "name": "BoredApeNikeClub #1", "tokenId": "0x01" }, { "contract": "0x000386e3f7559d9b6a2f5c46b4ad1a9587d59dc3", "name": null, "tokenId": "0x0160" }, { "contract": "0x000386e3f7559d9b6a2f5c46b4ad1a9587d59dc3", "name": null, "tokenId": "0x01bd" } ] } } }
至此我们完成了第一个请求。
在阅读完上述内容后,我们进行第一个实战,探索 V神 拥有NFT的数量和种类,我们首先进行数据索引:
import pandas as pd from gql import gql, Client from gql.transport.requests import RequestsHTTPTransport transport = RequestsHTTPTransport( url="https://beta.basement.dev/v2/graphql" ) client = Client(transport=transport,fetch_schema_from_transport=True) query = gql( """ query vitalikNFT{ address(address: "vitalik.eth") { tokens(limit: 100000) { contract name tokenId } } } """ ) result = client.execute(query)
注意我们改变了tokens
的limit
参数,因为此参数默认为 50,但V神的NFT远远大于此值,所以我们通过设置调高此参数。
使用以下代码可以获得排序后前十个结果,即V神持有量最大的 10 种NFT:
vitalik_nft.groupby("contract").count().tokenId.sort_values(ascending=False)[:10]
结果如下:
对于Basement
而言,其提供关于NFT的数据主要关于以下三个方面:
我们会在下文逐一介绍以上内容。
NFT 元数据是 NFT 最重要的属性之一,使用basement
的API,我们可以获得NFT的基本属性和一些拓展属性。
如果想获得单一NFT的 MetaData,我们需要以下参数:
conrtact
NFT合约地址tokenId
单一NFT的 id我们可以声明获取属性请参考此列表,个人猜测大部分属性来自 Opensea 平台。以下给出一个以Bored Ape Yacht Club
(BAYC) 为参数的请求示例。
使用 Opensea 网站的搜索功能,搜索Bored Ape
,我们获得此网页,如下:
通过点击etherscan
标识按钮,我们可以获得此NFT的合约地址,即0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d
我们可以通过basement
获得非常多的数据,以下是我准备的请求
query NFTFirst { token( contract: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D" tokenId: "7292" ){ name description image{ url } tokenUri sales{ transactionHash price marketplace } mintPrice } }
读者应该可以很简单的凭借语义了解此处请求获取的数据,由于长度问题,此处仅给出部分返回值:
{ "data": { "token": { "description": null, "image": { "url": "https://cdn.basement.dev/291e642509d5516da28e0ce746e69d99/original" }, "mintPrice": "80000000000000000", "name": null, "sales": [ { "marketplace": "OPENSEA", "price": "53600000000000000000", "transactionHash": "0x922910f07d81d91074f938b2c98d4eb89c065f720241f030f492a53f28499715" }, ... ], "tokenUri": { "attributes": [ { "trait_type": "Mouth", "value": "Phoneme Vuh" }, ... ], "image": "ipfs://QmXgtpxm5rMLkBqj9xbQb5w4GSy8vrLWvUP8kgenonYa4n" } } } }
对于部分用户而言,您请求的NFT数据可能并不存在于basement
数据库,如在编写此文时,无聊猿 #1 仍不存在,如下图:
此时我们可以通过一个特殊请求要求basement
获取相关数据,请求如下:
mutation {
nonFungibleTokenRefresh(
contract: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"
tokenId: "1"
)
}
此处mutation
关键词标识用户通过此请求可以修改服务器的相关数据。
返回如下:
{
"data": {
"nonFungibleTokenRefresh": "This token has been queued for refresh, check back in a few minutes."
}
}
再次进行请求,就可以获得NFT的相关数据,如下图:
最后,有部分读者可以有对一系列地址所拥有的NFT进行统一检索的需要,此时我们可以使用tokens
索引方法,此方法需要以下参数:
filter
目前仅支持通过地址进行过滤limit
单次请求返回的限额after
和before
均为分割点,一般不需要此参数在编写此文时,此API无法实现真正的多用户NFT检索,返回如下:
{ "data": null, "errors": [ { "locations": [ { "column": 5, "line": 2 } ], "message": "Providing multiple owner addresses is not supported right now but will be in the near future.", "path": [ "tokens" ] } ] }
通过 Opensea 分析网页,我们可以发现bayc.benddao.eth
是最大的BAYC持有人。
我们希望获得此人所有BAYC
的来源,我们可以通过erc721Transfers
进行检索,此检索需要以下参数:
filter
过滤器,下文会详细介绍limit
返回值限定数量after
和before
不常用参数,用于分页此处的filter
是一个对象,包含以下属性:
fromAddresses
限定发送者地址,仅允许ENS地址toAddresses
限定接收者地址,仅允许ENS地址blockNumbers
一个由需要查询交易的情况构成的列表toBlock
检索区块的上限fromBlock
检索区块下限contractAddresses
合约地址tokenIds
转移的NFT的id值我们可以通过以下代码进行索引:
query NFTtransferTest { erc721Transfers( filter: { toAddresses: "bayc.benddao.eth" contractAddresses: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D" } limit: 100 ){ erc721Transfers{ blockNumber token{ name tokenId } from{ address } } totalCount } }
当然,此处可以检索出所有转移给bayc.benddao.eth
的BAYC
。最后发现totalCount
为1897
,这是因为仅计入转入而未计入转出导致的。
此处的
benddao
实际上是一个 NFTfi 机构,提供质押蓝筹 NFT 进行贷款的服务,所以此地址会持有大量NFT
实际上,此数据并不是一个单独的Query
而是Object
对象。由于很多用户特别需要此数据,所以在此处详细给出其属性,当然,读者亦可以直接参考文档。
目前此API似乎仅支持 Opensae
平台,且数据似乎与平台给出的数据数量不同,一个简单的检索如下:
query NFTSale { token( contract: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", tokenId: "7116" ){ sales{ price marketplace maker{ ...simpleAddress } taker{ ...simpleAddress } } } } fragment simpleAddress on Address{ address reverseProfile{ name } }
其中的maker
和taker
分别为NFT的卖家和买家,均属于Address
属性数据,为避免重复,我们将此部分抽离出为fragment
,其中的on
标识此fragment
用于何种对象。
我认为此功能才是basement
最强的功能,但需要读者具有一定以太坊的基础知识,读者应至少了解过交易的Receipt Event Logs
。简单来说,在以太坊中进行的合约交互交易会抛出一系列事件,这些事件也会被包含在以太坊区块中,我们可以通过检索事件来寻找交易。
此处我们通过一个实战案例为大家介绍如何进行一次交易数据检索。此实战案例是获取Dori SamuraiNFT项目的所有铸造交易。为达成这一目标,我们需要首先获得代表性交易,进入任一NFT详情页,点击Item Activity
栏目内的Minted
链接,如下图:
读者进入Etherscan页面,点击Logs
选项卡,如下图:
我们发现此处抛出的交易事件为Transfer(address,address,uint256)
,其中第一个参数为转账来源而第二个参数为接受方,最后一个参数为NFT的tokenId
。对于大部分event
,我们可以通过etherscan
给出的参数判断含义,当然,查询对应的EIP也可以获得参数含义。如此处,我们可以查询EIP721 Specification阅读注释,大部分基于EIP的含义均遵循此处给出的注释。
如果您发现此合约没有对应的EIP标准且
etherscan
未给出参数,我们只能通过阅读源代码获取event
含义
对于NFT
的mint
操作,第一个参数始终为0x0000000000000000000000000000000000000000
,即空地址。(对于EIP20代币
的铸造也是从空地址转账)
第三个参数是接收方的地址,此地址可以随机改变。
基于以上内容,我们可以尝试构建检索交易过程中最重要的参数filter
,此参数属于TransactionLogFilter对象,最重要的参数为topics
,其构造为[[event][args1][args2]...]
,比如此处构造的检索mint
的topics
应构造为:
[["Transfer(address,address,uint256)"], ["0x0000000000000000000000000000000000000000"], [],[]]
其中[]
代表匹配任一内容。
我们需要此处还需要一个参数为transaction
,属于TransactionLogTransactionFilter
对象,此对象仅存在两个属性,如下:
toAddresses
交易接受者,此处为NFT合约地址fromAddresses
交易发起者,此处无需设置我们可以构造出如下检索:
query GetApprovalsForAddress { transactionLogs( filter: { topics: [["Transfer(address,address,uint256)"], ["0x0000000000000000000000000000000000000000"], [],[]] transaction: { toAddresses: ["0x6d9c17bc83a416bb992ccc671bebd98d7a76cfc3"] } } limit: 20 ) { totalCount transactionLogs { transactionHash address { address } topics data } } }
但是此检索会出现一个极其致命的错误,即This query timed out.
。这是因为以太坊的区块数据规模过于庞大,所以无法在规定的时间内检索数据。一个简单的方法是限定扫描的区块范围,即设置fromBlock
(扫描区块的起始)和toBlock
(扫描区块的结束)。我们可以通过查询第一个NFT的mint
区块作为起始,此处为16117573
和第 888 个NFT的mint
区块作为终止,此处为16119963
,修正后的检索如下:
query GetApprovalsForAddress { transactionLogs( filter: { topics: [["Transfer(address,address,uint256)"], ["0x0000000000000000000000000000000000000000"], [],[]] transaction: { toAddresses: ["0x6d9c17bc83a416bb992ccc671bebd98d7a76cfc3"] } fromBlock: 16117573 toBlock: 16119963 } limit: 20 ) { totalCount transactionLogs { transactionHash address { address } topics data } } }
返回如下:
读者可以自行构造一系列的复杂的交易查询以获得一些更加具有研究价值的数据。
在此篇文章内,我们主要介绍了以下内容:
Graphql
的基础知识Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。