Web3系列教程之进阶篇---11. The Graph索引协议

The Graph 是区块链的一个去中心化的查询协议和索引服务。它允许开发人员轻松地跟踪各种网络上的智能合约所发出的事件,并编写定制的数据转换脚本,这些脚本是实时运行的。这些数据也通过一个简单的GraphQL API提供,然后开发者可以用它来在他们的前端显示东西。
先决条件
- 我们将使用yarn,它是一个和npm一样的包管理器。
- 如果你的电脑还没有安装yarn的话,请从这里安装yarn
- 请观看这个40分钟的GraphQL教程
- 如果你不知道axios是什么,请看这个简短的教程
- 你应该已经完成了 Chainlink VRF 的教程
它是如何工作的

- dApp发送一个交易,之后一些数据被存储在智能合约中。然后这个智能合约发出一个或多个事件。
- Graph的节点不断扫描以太坊的新区块和这些区块可能包含的你的子图的数据。
- 如果节点找到你要找的并在你的子图中定义的事件,它就运行你定义的数据转换脚本(映射)。映射是一个WASM(Web assembly)模块,它响应事件,在图式节点上创建或更新数据Entities。
- 我们可以使用 GraphQL Endpoint查询Graph的节点,以获得这些数据
构建
- 我们将使用你在Chainlink VRF教程中创建的名为RandomWinnerGame的文件夹。
- 在你的RandomWinnerGame文件夹内创建一个abi.json文件(你将需要这个文件来整合你的图)并复制以下内容。
- 请注意,这是你在Chainlink VRF教程中创建的RandomWinnerGame合同的ABI。
- 因此,你的最终文件夹结构应该看起来像这样。
|  |  | 
- 
要创建你的子图,你将需要去The Graph’s Hosted Service 
- 
使用您的github登录并访问 My Dashboard标签

- 单击 Add Subgraph,填写信息并创建子图。

- 当你的子图被创建后,它将向你显示Install、Init和Deploy中的一些命令

- 在你的终端执行这个命令,指向RandomWinnerGame文件夹。
|  |  | 
- 之后执行这个命令,但用你的Github用户名替换GITHUB_USERNAME,用你在Chainlink VRF教程中部署的RandomWinnerGame合约的地址替换YOUR_RANDOM_WINNER_GAME_CONTRACT_ADDRESS。之后的所有问题都按回车键 :)
|  |  | 

- 对于部署密钥,进入 The Graph’s Hosted Service,点击 My Dashboard,复制Access Token并将其粘贴到Deploy Key上。
|  |  | 

- 现在要执行的最后两条命令是
|  |  | 
你可以回到The Graph’s Hosted Service ,点击 My Dashboard,你将能够看到你的图表,因为它现在已经部署了🚀 👀

你已经部署了你的第一个图表!!!。
现在,有趣的部分来了,我们将把The Graph提供给我们的默认代码修改为可以帮助我们跟踪合同事件的代码。
让我们开始吧
- 
打开 graph文件夹中的subgraph.yaml,在abi: RamdomWinnerGame一行之后添加一个startBlock到yaml文件中。为了获得startBlock,你需要到 Mumbai PolygonScan中搜索你的合同地址,然后你需要复制你的合同所在区块的区块号。
- 
开始区块没有默认设置,但因为我们知道我们只需要跟踪合同部署区块的事件,所以我们不需要同步整个区块链,只需要同步合同部署后的部分来跟踪事件。 

|  |  | 
你的最终文件应该是这样的
- 好了,现在是时候创建一些Entities了。Entities是定义你的数据将如何存储在The Graph's nodes的结构的对象。如果你想阅读更多关于它们的信息,请点击这个链接
我们将需要一个Entity,它可以涵盖我们事件中的所有变量,以便我们可以跟踪所有的变量。打开schema.graphql文件,用以下几行代码替换已有的代码。
|  |  | 
- 这里的ID是一个游戏的唯一标识符,将等同于我们在合同中的gameId变量。
- maxPlayers将记录这个游戏中允许有多少最大的玩家。
- entryFee是进入游戏的费用,它是一个BigInt,因为在我们的合同中,- entryFee是一个- uint256,它是一个BigNumber。
- winner是游戏中赢家的地址,定义为Bytes,因为地址是一个十六进制的字符串。
- requestId也是一个十六进制的字符串,因此被定义为- Bytes
- players是游戏中玩家的地址列表,由于每个地址都是一个十六进制的字符串,我们将玩家符号化为一个字节数组。
- 另外,请注意!表示必须的变量,我们将maxPlayers、entryFee、player和id标记为必须,因为当Game最初启动时,它将发出GameStarted事件,该事件将发出这三个变量(maxPlayers、entryFee和id),所以没有这三个变量,一个Game实体永远无法被创建,对于player阵列,它将被我们初始化为一个空阵列。
- winner和- requestId将与- GameEnded事件一起出现,- players将跟踪每个- player address,这是由- PlayerJoined事件发出的。
如果你想了解更多的类型,你可以访问这个链接
好了,现在我们已经让the graph知道我们将追踪什么样的数据,以及它将包含什么🥳 🥳 🥳
现在是查询这些数据的时候了🎉
Graph 有一个惊人的功能,给定的Entity可以为你自动生成大块的代码!!。
这不是很神奇吗?让我们使用这个功能。在你的终端指向 the graph目录,执行以下命令
|  |  | 
- 在这之后,The Graph将为你创建大部分的代码,希望你的映射。
- 如果你看一下src中的mapping.ts,graph 会为你创建一些函数,每个都指向你在合同中创建的一个事件。
- 每次Graph发现与这些函数有关的事件时,这些函数都会被调用。
- 我们将为这些函数添加一些代码,这样我们就可以在事件来临时存储数据。
- 复制以下几行代码到你的mapping.ts中
|  |  | 
- 
让我们了解 handleGameEnded函数中发生了什么- 它接受GameEnded事件并期望void被返回,这意味着函数没有返回任何内容
- 它从Graph的数据库中加载一个Game对象,其ID等于Graph检测到的事件中存在的gameId。
- 如果具有给定的实体id不存在,则从函数返回并且不做任何事情
- 如果存在,则将事件中的获胜者和 requestId 设置到我们的游戏对象中(注意GameEnded事件有获胜者和 requestId)
- 然后将这个更新的游戏对象保存回Graph's DB
- 对于每个游戏,都会有一个独特的Game对象,该对象将具有独特的gameId
 
- 它接受
- 
现在让我们看看在 handlePlayerJoined中发生了什么。- 它从Graph的数据库中加载一个Game对象,其ID等于Graph检测到的事件中存在的gameId。
- 如果一个具有给定ID的实体不存在,则从函数中返回,不做任何事情。
- 为了实际更新玩家的数组,我们需要重新分配包含数组的实体上的属性(即玩家),类似于我们给实体上的其他属性(如maxPlayers)赋值的方法。因此,我们需要创建一个临时数组,其中包含所有现有的entity.player元素,推送到该数组,并重新分配entity.player,使其等于新数组。
- 然后将这个更新的游戏对象保存到Graph's DB中。
 
- 它从Graph的数据库中加载一个
- 
现在让我们看看 handleGameStarted中发生了什么- 它从Graph的数据库中加载一个Game对象,其ID等于Graph检测到的事件中存在的gameId。
- 如果一个这样的实体不存在,就创建一个新的,同时初始化玩家数组
- 然后在我们的游戏对象中设置事件中的maxPlayer和entryFee。
- 将这个更新的游戏对象保存到Graph's DB。
 
- 它从Graph的数据库中加载一个
现在你可以去你的终端,指向graph文件夹,执行以下命令。
|  |  | 
- 部署好The Graph’s Hosted Service,点击 My Dashboard,你就可以看到你的图表了。
- 点击你的图表,确保它显示已同步,如果没有,请等待它被同步后再继续。

网站
- 
为了开发该网站,我们将使用React 和Next Js.。React是一个用于制作网站的javascript框架,而Next Js是建立在React之上的。 
- 
首先,你将需要创建一个新的 next应用程序。你的文件夹结构应该是这样的
|  |  | 
- 要创建这个next-app,在终端指向RandomWinnerGame文件夹并输入
|  |  | 
并对所有问题按回车键
- 现在要运行该应用程序,在终端执行这些命令
|  |  | 
- 现在让我们安装Web3Modal库(https://github.com/Web3Modal/web3modal)。Web3Modal是一个易于使用的库,帮助开发者通过简单的可定制配置在他们的应用程序中添加对多个供应商的支持。默认情况下,Web3Modal库支持注入的提供者,如(Metamask、Dapper、Gnosis Safe、Frame、Web3 Browsers等),你也可以轻松配置该库以支持Portis、Fortmatic、Squarelink、Torus、Authereum、D’CENT Wallet和Arkane。打开终端,指向my-app目录,执行以下命令
|  |  | 
- 在同一终端中也安装ethers.js
|  |  | 
- 我们还将使用axios来向the graph,发送请求,所以让我们安装它吧
|  |  | 
- 
在你的公共文件夹中,下载这张图片。确保下载的图像名称为 randomWinner.png。
- 
现在去 style文件夹,用以下代码替换Home.modules.css文件的所有内容,这将给你的dapp添加一些样式。
- 
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68.main { min-height: 90vh; display: flex; flex-direction: row; justify-content: center; align-items: center; font-family: "Courier New", Courier, monospace; } .footer { display: flex; padding: 2rem 0; border-top: 1px solid #eaeaea; justify-content: center; align-items: center; } .input { width: 200px; height: 100%; padding: 1%; margin: 2%; box-shadow: 0 0 15px 4px rgba(0, 0, 0, 0.06); border-radius: 10px; } .image { width: 70%; height: 50%; margin-left: 20%; } .title { font-size: 2rem; margin: 2rem 1rem; } .description { line-height: 1; margin: 2rem 1rem; font-size: 1.2rem; } .log { line-height: 1rem; margin: 1rem 1rem; font-size: 1rem; } .button { border-radius: 4px; background-color: blue; border: none; color: #ffffff; font-size: 15px; padding: 8px; width: 200px; cursor: pointer; margin: 2rem 1rem; } @media (max-width: 1000px) { .main { width: 100%; flex-direction: column; justify-content: center; align-items: center; } }
- 
现在让我们写一些代码来查询 the graph,,在你的 my-app文件夹内创建一个新的文件夹,并命名为queries。在queries文件夹中创建一个名为index.js的新文件,并粘贴以下几行代码。
|  |  | 
- 正如你所看到的,我们创建了一个GraphQL查询,我们说我们想要一个game对象,其中的数据按Id(也就是gameId)降序排列,我们想从这个有序的数据中获得第一个游戏。
- 让我们用一个例子来简化这个问题。假设你有三个游戏对象存储在The Graph's内。
|  |  | 
- 
现在你希望每次都是最新的游戏。为了得到最新的游戏,你首先要按id排序,然后把这些数据按降序排列,这样gameId 4就可以排在最前面(它将是当前的游戏),然后我们说 first:1,因为我们只想要gameId 4对象,我们不关心旧游戏。
- 
你实际上可以看到这个查询在 the graph'的托管服务上工作。让我们试着这样做。
- 
我已经部署了一个graph,让我们看看graph,并使用查询来查询它,进入这个链接 
- 
用我们的查询替换示例查询,并点击紫色的播放按钮 

- 
你可以看到我的graph 出现了一些数据,游戏的ID是4。 
- 
简单吧?是的,确实如此 😎 
- 
你可以通过 My dashboard进入你的图表,做同样的事情🚀,这将很有趣。
- 
让我们继续… 
- 
通过 Graph已经提供的UI进行操作是很好的,但是要在我们的前端使用这个,我们需要写一个axios post request,这样我们就可以从 the graph中获得这些数据。
- 
创建一个名为 utils的新文件夹,并在该文件夹中创建一个名为index.js的新文件。在index.js文件中复制以下几行代码。
|  |  | 
- 让我们试着理解这个函数中发生了什么
- 它需要一个SUBGRAPH_URL,你需要将 “YOUR-SUBGRAPH-URL “替换为你的子图的URL,你可以通过进入图的hosted service ,点击My dashboard,然后点击你的图来获得。复制Queries(HTTP)下的网址
 
- 它需要一个SUBGRAPH_URL,你需要将 “YOUR-SUBGRAPH-URL “替换为你的子图的URL,你可以通过进入图的hosted service ,点击

然后它用我们用axios post request创建的查询来调用这个网址
然后,它处理任何错误,如果没有错误,则发回答复。
- 现在打开page文件夹下的index.js文件,粘贴以下代码,代码的解释可以在评论中找到。
|  |  | 
- 
现在在my-app文件夹下创建一个新的文件夹,并将其命名为 constants。
- 
在 constants文件夹中创建一个index.js文件,并粘贴以下代码。- 替换 "address of your RandomWinnerGame contract"与您部署和保存到您的记事本的RandomWinnerGame合同的地址。
- 替换---your abi---为你的RandomWinnerGame合同的ABI。为了得到你的合同的ABI,去你的hardhat-tutorial/artifacts/contracts/RandomWinnerGame.sol文件夹,从RandomWinnerGame.json文件得到"abi"键下的数组标记。
 
- 替换 
|  |  | 
- 现在,在你的终端,也就是指向my-app文件夹,执行
|  |  | 
- 你的RandomWinnerGame dapp现在应该没有错误地工作了 🚀
- 玩游戏玩得开心
推送到github
在继续之前,请确保你已经将所有的代码推送到github :)
部署你的dApp
现在我们将部署你的DApp,这样大家就可以看到你的网站,你也可以和所有LearnWeb3 DAO的朋友分享。
- 
到https://vercel.com/,用你的GitHub登录。 
- 
然后点击 New Project按钮,然后选择你的RandomWinnerGame 仓库。
- 
在配置你的新项目时,Vercel将允许你自定义你的 Root Directory
- 
点击 Root Directory旁边的Edit,并将其设置为my-app
- 
选择框架为 Next.js
- 
点击部署 

- 
现在,你可以通过进入你的仪表板,选择你的项目,并从那里复制 domain来查看你部署的网站!
- 
与大家分享这个域名,让大家一起玩游戏 🚀🚀🚀🚀 
原文: https://www.learnweb3.io/tracks/junior/the-graph-protocol


