如何在以太坊上构建GraphQL API

区块链
过去,开发人员通过构建自己的集中式索引服务器从区块链中提取数据,将数据存储在数据库中,并通过API进行公开。这需要大量的工程和硬件资源,并且破坏了分散化所需的重要安全性。

 [[397787]]

本文转载自微信公众号「区块链研究实验室」,作者链三丰。转载本文请联系区块链研究实验室公众号。

过去,开发人员通过构建自己的集中式索引服务器从区块链中提取数据,将数据存储在数据库中,并通过API进行公开。这需要大量的工程和硬件资源,并且破坏了分散化所需的重要安全性。

本文将向大家介绍如何在去中心化Web基础架构-区块链数据上轻松部署API。

分散Web基础架构

分布式互联网的构想和发展方向通常称为Web3,Web3通过以下附加功能增强了我们今天所知道的互联网:

  • 去中心化
  • 可验证的
  • 不信任
  • 自我管理

为了实现分散化,协议定义了网络,这些网络提供了一系列数字服务,例如计算,存储,带宽,身份以及其他没有中介的Web基础结构。这些协议通常分布在多个节点(服务器)上,使大部分希望成为网络并提供服务的任何人都能参与。

在图上建立

在本文中,我们还将研究一种这样的协议Graph,以及如何使用以太坊区块链中存储的数据来构建和部署我们自己的GraphQL API。

Graph是一个索引协议,用于查询以太坊等区块链和IPFS等网络,任何人都可以构建和发布称为子图的开放API,从而使数据易于访问。

子图定义了您希望通过GraphQL API提供的数据,数据源和数据访问模式。作为一个开发人员可以选择使用一个子已经部署的其他开发人员,或者定义和部署自己的子图,并使用它。

子图由几个主要部分组成:

1. GraphQL模式

GraphQL模式定义您要保存和查询的数据类型/实体,您还可以在架构中定义诸如关系和全文搜索功能之类的配置。

2.子图清单(yaml配置)

清单定义了子图索引的智能合约,它们的ABI,这些合约中要注意的事件以及如何将事件数据映射到Graph Node存储并允许查询的实体。

3. AssemblyScript映射

AssemblyScript映射使您可以保存要使用架构中定义的实体类型建立索引的数据;该图表CLI还使用子图的模式的组合与智能合约的ABI一起产生AssemblyScript类型。

开始建造

现在我们对Graph及其工作原理有了很好的了解,让我们开始编写一些代码。

在本教程中,我们将构建一个子图,用于从Zora智能合约查询NTF数据,实现用于获取NFT及其所有者的查询,并在它们之间建立关系。

先决条件:

为了在本教程中取得成功,您应该在计算机上安装Node.js,我建议您使用NVM或FNM管理Node.js的版本。

在图资源管理器中创建图项目

首先,打开Graph Explorer,然后登录或创建一个新帐户。接下来,转到仪表板,然后单击“添加子图”以创建一个新的子图。

使用以下属性配置子图:

  1. 子图名称-Zoranft子图
  2. 字幕-用于查询NFT的子图
  3. 可选-填写说明和GITHUB URL属性

使用Graph CLI初始化新的子图

接下来,安装Graph CLI:

$ npm install -g @graphprotocol/graph-cli 
or 
$ yarn global add @graphprotocol/graph-cli 
  • 1.
  • 2.
  • 3.

安装Graph CLI后,您可以使用Graph CLIinit命令初始化一个新的子图。

两种方法:

1 从示例子图中

$ graph init --from-example <GITHUB_USERNAME>/<SUBGRAPH_NAME> [<DIRECTORY>] 
  • 1.

2 来自现有的智能合约

如果您已经将智能合约部署到以太坊主网或测试网之一,则从该合约初始化新的子图是启动和运行的简便方法。

$ graph init --from-contract <CONTRACT_ADDRESS> \ 
  [--network <ETHEREUM_NETWORK>] \ 
  [--abi <FILE>] \ 
  <GITHUB_USER>/<SUBGRAPH_NAME> [<DIRECTORY>] 
  • 1.
  • 2.
  • 3.
  • 4.

在我们的例子中,我们将使用Zora令牌合约,因此我们可以通过使用--from-contract标志传递合约地址来从该合约地址进行初始化:

$ graph init --from-contract 0xabEFBc9fD2F806065b4f3C237d4b59D9A97Bcac7 --network mainnet  \ 
--contract-name Token --index-events 
? Subgraph name › your-username/Zoranftsubgraph 
? Directory to create the subgraph in › Zoranftsubgraph 
? Ethereum network › Mainnet 
? Contract address › 0xabEFBc9fD2F806065b4f3C237d4b59D9A97Bcac7 
? Contract Name · Token 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

此命令将根据作为参数传入的合同地址生成一个基本子图--from-contract。通过使用此合同地址,CLI将在项目中初始化一些内容以帮助您入门。

子图的主要配置和定义位于subgraph.yaml文件中,子图代码库由几个文件组成:

  • subgraph.yaml:包含子图清单的YAML文件。
  • schema.graphql:一个GraphQL架构,用于定义为子图存储的数据以及如何通过GraphQL查询数据。
  • AssemblyScript映射:从以太坊中的事件数据转换为架构中定义的实体的AssemblyScript代码。

我们将使用的subgraph.yaml中的条目是:

  • description(可选):子图是什么的可读描述,子图部署到Hosted Service时,图资源管理器将显示此描述。
  • repository(可选):可在其中找到子图清单的存储库的URL。
  • dataSources.source:子图来源的智能合约的地址,以及要使用的智能合约的abi。
  • dataSources.source.startBlock(可选):数据源从其开始索引的块的编号。
  • dataSources.mapping.entities:数据源写入存储的实体,每个实体的架构都在schema.graphql文件中定义。
  • dataSources.mapping.abis:一个或多个命名ABI文件,用于源合同以及您在映射中与之交互的任何其他智能合同。
  • dataSources.mapping.eventHandlers:列出该子图所响应的智能合约事件以及映射中的处理程序(示例中为./src/mapping.ts),这些处理程序将这些事件转换为商店中的实体。

定义实体

使用The Graph,您可以在schema.graphql中定义实体类型,并且Graph Node将生成用于查询该实体类型的单个实例和集合的顶级字段。每种应为实体的类型都必须使用@entity指令进行注释。

我们将要建立索引的实体/数据是Token和User。这样,我们可以索引用户以及用户自己创建的令牌。

为此,请使用以下代码更新schema.graphql:

type Token @entity { 
  id: ID! 
  tokenID: BigInt
  contentURI: String! 
  metadataURI: String! 
  creator: User
  owner: User

type User @entity { 
  id: ID! 
  tokens: [Token!]! @derivedFrom(field: "owner"
  created: [Token!]! @derivedFrom(field: "creator"

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

通过@derivedFrom(来自文档)通过“关系”

可以通过@derivedFrom字段在实体上定义反向查找。这会在实体上创建一个虚拟字段,可以查询该虚拟字段,但无法通过映射API手动设置。

相反,它是从另一个实体上定义的关系派生的。对于此类关系,存储关系的两边几乎没有意义,并且仅存储一侧而派生另一侧时,索引和查询性能都将更好。

现在,我们已经为我们的应用程序创建了GraphQL模式,我们可以在本地生成实体,以开始在mappingsCLI所创建的实体中使用:

graph codegen 
  • 1.

为了使工作中的智能合约,事件和实体变得容易且类型安全,Graph CLI从子图的GraphQL模式和数据源中包含的合约ABI的组合中生成AssemblyScript类型。

使用实体和映射更新子图

现在,我们可以配置subgraph.yaml以使用我们刚刚创建的实体并配置它们的映射。

为此,请先dataSources.mapping.entities使用User和Token实体更新字段:

entities: 
  - Token 
  - User 
  • 1.
  • 2.
  • 3.

接下来,更新,dataSources.mapping.eventHandlers使其仅包括以下两个事件处理程序:

eventHandlers: 
  - event: TokenURIUpdated(indexed uint256,address,string) 
    handler: handleTokenURIUpdated 
  - event: Transfer(indexed address,indexed address,indexed uint256) 
    handler: handleTransfer 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

最后,更新配置以添加startBlock:

source: 
  address: "0xabEFBc9fD2F806065b4f3C237d4b59D9A97Bcac7" 
  abi: Token 
  startBlock: 11565020 
  • 1.
  • 2.
  • 3.
  • 4.

Assemblyscript映射

接下来,打开src / mappings.ts来编写我们在子图subgraph中定义的映射eventHandlers。

使用以下代码更新文件:

import { 
  TokenURIUpdated as TokenURIUpdatedEvent, 
  Transfer as TransferEvent, 
  Token as TokenContract 
from "../generated/Token/Token" 
import { 
  Token, User 
from '../generated/schema' 
export function handleTokenURIUpdated(event: TokenURIUpdatedEvent): void { 
  let token = Token.load(event.params._tokenId.toString()); 
  token.contentURI = event.params._uri; 
  token.save(); 

export function handleTransfer(event: TransferEvent): void { 
  let token = Token.load(event.params.tokenId.toString()); 
  if (!token) { 
    token = new Token(event.params.tokenId.toString()); 
    token.creator = event.params.to.toHexString(); 
    token.tokenID = event.params.tokenId; 
    let tokenContract = TokenContract.bind(event.address); 
    token.contentURI = tokenContract.tokenURI(event.params.tokenId); 
    token.metadataURI = tokenContract.tokenMetadataURI(event.params.tokenId); 
  } 
  token.owner = event.params.to.toHexString(); 
  token.save(); 
  let user = User.load(event.params.to.toHexString()); 
  if (!user) { 
    user = new User(event.params.to.toHexString()); 
    user.save(); 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

这些映射将处理创建,传输或更新新令牌的事件。当这些事件触发时,映射会将数据保存到子图中。

运行构建

接下来,让我们运行一个构建以确保正确配置了所有内容。为此,请运行以下build命令:

$ graph build 
  • 1.

如果构建成功,则应该在根目录中看到一个新的构建文件夹。

部署子图

要进行部署,我们可以deploy使用Graph CLI运行该命令。要进行部署,您首先需要为在Graph Explorer中创建的子图复制Access令牌:

接下来,运行以下命令:

$ graph auth https://api.thegraph.com/deploy/ <ACCESS_TOKEN> 
$ yarn deploy 
  • 1.
  • 2.

部署子图后,您应该看到它显示在您的仪表板中:

当您单击子图时,它应该打开Graph资源管理器:

查询数据

现在我们位于仪表板中,我们应该能够开始查询数据了。运行以下查询以获取令牌及其元数据的列表:


  tokens { 
    id 
    tokenID 
    contentURI 
    metadataURI 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

我们还可以配置订单方向:


  tokens( 
    orderBy:id, 
    orderDirection: desc 
  ) { 
    id 
    tokenID 
    contentURI 
    metadataURI 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

或选择跳过某些结果以实现一些基本分页:


  tokens( 
    skip: 100, 
    orderBy:id, 
    orderDirection: desc 
  ) { 
    id 
    tokenID 
    contentURI 
    metadataURI 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

或查询用户及其相关内容:


  users { 
    id 
    tokens { 
      id 
      contentURI 
    } 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

更新子图

如果我们想要对子图进行一些更改然后重新部署,我们应该怎么办?假设我们要向子图添加新功能,假设我们除了现有的查询功能外,还想添加该功能以按创建NFT的时间戳进行排序。

为此,我们需要先向实体添加一个新createdAtTimestamp字段Token:

type Token @entity { 
  id: ID! 
  tokenID: BigInt
  contentURI: String! 
  metadataURI: String! 
  creator: User
  owner: User
  "Add new createdAtTimesamp field" 
  createdAtTimestamp: BigInt

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

现在,我们可以重新运行代码生成:

graph codegen 
  • 1.

接下来,我们需要更新映射以保存此新字段:

// update the handleTransfer function to add the createdAtTimestamp to the token object 
export function handleTransfer(event: TransferEvent): void { 
  let token = Token.load(event.params.tokenId.toString()); 
  if (!token) { 
    token = new Token(event.params.tokenId.toString()); 
    token.creator = event.params.to.toHexString(); 
    token.tokenID = event.params.tokenId; 
    // Add the createdAtTimestamp to the token object 
    token.createdAtTimestamp = event.block.timestamp
    let tokenContract = TokenContract.bind(event.address); 
    token.contentURI = tokenContract.tokenURI(event.params.tokenId); 
    token.metadataURI = tokenContract.tokenMetadataURI(event.params.tokenId); 
  } 
  token.owner = event.params.to.toHexString(); 
  token.save(); 
  let user = User.load(event.params.to.toHexString()); 
  if (!user) { 
    user = new User(event.params.to.toHexString()); 
    user.save(); 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

现在我们可以重新部署子图:

$ yarn deploy 
  • 1.

子图重新部署后,我们现在可以按时间戳查询以查看最近创建的NFTS:


  tokens( 
    orderBy:createdAtTimestamp, 
    orderDirection: desc 
  ) { 
    id 
    tokenID 
    contentURI 
    metadataURI 
  } 
}} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

 【编辑推荐】

 

责任编辑:武晓燕 来源: 区块链研究实验室
相关推荐

2023-05-05 08:00:00

2020-09-28 06:57:39

Node.jsGraphQLAPI

2021-05-13 16:13:21

区块链以太坊NFT

2023-10-12 09:05:11

2021-05-02 22:19:46

以太坊比特币加密货币

2020-10-27 18:45:45

GolangGraphQ开发

2018-04-23 14:31:02

微服务GraphQLBFF

2021-05-22 22:57:24

以太坊加密货币比特币

2021-12-08 13:57:29

以太坊加密货币比特币

2021-05-03 23:32:55

以太坊区块链比特币

2021-10-26 00:27:28

Python以太坊智能

2022-10-18 08:00:00

2020-04-23 08:55:01

LinuxGradle工具

2021-04-19 12:31:04

太坊数据QuestDB

2022-12-05 07:13:44

2024-01-09 09:09:45

RESTGraphQL

2021-04-29 16:11:14

以太坊共识链验证者

2018-06-01 09:17:52

区块链以太坊

2022-02-23 23:43:15

网络安全IT云安全

2017-02-09 09:30:18

UbuntuDokuWikiApache
点赞
收藏

51CTO技术栈公众号