Cover photo

The Graph:區塊鏈資料索引化的新世代解決方案

區塊鏈的崛起帶來了無數的資料,然而,如何高效地檢索和查詢這些資料一直是一個挑戰。在這樣的背景下,The Graph 應運而生,作為一個去中心化的 GraphQL 索引協定,為我們提供了一個嶄新的方法來處理區塊鏈資料。

註:GraphQL 是一個可由 Client 端決定回傳資料格式的資料庫查詢語言

The Graph 的關鍵在於其 Subgraph 概念,它可以被視為 DApp 的索引專案。Subgraph 會遍歷每個區塊儲存的 Event 狀態,然後透過 Event Handler 處理這些 Event,將它們儲存至 Entity 中,相當於 GraphQL Schema 的實例。這使得我們能夠自定義和客製化查詢,從而在需要的時候輕鬆地檢索特定資料,例如:NFT 的歷史最高價。

Entity 在 Subgraph Handler 中具有關鍵作用。每次觸發 Event Handler 時,我們可以決定是要續用已有的 Entity 資料,還是進行覆蓋。Entity 由 Name 和 ID 來區分,就像是關聯式資料庫中的 Table Name 和唯一識別用的 Primary Key。

註:實際撰寫 Handler 邏輯時,Entity 需在每次賦值處理後要再執行 .save() 函數將資料回存

Tokenlon Buyback 頁面為例,透過 The Graph,我們可以輕鬆地查詢 Entity,並使用 GraphQL 和支付 GRT(The Graph 去中心化查詢協議的代幣)來執行查詢。這種方式相對於以往僅能使用 Etherscan 查詢每個區塊的 Event 的方式,更加高效、靈活且客製化。

The Graph 中的資料流動方式,圖片來源:https://thegraph.com/docs/en/about/
The Graph 中的資料流動方式,圖片來源:https://thegraph.com/docs/en/about/

1、The Graph 的去中心化生態系統

在區塊鏈技術的迅速發展下,The Graph 成為了一個引人注目的去中心化數據查詢協議。該協議的核心在於去中心化,並且涵蓋了不同角色的參與,包括開發者(Developer)、消費者(Consumer)、索引者(Indexer)、策展者(Curator)以及委托人(Delegator),各自扮演著協議生態系統中不可或缺的角色。

開發者(Developer):推動 DAPP 項目的推廣與查詢

開發者在 The Graph 生態系統中扮演著極為關鍵的角色,他們是去中心化應用(DAPP)項目的推動者。為了讓自己的 DAPP 能夠順利運行並獲得查詢支持,開發者需要建立 Subgraph,這是一個能夠被查詢的數據子集。此外,為了吸引索引者(Indexer)參與索引,開發者需要質押 GRT 作為激勵。

對於開發者而言,建議他們在建立 Subgraph 時至少質押 10,000 GRT,並且最好能夠將這份質押保持 1 到 2 年以上,以確保項目的穩定運行與持續發展。

消費者(Consumer):最終用戶的查詢支持

作為協議的最終用戶,消費者可以透過支付 GRT 來進行 GraphQL 查詢,從而獲得所需的數據。這些查詢不僅幫助消費者獲取所需的資訊,同時也為生態系統中的其他角色提供了收入來源。

索引者(Indexer):維護索引節點的角色

索引者是 The Graph 協議中的重要參與者,他們負責維護節點的索引並提供查詢服務。索引者根據信號(Signal)的多寡來建立索引,同時也會收取一定的 GRT 作為報酬。要成為一名索引者,需要質押至少 100,000 GRT 作為激勵,並且可以參與查詢的 GRT 定價。

策展者(Curator):評價 Subgraph 的重要角色

策展者在 The Graph 生態系統中負責評價不同的 Subgraph,以幫助吸引索引者參與索引。他們可以透過質押 GRT 並對 Subgraph 進行信號標記(Signal),從而提升該 Subgraph 的價值。這些標記的信號將轉化為 Curator Shares(CS),並最終可以兌換回 GRT,同時也提升了相關 Subgraph 的吸引力。

作為 Curator,每一次透過質押 GRT 並對 Subgraph 進行 Signal,均會支付其中的 1% 作為給 The Graph 的管理稅

委託人(Delegator):與索引者共享收入的參與者

委託人可以透過將 GRT 質押給索引者來分享他們的收入。這種方式使得更多的參與者可以在協議的生態系統中獲得收益,同時也加強了去中心化的特性。

The Graph 世界的角色分工,圖片來源:https://github.com/oneleo/awesome-blockchain-articles
The Graph 世界的角色分工,圖片來源:https://github.com/oneleo/awesome-blockchain-articles

2、The Graph 定價策略與三大平臺:中心化索引平臺、開發者測試平臺、去中心化索引平臺

隨著區塊鏈技術的不斷演進,The Graph 在去中心化數據查詢領域中嶄露頭角。然而,一個有效的定價策略對於確保該協議的可持續運行和發展至關重要。在 The Graph 的生態系統中,定價策略涵蓋了不同的服務和角色,讓我們一起來了解其中的細節。

Hosted Service:免費的中心化、開放索引平臺(即將關閉)

在過去的一段時間裡,The Graph 提供了免費的 Hosted Service,使開發者能夠輕鬆建立和管理 Subgraph,並透過中心化的方式獲得查詢支持。然而,隨著時間的推移,Hosted Service 將於 2023 年 Q1 開始啟動關閉計劃,這將是一個轉折點,也意味著使用者需要移轉至 Graph Explorer 去中心化解決方案。

Subgraph Studio:免費的中心化、不公開的開發者調校後臺

對於那些計劃在 Graph Explorer 平臺上移轉或新建 Subgraph 的開發者來說,Subgraph Studio 提供了一個極具價值的中心化開發者後臺。在將 Subgraph 部署到去中心化 Graph Explorer 平臺之前,開發者可以在 Subgraph Studio 的不公開環境中進行詳細的測試和調校工作。儘管該環境提供了一個免費的 GraphQL URL 供查詢使用(雖然時常斷線進行維護),但它已足以支持開發者進行全面的 Subgraph 調校。

在經過調校和測試後,開發者便可以正式付費以 Gas 費用和 ArbitrumGRT 代幣將 Subgraph 部署及質押到去中心化且公開的 Graph Explorer 平臺上,使 Consumer 能夠進行付費查詢。這個過程保證了 Subgraph 在最終用戶查詢之前經過了嚴格的測試,確保了高品質和可靠的查詢體驗。

註:目前 GRT 代幣有部署至 Mainnet、Goerli、Arbitrum 及 Arbitrum Goerli,詳情可參考官方文件

Graph Explorer:付費的去中心化、開放的索引平臺(主流)

隨著 Hosted Service 即將關閉,The Graph 推出了付費的 Graph Explorer 服務,這將是未來去中心化數據查詢的主要通道。使用者可以透過支付 Gas 費用來鑄造 ERC-721 代幣 Subgraph,並在其中發佈和更新 Subgraph。然而,每次更新都需要支付 0.25% 的總 Signal 金額作為更新稅,其中 0.5% 會平分給 Curator 和 Developer。

此外,在使用 Graph Explorer 進行查詢時,每筆查詢都需要支付 0.00025 ~ 0.06 ArbitrumGRT(根據查詢複雜度而定)。這些付費將被用來激勵去中心化的 Indexer,他們根據 Signal 的多寡來進行索引,同時也決定了每筆查詢的費用。

在撰寫此文時,Graph Explorer 上總共有 214 位 Indexer 為我們進行去中心化的索引

對於那些希望儲值並根據每週 API Keys 呼叫次數扣款的使用者,Graph Explorer 提供了方便的方式來管理查詢費用。而對於在特定 Subgraph 上進行查詢的需求,也可以透過限制 API Keys 的方式來實現。

作為 Curator,您使用 EOA 錢包、或 Multisig 多簽合約錢包來進行儲值與取款,詳細流程請參考官方文件

然而,值得注意的是,這些付費和費用在 Goerli 測試網上的測試中不會被計入統計,以保證用戶能夠在安全的環境中進行實驗和測試。

3、The Graph 的 Subgraph 開發流程

The Graph 作為去中心化數據查詢的重要工具之一。要充分發揮 The Graph 的優勢,我們需要了解其開發流程以及相關的操作步驟。本章節將介紹去中心化 The Graph 的開發流程,讓我們一起來探索吧。

Subgraph 從創建到部署

The Graph 提供了一個清晰的 Subgraph 開發流程,幫助開發者輕鬆地建立和管理自己的數據子集。以下是 Subgraph 開發流程的主要步驟:

  1. Create(創建): 開始創建你的 Subgraph,這可以在 Subgraph Studio 中進行。這是一個中心化的開發者後臺,讓你能夠輕鬆部署 Subgraph 到 IPFS(InterPlanetary File System)。

  2. 編輯 Entity Handler(實體處理): 在本機編輯 Subgraph 的 Entity Handler,這是用於處理區塊鏈事件和數據的函數。這一步驟的目的是確保 Subgraph 能夠準確地處理區塊鏈事件。

  3. Deploy(部署): 將經過編輯的 Entity Handler 部署到 Subgraph Studio 中,這是一個在中心化環境中進行的操作。

  4. Publish(發佈): 一旦 Subgraph 在 Subgraph Studio 中成功部署,你可以將其發佈到去中心化的 Graph Explorer 平臺中。這將使你的 Subgraph 可供 Consumer 尋找與查詢。

Subgraph 狀態

在 Subgraph 的開發過程中,你將會遇到不同的狀態,這些狀態反映了 Subgraph 的部署和可用性

  1. UNDEPLOYED(未部署):Subgraph 還未部署到任何平臺上。

  2. DEPLOYED(已部署) Subgraph 已成功部署,可以在特定平臺上進行查詢。

  3. GOERLI/MAINNET(Goerli 測試網/主網):Subgraph 可在 Goerli 測試網或以太坊主網上進行查詢。

Subgraph 在本機的開發流程、步驟

Subgraph 在本機的開發流程中,有一些關鍵步驟需要知道:

  1. 連結合約 Event 與 Entity:通過定義 Event 和 Entity,你可以確保 Subgraph 正確地捕獲區塊鏈事件並處理數據。

  2. 定義 Event 和 Entity:Event 定義了你希望 Subgraph 跟踪的區塊鏈事件,而 Entity 則定義了 Subgraph 要存儲和處理的數據結構。

  3. 編譯 Entity Handler:Entity Handler 是用於處理 Event 的函數,你需要確保它們在部署和運行時能夠正確運作。

  4. Deploy 至 Subgraph Studio:將經過編譯的 Entity Handler 發佈到中心化的 Subgraph Studio 測試平臺上。以進行 Subgraph 正式發佈至公開的 Graph Explorer 之前的測試與調校。

在本機開發時您需要知道的 Subgraph 檔案意義:

  • Contract ABI 檔:取得 Event 變數(定義 Event)

  • Entity GraphQL 檔:定義 GraphQL API 對外可被 Query 的 Schema 資料(定義 Entity 變數)

  • Manifest YAML 檔:定義監聽哪個合約的 Event,及取得 Event 後對應的 Handler 處理函數(連結合約 Event 與 Entity)

  • Handler TypeScript 檔:定義被監聽合約的 Event 發生後,要如何處理 Event 狀態並儲存至 Entity 中

Subgraph 開發流程
Subgraph 開發流程

4、實際發佈 USDT 合約 Subgraph 至 Graph Explorer

(1)前置作業

  • 安裝 MetaMask

  • 取得 GoerliETH

    • 請至 Goerli PoW Faucet 網站,輸入您的錢包地址,並點選【Start Mining】透過電腦算力來交換 GoerliETH

  • 取得 GoerliGRT 代幣

    • 請登入 The Graph Discord

    • 請點選【#🚰︱testnet-faucet 】頻道,再輸入以下指令:

!grt [錢包地址]
  • 安裝 Graph-Cli 與 VSCode

    • 請輸入以下指令:

~ % brew install pnpm
~ % brew install --cask visual-studio-code
~ % pnpm --global add @graphprotocol/graph-cli

(2)在 Subgraph Studio 建置 Subgraph

  • 請至 Subgraph Studio 網站

  • 請點選右上角【Create a Subgraph】鈕

  • 點選【Ethereum Goerli】網路 → 在「Subgraph name」欄位輸入名稱【LON on Goerli】→【Continue】

  • 來到「LON on Goerli」頁面,請【複製】右下角「AUTHENTICATE IN CLI」認證指令

    • 註 1:[DEPLOY KEY] 可在 Subgraph Studio 網站右上方查詢或重建

    • 註 2:認證指令應如下所示

# 請自行取代 [DEPLOY KEY]
~ % graph auth --studio [DEPLOY KEY]

(3)在本機編輯 Subgraph Entity Handler 腳本

讓 Graph-Cli 登入 Subgraph Studio 帳號

  • 請在本機 Terminal【執行】上個步驟複製的認證指令

建置與初始化「LON on Goerli」Subgraph

  • 請執行以下指令

~ % mkdir -p "$HOME/GitHub/subgraph"
~ % cd $HOME/GitHub/subgraph
subgraph % graph init --studio lon-on-goerli
  • 請輸入以下參數

✔ Protocol · ethereum
✔ Subgraph slug · lon-on-goerli
✔ Directory to create the subgraph in · lon-on-goerli
✔ Ethereum network · goerli
✔ Contract address · 0x6dA0e6ABd44175f50C563cd8b860DD988A7C3433
✔ Fetching ABI from Etherscan
✔ Start Block · 4691265
✔ Contract Name · LON
✔ Index contract events as entities (y/N) · trueAdd another contract? (y/N) · N
post image
  • 開啟「LON on Goerli」Subgraph

subgraph % code $HOME/GitHub/subgraph/lon-on-goerli/

新增 User、TransferTotal Entity 以紀錄 Lon 代幣數量

  • 編輯 schema.graphql

lon-on-goerli % code schema.graphql
// …上略

# 紀錄執行 Transfer 次數
type TransferTotal @entity {
  id: Bytes!
  total: BigInt!
}

# 紀錄 Lon 餘額與互動對象
type User @entity {
  id: Bytes!
  tradeCount: Int!
  lonAmount: BigInt!
  interactedAddr: [Bytes!]!
}
編輯 schema.graphql 檔
編輯 schema.graphql 檔
  • 請執行以下指令,以建立 Entity TypeScript:generated/schema.ts

lon-on-goerli % graph codegen
  • 編輯 Git 忽略清單

% code .gitignore
# dependency
node_modules/
generated/
post image

編輯 Handler TypeScript

  • 編輯 src/lon.ts

lon-on-goerli % code src/lon.ts
// …上略

import { BigInt, Bytes, Address, log } from "@graphprotocol/graph-ts";

// …中略…

import {
  TransferTotal as TransferTotalEntity,
  User as UserEntity,
} from "../generated/schema";

export function handleTransfer(event: TransferEvent): void {

// …中略…

  // TransferTotalEntity 儲存觸發次數
  let transferTotalEntity = TransferTotalEntity.load(Bytes.fromI32(1));
  if (!transferTotalEntity) {
    transferTotalEntity = new TransferTotalEntity(Bytes.fromI32(1));
    transferTotalEntity.total = BigInt.fromI32(0);
    log.info(
      "A new 'TransferTotal' Entity is created, TransferTotal.id = {}}",
      [transferTotalEntity.id.toString()]
    );
  }
  transferTotalEntity.total = transferTotalEntity.total.plus(BigInt.fromI32(1));
  transferTotalEntity.save();

  // UserEntity 儲存交易次數、交易數量、交易對象
  const fromEntity = _getUser(event.params.from);
  fromEntity.tradeCount += 1;
  fromEntity.lonAmount = fromEntity.lonAmount.minus(event.params.value);
  const fromInteractedAddr = fromEntity.interactedAddr;
  fromInteractedAddr.push(event.params.to as Bytes);
  fromEntity.interactedAddr = fromInteractedAddr;
  fromEntity.save();

  const toEntity = _getUser(event.params.to);
  toEntity.tradeCount += 1;
  toEntity.lonAmount = toEntity.lonAmount.plus(event.params.value);
  const toInteractedAddr = toEntity.interactedAddr;
  toInteractedAddr.push(event.params.from as Bytes);
  toEntity.interactedAddr = toInteractedAddr;
  toEntity.save();

  // 在 Subgraph Studio 印出 Debug Log
  log.debug(
    "Some Entities have been updated: Transfer.id: {}, value = {}; TransferTotal.id: {}, total = {}; From.id: {}, tradeCount = {}; To.id: {}, tradeCount = {}",
    [
      entity.id.toString(),
      entity.value.toString(),
      transferTotalEntity.id.toString(),
      transferTotalEntity.total.toString(),
      fromEntity.id.toString(),
      fromEntity.tradeCount.toString(),
      toEntity.id.toString(),
      toEntity.tradeCount.toString(),
    ]
  );
}

const _getUser = (userAddr: Address): UserEntity => {
  const userAddrStr = userAddr.toHex();
  let user = UserEntity.load(userAddrStr);
  if (!user) {
    user = new UserEntity(userAddrStr);
    user.tradeCount = 0;
    user.lonAmount = BigInt.fromI32(0);
    user.interactedAddr = [];
    log.info("A new 'User' Entity is created, User.id = {}}", [user.id]);
  }
  return user;
};
post image
  • 編譯 Handler 程式碼

lon-on-goerli % graph build

(4)Deploy 本機 Subgraph 至 Subgraph Studio

  • 請執行以下指令,以 Deploy 本機 Subgraph 至 Subgraph Studio

lon-on-goerli % graph auth --studio [DEPLOY KEY]
lon-on-goerli % graph deploy --studio lon-on-goerli
  • 請輸入以下參數

    • 注意:不可與已 Deploy 的版本重複

✔ Version Label (e.g. v0.0.1) · v0.0.1

(5)在 Subgraph Studio 使用 PlayGround 查詢

  • 請點選【Playground】鈕,並執行以下 GraphQL 程式碼

{
  transferTotals {
    id
    total
  }
  users(
    first: 3
    orderBy: id
    skip: 6
    orderDirection: asc
    where: {id_not: "0x0000000000000000000000000000000000000000", tradeCount_lte: 9}
  ) {
    id
    interactedAddr
    lonAmount
    tradeCount
  }
}
post image

(6)透過測試 API 在 GraphiQL 查詢

  • 請【複製】右上角「DEVELOPMENT QUERY URL - LASTEST VERSION」

    • 註:URL 應如下所示

https://api.studio.thegraph.com/query/34764/lon-on-goerli/v0.0.1
  • 請至 GraphiQL 網站

  • 請在網站右側貼入上個步驟複製的 URL → 點選【Connect to Endpoint】

  • 請執行之前步驟的 GraphQL 程式碼

post image

(7)Publish 至 Graph Explorer

  • 請至您的「Lon on Goerli」頁面

  • 請點選右上角【Publish】鈕

  • 請點選【Ethereum Goerli】Network → 勾選【Be the first to signal on this subgraph】→ 輸入【10000】GRT → 點選【Allow GRT Access】鈕(出現 Metamask Confirm 視窗)

  • 請點選【Publish】鈕開始發佈 Subgraph(出現 Metamask Confirm 視窗)

  • 可至 Graph Explorer on Goerli 網站查看

post image

(8)在 Graph Explorer 使用 PlayGround 查詢

  • 方法與 Subgraph Studio 查詢方式相同

{
  transferTotals {
    id
    total
  }
  users(
    first: 3
    orderBy: id
    skip: 6
    orderDirection: asc
    where: {id_not: "0x0000000000000000000000000000000000000000", tradeCount_lte: 9}
  ) {
    id
    interactedAddr
    lonAmount
    tradeCount
  }
}

(9)透過付費 API 在 GraphiQL 查詢

  • 請至 GraphiQL 網站

  • 請點選右上角【Query】鈕 → 點選【Copy Query URL】鈕

    • 註:URL 應如下所示,並請替換在 API Keys 頁面申請的 [api-key]

https://gateway.testnet.thegraph.com/api/[api-key]/subgraphs/id/[subgraph-id]
  • 請至 GraphiQL 網站

  • 請在網站右側貼入上個步驟複製的 URL → 點選【Connect to Endpoint】

  • 請執行之前步驟的 GraphQL 程式碼

5、附錄:如何建立及保護您的 API Key、增加 Email 告警

(1)建立 The Graph 用於查詢 Subgraph 的 API Key

  • 完成 Wallet 連結(登入)後,請至 The Graph API Keys 頁面

  • 點選【Create API Key】鈕 → 建立名為【Tokenlon】的 API Key

    • 註:若是第一次建 Key,頁面會提示可輸入 Email、姓名、組織來取得前 1,000 次的免費 Query

post image

(2)限制 API Key 僅能 Query 指定的 Subgraphs

  • 請至剛建立的「Tokenlon」Key 管理頁面

  • 請點選左側【Security】鈕 → 點選【Restrict to a Subgraph】鈕

  • 輸入想要限制 API Key 僅能 Query 指定的一至多個【Subgraph ID】即可

    • 註:不同的 Subgraph ID 可從 Graph Explorer 頁面中查詢(搜尋 → 點選 Subgraph 即可看到 ID)

post image

(3)設置選擇的 Indexer 偏好

  • 注意:此功能僅對於有儲值 GRT 幣的帳戶

  • 因一個 Subgraph 可同時有多個 Indexer 來索引,我們可分配偏好點數來決定該對哪一個 Indexer 進行 Query

  • 請點選左側【Indexer Preference】鈕,有以下偏好進行選擇

    • 最快速度

    • 最低價格

    • 最新索引數據

      • 因不同 Indexer 索引速度不同,故會傾向 Query 擁有最新區塊數據的 Indexer 為主

    • 最正確數據

      • 因給予錯誤數據的 Indexer 會損失 GRT 幣,故會傾向 Query 擁有最多 Stake 的 Indexer 為主

post image

(4)設置 Billing 自動告警 Email

  • 請點選右上角【帳戶】→【Settings】即可新增告警 Email

  • 可根據需要開關接收告警的 Email,告警條件如下:

    • 餘額將在 2 週後不足時告警

    • 餘額成負數時告警

    • 當 Query 因未繳費而不再被接受時告警

post image

(5)手動查詢 Billing 餘額方式

{
  users(first: 5, where: {id: "0xe3c19b6865f2602f30537309e7f8d011ef99c1e0"}) {
    billingBalance
    id
    polygonGRTBalance
    totalTokensAdded
    totalTokensPulled
    totalTokensRemoved
    transactions {
      id
    }
  }
}
post image

6、結論

The Graph:解決區塊鏈數據處理難題的關鍵工具

區塊鏈技術的迅速發展帶來了眾多應用和可能性,然而,區塊鏈數據的處理一直是一個具有挑戰性的問題。在這個背景下,The Graph 的出現為我們提供了一個強大的解決方案,讓我們能夠更便捷地查詢和檢索特定資料,極大地提高了區塊鏈應用的效率和可靠性。

The Graph 的去中心化生態系統:協同合作的關鍵角色

The Graph 協議的去中心化生態系統是實現其強大功能的基石,其中包含著多個關鍵角色。每個角色在確保協議的穩定運行和為用戶提供支持方面都扮演著不可或缺的角色。開發者(Developer)、消費者(Consumer)、索引者(Indexer)、策展者(Curator)和委託人(Delegator)在協同合作中,共同促進了區塊鏈數據查詢的多元發展。這個生態系統的活力和多樣性,使得 The Graph 成功地搭建起了一個強大而有活力的區塊鏈數據查詢生態系統。

The Graph 的多樣性定價策略:促進生態系統發展的關鍵因素

在不同階段,The Graph 提供了多樣性的定價策略,這反映了去中心化數據查詢生態系統的成熟和發展。從免費的 Hosted Service 到付費的 Graph Explorer,不同的選擇為用戶和開發者提供了更多的彈性。這種多樣性的定價策略不僅確保了協議的持續運行,也激勵著參與者共同推動區塊鏈技術的創新和應用,為整個生態系統的發展注入了新的活力。

去中心化 The Graph 的開發流程:鋪就數據查詢未來的道路

在探索 The Graph 的世界中,去中心化的 Subgraph 開發流程成為了開發者們的指南。從創建到部署,從編輯 Entity Handler 到 Subgraph 的最終發佈,這一系列步驟為開發者們提供了清晰的方向。通過這個開發流程,開發者能夠確保他們的 Subgraph 在去中心化環境中運行正常,為用戶提供高質量的數據查詢服務。這一過程將有助於推動區塊鏈技術的應用和創新,實現更廣泛的去中心化數據查詢生態系統的發展。

The Graph 總整理
The Graph 總整理

5、參考資料

(1)The Graph 官方社群

(2)The Graph 教學

(3)學習影片(非官方)

(4)開發資源

(5)開發工具