The Graph[1]
是區塊鏈的一個去中心化的查詢協議和索引服務。它允許開發人員輕鬆地跟蹤各種網路上的智慧合約所發出的事件,並編寫定製的資料轉換指令碼,這些指令碼是實時執行的。這些資料也透過一個簡單的GraphQL API提供,然後開發者可以用它來在他們的前端顯示東西。
先決條件
• 我們將使用yarn,它是一個和npm一樣的包管理器。
• 如果你的電腦還沒有安裝yarn的話,請從
這裡[2]
安裝yarn
• 請觀看這個40分鐘的
GraphQL教程[3]
• 如果你不知道axios是什麼,請看這個
簡短的教程[4]
• 你應該已經完成了
Chainlink VRF [5]
的教程
它是如何工作的
(引用自 The Graph 網站)
1。 dApp傳送一個交易,之後一些資料被儲存在智慧合約中。然後這個智慧合約發出一個或多個事件。
2。 Graph的節點不斷掃描以太坊的新區塊和這些區塊可能包含的你的子圖的資料。
3。 如果節點找到你要找的並在你的子圖中定義的事件,它就執行你定義的資料轉換指令碼(對映)。對映是一個WASM(Web assembly)模組,它響應事件,在圖式節點上建立或更新資料
Entities
。
4。 我們可以使用
GraphQL Endpoint[6]
查詢Graph的節點,以獲得這些資料
構建
• 我們將使用你在
Chainlink VRF
教程中建立的名為
RandomWinnerGame
的資料夾。
• 在你的
RandomWinnerGame
資料夾內建立一個abi。json檔案(你將需要這個檔案來整合你的圖)並複製以下
內容[7]
。
• 請注意,這是你在Chainlink VRF教程中建立的
RandomWinnerGame
合同的ABI。
• 因此,你的最終資料夾結構應該看起來像這樣。
RandomWinnerGame - hardhat-tutorial - abi。json
• 要建立你的子圖,你將需要去
The Graph‘s Hosted Service[8]
• 使用您的github登入並訪問
My Dashboard
標籤
• 單擊
Add Subgraph
,填寫資訊並建立子圖。
• 當你的子圖被建立後,它將向你顯示
Install
、
Init
和
Deploy
中的一些命令
• 在你的終端執行這個命令,指向
RandomWinnerGame
資料夾。
yarn global add @graphprotocol/graph-cli
• 之後執行這個命令,但用你的Github使用者名稱替換
GITHUB_USERNAME
,用你在Chainlink VRF教程中部署的RandomWinnerGame合約的地址替換
YOUR_RANDOM_WINNER_GAME_CONTRACT_ADDRESS
。之後的所有問題都按回車鍵 :)
graph init ——contract-name RandomWinnerGame ——product hosted-service GITHUB_USERNAME/Learnweb3 ——from-contract YOUR_RANDOM_WINNER_GAME_CONTRACT_ADDRESS ——abi 。/abi。json ——network mumbai graph
• 對於部署金鑰,進入
The Graph’s Hosted Service[9]
,點選
My Dashboard
,複製
Access Token
並將其貼上到
Deploy Key
上。
graph auth
• 現在要執行的最後兩條命令是
cd graphyarn deploy
你可以回到
The Graph‘s Hosted Service[10]
,點選
My Dashboard
,你將能夠看到你的圖表,因為它現在已經部署了
你已經部署了你的第一個圖表!!!。
現在,有趣的部分來了,我們將把The Graph提供給我們的預設程式碼修改為可以幫助我們跟蹤合同事件的程式碼。
讓我們開始吧
• 開啟
graph
資料夾中的
subgraph。yaml
,在
abi: RamdomWinnerGame
一行之後新增一個
startBlock
到yaml檔案中。為了獲得startBlock,你需要到
Mumbai PolygonScan[11]
中搜索你的合同地址,然後你需要複製你的合同所在區塊的區塊號。
• 開始區塊沒有預設設定,但因為我們知道我們只需要跟蹤合同部署區塊的事件,所以我們不需要同步整個區塊鏈,只需要同步合同部署後的部分來跟蹤事件。
source: address: “0x889Ef69261272Caa27f0655D0208bAc7055EDAD5” abi: RandomWinnerGame startBlock: BLOCK_NUMBER
你的最終檔案應該是
這樣[12]
的
• 好了,現在是時候建立一些
Entities
了。
Entities
是定義你的資料將如何儲存在
The Graph’s nodes
的結構的物件。如果你想閱讀更多關於它們的資訊,請點選
這個[13]
連結
我們將需要一個
Entity
,它可以涵蓋我們事件中的所有變數,以便我們可以跟蹤所有的變數。開啟
schema。graphql
檔案,用以下幾行程式碼替換已有的程式碼。
type Game @entity { id: ID! maxPlayers: Int! entryFee: BigInt! winner: Bytes requestId: Bytes players: [Bytes!]!}
• 這裡的
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
事件發出的。
如果你想了解更多的型別,你可以訪問這個
連結[14]
好了,現在我們已經讓the graph知道我們將追蹤什麼樣的資料,以及它將包含什麼
現在是查詢這些資料的時候了
Graph 有一個驚人的功能,給定的
Entity
可以為你自動生成大塊的程式碼!!。
這不是很神奇嗎?讓我們使用這個功能。在你的終端指向 the graph目錄,執行以下命令
yarn codegen
• 在這之後,
The Graph
將為你建立大部分的程式碼,希望你的對映。
• 如果你看一下
src
中的
mapping。ts
,graph 會為你建立一些函式,每個都指向你在合同中建立的一個事件。
• 每次Graph發現與這些函式有關的事件時,這些函式都會被呼叫。
• 我們將為這些函式新增一些程式碼,這樣我們就可以在事件來臨時儲存資料。
• 複製以下幾行程式碼到你的
mapping。ts
中
import { BigInt } from “@graphprotocol/graph-ts”;import { PlayerJoined, GameEnded, GameStarted, OwnershipTransferred,} from “。。/generated/RandomWinnerGame/RandomWinnerGame”;import { Game } from “。。/generated/schema”;export function handleGameEnded(event: GameEnded): void { // Entities can be loaded from the store using a string ID; this ID // needs to be unique across all entities of the same type let entity = Game。load(event。params。gameId。toString()); // Entities only exist after they have been saved to the store; // `null` checks allow to create entities on demand if (!entity) { return; } // Entity fields can be set based on event parameters entity。winner = event。params。winner; entity。requestId = event。params。requestId; // Entities can be written to the store with `。save()` entity。save();}export function handlePlayerJoined(event: PlayerJoined): void { // Entities can be loaded from the store using a string ID; this ID // needs to be unique across all entities of the same type let entity = Game。load(event。params。gameId。toString()); // Entities only exist after they have been saved to the store; // `null` checks allow to create entities on demand if (!entity) { return; } // Entity fields can be set based on event parameters let newPlayers = entity。players; newPlayers。push(event。params。player); entity。players = newPlayers; // Entities can be written to the store with `。save()` entity。save();}export function handleGameStarted(event: GameStarted): void { // Entities can be loaded from the store using a string ID; this ID // needs to be unique across all entities of the same type let entity = Game。load(event。params。gameId。toString()); // Entities only exist after they have been saved to the store; // `null` checks allow to create entities on demand if (!entity) { entity = new Game(event。params。gameId。toString()); entity。players = []; } // Entity fields can be set based on event parameters entity。maxPlayers = event。params。maxPlayers; entity。entryFee = event。params。entryFee; // Entities can be written to the store with `。save()` entity。save();}export function handleOwnershipTransferred(event: OwnershipTransferred): void {}
• 讓我們瞭解
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
中。
• 現在讓我們看看
handleGameStarted
中發生了什麼
• 它從Graph的資料庫中載入一個
Game
物件,其ID等於Graph檢測到的事件中存在的
gameId
。
• 如果一個這樣的實體不存在,就建立一個新的,同時初始化玩家陣列• 然後在我們的遊戲物件中設定事件中的maxPlayer和entryFee。• 將這個更新的遊戲物件儲存到
Graph‘s DB
。
現在你可以去你的終端,指向
graph
資料夾,執行以下命令。
yarn deploy
• 部署好
The Graph’s Hosted Service[15]
,點選
My Dashboard
,你就可以看到你的圖表了。
• 點選你的圖表,確保它顯示已同步,如果沒有,請等待它被同步後再繼續。
網站
• 為了開發該網站,我們將使用
React[16]
和
Next Js[17]
。。React是一個用於製作網站的javascript框架,而Next Js是建立在React之上的。
• 首先,你將需要建立一個新的
next
應用程式。你的資料夾結構應該是這樣的
- RandomWinnerGame - graph - hardhat-tutorial - next-app - abi。json
• 要建立這個
next-app
,在終端指向RandomWinnerGame資料夾並輸入
npx create-next-app@latest
並對所有問題按回車鍵
• 現在要執行該應用程式,在終端執行這些命令
cd my-appnpm run dev
• 現在讓我們安裝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
目錄,執行以下命令
npm install web3modal
• 在同一終端中也安裝
ethers。js
npm i ethers
• 我們還將使用
axios
來向
the graph,
傳送請求,所以讓我們安裝它吧
npm i axios
• 在你的公共資料夾中,下載
這張圖片[18]
。確保下載的影象名稱為
randomWinner。png
。
• 現在去
style
資料夾,用以下程式碼替換
Home。modules。css
檔案的所有內容,這將給你的dapp新增一些樣式。
。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
的新檔案,並貼上以下幾行程式碼。
export function FETCH_CREATED_GAME() { return `query { games(orderBy:id, orderDirection:desc, first: 1) { id maxPlayers entryFee winner players } }`;}
• 正如你所看到的,我們建立了一個
GraphQL
查詢,我們說我們想要一個
game
物件,其中的資料按Id(也就是gameId)降序排列,我們想從這個有序的資料中獲得第一個遊戲。
• 讓我們用一個例子來簡化這個問題。假設你有三個遊戲物件儲存在
The Graph’s
內。
{ “id”: “1”, “maxPlayers”: 2, “entryFee”: “10000000000000”, “winner”: “0xdb6eaffa95899b53b27086bd784f3bbfd58ff843” }, { “id”: “2”, “maxPlayers”: 2, “entryFee”: “10000000000000”, “winner”: “0x01573df433484fcbe6325a0c6e051dc62ab107d1” }, { “id”: “3”, “maxPlayers”: 2, “entryFee”: “100000000000000”, “winner”: “0x01573df433484fcbe6325a0c6e051dc62ab107d1” }, { “id”: “4”, “maxPlayers”: 2, “entryFee”: “10”, “winner”: null }
• 現在你希望每次都是最新的遊戲。為了得到最新的遊戲,你首先要按id排序,然後把這些資料按降序排列,這樣gameId 4就可以排在最前面(它將是當前的遊戲),然後我們說
first:1
,因為我們只想要gameId 4物件,我們不關心舊遊戲。
• 你實際上可以看到這個查詢在
the graph‘
的託管服務上工作。讓我們試著這樣做。
• 我已經部署了一個graph,讓我們看看graph,並使用查詢來查詢它,進入
這個連結[19]
• 用我們的查詢替換示例查詢,並點選紫色的播放按鈕
• 你可以看到我的graph 出現了一些資料,遊戲的ID是4。
• 簡單吧?是的,確實如此
• 你可以透過
My dashboard
進入你的圖表,做同樣的事情,這將很有趣。
• 讓我們繼續。。。
• 透過
Graph
已經提供的UI進行操作是很好的,但是要在我們的前端使用這個,我們需要寫一個axios post request,這樣我們就可以從 the graph中獲得這些資料。
• 建立一個名為
utils
的新資料夾,並在該資料夾中建立一個名為
index。js
的新檔案。在
index。js
檔案中複製以下幾行程式碼。
import axios from “axios”;export async function subgraphQuery(query) { try { // Replace YOUR-SUBGRAPH-URL with the url of your subgraph const SUBGRAPH_URL = “YOUR-SUBGRAPH-URL”; const response = await axios。post(SUBGRAPH_URL, { query, }); if (response。data。errors) { console。error(response。data。errors); throw new Error(`Error making subgraph query ${response。data。errors}`); } return response。data。data; } catch (error) { console。error(error); throw new Error(`Could not query the subgraph ${error。message}`); }}
• 讓我們試著理解這個函式中發生了什麼• 它需要一個SUBGRAPH_URL,你需要將 “YOUR-SUBGRAPH-URL ”替換為你的子圖的URL,你可以透過進入圖的
hosted service[20]
,點選
My dashboard
,然後點選你的圖來獲得。複製
Queries(HTTP)
下的網址
然後它用我們用axios post request建立的查詢來呼叫這個網址
然後,它處理任何錯誤,如果沒有錯誤,則發回答覆。
• 現在開啟page資料夾下的index。js檔案,貼上以下程式碼,程式碼的解釋可以在評論中找到。
import { BigNumber, Contract, ethers, providers, utils } from “ethers”;import Head from “next/head”;import React, { useEffect, useRef, useState } from “react”;import Web3Modal from “web3modal”;import { abi, RANDOM_GAME_NFT_CONTRACT_ADDRESS } from “。。/constants”;import { FETCH_CREATED_GAME } from “。。/queries”;import styles from “。。/styles/Home。module。css”;import { subgraphQuery } from “。。/utils”;export default function Home() { const zero = BigNumber。from(“0”); // walletConnected keep track of whether the user’s wallet is connected or not const [walletConnected, setWalletConnected] = useState(false); // loading is set to true when we are waiting for a transaction to get mined const [loading, setLoading] = useState(false); // boolean to keep track of whether the current connected account is owner or not const [isOwner, setIsOwner] = useState(false); // entryFee is the ether required to enter a game const [entryFee, setEntryFee] = useState(zero); // maxPlayers is the max number of players that can play the game const [maxPlayers, setMaxPlayers] = useState(0); // Checks if a game started or not const [gameStarted, setGameStarted] = useState(false); // Players that joined the game const [players, setPlayers] = useState([]); // Winner of the game const [winner, setWinner] = useState(); // Keep a track of all the logs for a given game const [logs, setLogs] = useState([]); // Create a reference to the Web3 Modal (used for connecting to Metamask) which persists as long as the page is open const web3ModalRef = useRef(); // This is used to force react to re render the page when we want to // in our case we will use force update to show new logs const forceUpdate = React。useReducer(() => ({}), {})[1]; /* connectWallet: Connects the MetaMask wallet */ const connectWallet = async () => { try { // Get the provider from web3Modal, which in our case is MetaMask // When used for the first time, it prompts the user to connect their wallet await getProviderOrSigner(); setWalletConnected(true); } catch (err) { console。error(err); } }; /** * Returns a Provider or Signer object representing the Ethereum RPC with or without the * signing capabilities of metamask attached * * A `Provider` is needed to interact with the blockchain - reading transactions, reading balances, reading state, etc。 * * A `Signer` is a special type of Provider used in case a `write` transaction needs to be made to the blockchain, which involves the connected account * needing to make a digital signature to authorize the transaction being sent。 Metamask exposes a Signer API to allow your website to * request signatures from the user using Signer functions。 * * @param {*} needSigner - True if you need the signer, default false otherwise */ const getProviderOrSigner = async (needSigner = false) => { // Connect to Metamask // Since we store `web3Modal` as a reference, we need to access the `current` value to get access to the underlying object const provider = await web3ModalRef。current。connect(); const web3Provider = new providers。Web3Provider(provider); // If user is not connected to the Mumbai network, let them know and throw an error const { chainId } = await web3Provider。getNetwork(); if (chainId !== 80001) { window。alert(“Change the network to Mumbai”); throw new Error(“Change network to Mumbai”); } if (needSigner) { const signer = web3Provider。getSigner(); return signer; } return web3Provider; }; /** * startGame: Is called by the owner to start the game */ const startGame = async () => { try { // Get the signer from web3Modal, which in our case is MetaMask // No need for the Signer here, as we are only reading state from the blockchain const signer = await getProviderOrSigner(true); // We connect to the Contract using a signer because we want the owner to // sign the transaction const randomGameNFTContract = new Contract( RANDOM_GAME_NFT_CONTRACT_ADDRESS, abi, signer ); setLoading(true); // call the startGame function from the contract const tx = await randomGameNFTContract。startGame(maxPlayers, entryFee); await tx。wait(); setLoading(false); } catch (err) { console。error(err); setLoading(false); } }; /** * startGame: Is called by a player to join the game */ const joinGame = async () => { try { // Get the signer from web3Modal, which in our case is MetaMask // No need for the Signer here, as we are only reading state from the blockchain const signer = await getProviderOrSigner(true); // We connect to the Contract using a signer because we want the owner to // sign the transaction const randomGameNFTContract = new Contract( RANDOM_GAME_NFT_CONTRACT_ADDRESS, abi, signer ); setLoading(true); // call the startGame function from the contract const tx = await randomGameNFTContract。joinGame({ value: entryFee, }); await tx。wait(); setLoading(false); } catch (error) { console。error(error); setLoading(false); } }; /** * checkIfGameStarted checks if the game has started or not and intializes the logs * for the game */ const checkIfGameStarted = async () => { try { // Get the provider from web3Modal, which in our case is MetaMask // No need for the Signer here, as we are only reading state from the blockchain const provider = await getProviderOrSigner(); // We connect to the Contract using a Provider, so we will only // have read-only access to the Contract const randomGameNFTContract = new Contract( RANDOM_GAME_NFT_CONTRACT_ADDRESS, abi, provider ); // read the gameStarted boolean from the contract const _gameStarted = await randomGameNFTContract。gameStarted(); const _gameArray = await subgraphQuery(FETCH_CREATED_GAME()); const _game = _gameArray。games[0]; let _logs = []; // Initialize the logs array and query the graph for current gameID if (_gameStarted) { _logs = [`Game has started with ID: ${_game。id}`]; if (_game。players && _game。players。length > 0) { _logs。push( `${_game。players。length} / ${_game。maxPlayers} already joined ` ); _game。players。forEach((player) => { _logs。push(`${player} joined ♂️`); }); } setEntryFee(BigNumber。from(_game。entryFee)); setMaxPlayers(_game。maxPlayers); } else if (!gameStarted && _game。winner) { _logs = [ `Last game has ended with ID: ${_game。id}`, `Winner is: ${_game。winner} `, `Waiting for host to start new game。。。。`, ]; setWinner(_game。winner); } setLogs(_logs); setPlayers(_game。players); setGameStarted(_gameStarted); forceUpdate(); } catch (error) { console。error(error); } }; /** * getOwner: calls the contract to retrieve the owner */ const getOwner = async () => { try { // Get the provider from web3Modal, which in our case is MetaMask // No need for the Signer here, as we are only reading state from the blockchain const provider = await getProviderOrSigner(); // We connect to the Contract using a Provider, so we will only // have read-only access to the Contract const randomGameNFTContract = new Contract( RANDOM_GAME_NFT_CONTRACT_ADDRESS, abi, provider ); // call the owner function from the contract const _owner = await randomGameNFTContract。owner(); // We will get the signer now to extract the address of the currently connected MetaMask account const signer = await getProviderOrSigner(true); // Get the address associated to the signer which is connected to MetaMask const address = await signer。getAddress(); if (address。toLowerCase() === _owner。toLowerCase()) { setIsOwner(true); } } catch (err) { console。error(err。message); } }; // useEffects are used to react to changes in state of the website // The array at the end of function call represents what state changes will trigger this effect // In this case, whenever the value of `walletConnected` changes - this effect will be called useEffect(() => { // if wallet is not connected, create a new instance of Web3Modal and connect the MetaMask wallet if (!walletConnected) { // Assign the Web3Modal class to the reference object by setting it‘s `current` value // The `current` value is persisted throughout as long as this page is open web3ModalRef。current = new Web3Modal({ network: “mumbai”, providerOptions: {}, disableInjectedProvider: false, }); connectWallet(); getOwner(); checkIfGameStarted(); setInterval(() => { checkIfGameStarted(); }, 2000); } }, [walletConnected]); /* renderButton: Returns a button based on the state of the dapp */ const renderButton = () => { // If wallet is not connected, return a button which allows them to connect their wllet if (!walletConnected) { return ( ); } // If we are currently waiting for something, return a loading button if (loading) { return ; } // Render when the game has started if (gameStarted) { if (players。length === maxPlayers) { return ( ); } return (
Welcome to Random Winner Game!
• 現在在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”
鍵下的陣列標記。
export const abi =——-your abi——-export const RANDOM_GAME_NFT_CONTRACT_ADDRESS = “address of your RandomWinnerGame contract”
• 現在,在你的終端,也就是指向
my-app
資料夾,執行
npm run dev
• 你的RandomWinnerGame dapp現在應該沒有錯誤地工作了
• 玩遊戲玩得開心
推送到github
在繼續之前,請確保你已經將所有的程式碼
推送到github[21]
:)
部署你的dApp
現在我們將部署你的DApp,這樣大家就可以看到你的網站,你也可以和所有LearnWeb3 DAO的朋友分享。
• 到https://vercel。com/,用你的GitHub登入。
• 然後點選
New Project
按鈕,然後選擇你的RandomWinnerGame 倉庫。
• 在配置你的新專案時,Vercel將允許你自定義你的
Root Directory
• 點選
Root Directory
旁邊的
Edit
,並將其設定為
my-app
• 選擇框架為
Next。js
• 點選部署
• 現在,你可以透過進入你的儀表板,選擇你的專案,並從那裡複製
domain
來檢視你部署的網站!
• 與大家分享這個域名,讓大家一起玩遊戲
引用連結
[1] The Graph:
https://thegraph。com/
[2] 這裡:
https://classic。yarnpkg。com/lang/en/docs/install/#mac-stable
[3] GraphQL教程:
https://www。youtube。com/watch?v=ZQL7tL2S0oQ
[4] 簡短的教程:
https://www。youtube。com/watch?v=6LyagkoRWYA
[5] Chainlink VRF :
https://github。com/LearnWeb3DAO/Chainlink-VRFs
[6] GraphQL Endpoint:
https://graphql。org/learn/
[7] 內容:
https://github。com/LearnWeb3DAO/Graph/blob/master/abi。json
[8] The Graph’s Hosted Service:
https://thegraph。com/hosted-service/
[9] The Graph‘s Hosted Service:
https://thegraph。com/hosted-service/
[10] The Graph’s Hosted Service:
https://thegraph。com/hosted-service/
[11] Mumbai PolygonScan:
https://mumbai。polygonscan。com/
[12] 這樣:
https://github。com/LearnWeb3DAO/Graph/blob/master/graph/subgraph。yaml#L11
[13] 這個:
https://thegraph。com/docs/en/developer/create-subgraph-hosted/#defining-entities
[14] 連結:
https://thegraph。com/docs/en/developer/create-subgraph-hosted/#built-in-scalar-types
[15] The Graph‘s Hosted Service:
https://thegraph。com/hosted-service/
[16] React:
https://reactjs。org/
[17] Next Js:
https://nextjs。org/
[18] 這張圖片:
https://github。com/LearnWeb3DAO/Graph/blob/master/my-app/public/randomWinner。png
[19] 這個連結:
https://thegraph。com/hosted-service/subgraph/sneh1999/learnweb3
[20] hosted service:
https://thegraph。com/hosted-service
[21] 推送到github:
https://medium。com/hackernoon/a-gentle-introduction-to-git-and-github-the-eli5-way-43f0aa64f2e4