在 Web3 與 DeFi 蓬勃發展的時代,如何高效查詢與分析區塊鏈數據成為開發者的核心挑戰。本文全面解析 The Graph 去中心化索引協議,結合 GraphQL API 實例,手把手教你如何查詢 Uniswap 交易、理解子圖運作、優化查詢效能,並整合到現代前端應用。無論你是區塊鏈新手還是資深開發者,都能從這篇教學中掌握 Web3 數據分析的實用技巧與最佳實踐。
The Graph 協議簡介
The Graph 是一個去中心化的索引協議,專門用於從區塊鏈及相關去中心化網路中索引和查詢資料。它的作用類似於 Google 對傳統網路的索引方式,將區塊鏈上的資料組織成易於查詢和訪問的格式。透過提供這種中間層服務,The Graph 使開發者能夠高效地檢索和利用區塊鏈資料,而無需自行構建複雜的索引系統。
The Graph 最初專注於以太坊區塊鏈,但現已擴展到支援多種區塊鏈網路,包括 IPFS 和 POA 等。它的核心價值在於將區塊鏈的複雜性抽象化,讓開發者可以專注於構建優質的使用者體驗,而不是陷入資料檢索的技術難題中。The Graph 通過組織和供應 Web3 數據,大大簡化了去中心化應用(dApps)的開發過程。在 The Graph 出現之前,直接從區塊鏈讀取數據是一個緩慢且低效的過程,特別是在處理複雜的智能合約或跨越大量區塊的交易時。The Graph 成功解決了這些問題,不僅消除了開發者自建數據服務器的需求,還顯著降低了每月 60-98% 的基礎設施成本。更重要的是,The Graph 提供了高達 99.99%+ 的運行時間,確保了應用數據流的穩定性和可靠性。這些優勢使得 The Graph 成為 Web3 開發中不可或缺的基礎設施工具。
The Graph 的核心架構與運作
The Graph 生態系統由子圖(Subgraphs)、索引器(Indexers)、策展人(Curators)、委託人(Delegators)及 GRT 代幣等五大角色組成。子圖由開發者定義鏈上資料的擷取與結構,索引器負責運行節點與處理查詢,策展人標記與推廣重要子圖,委託人則透過委託 GRT 參與網路運作。GRT 代幣則作為網路的經濟激勵與安全保障。整體流程包括子圖建構、鏈上資料索引、資料儲存,最終透過 GraphQL API 查詢結構化資料。
GraphQL 查詢的優勢
GraphQL 作為 The Graph 的查詢語言,具備高效、彈性與單一端點等優點。開發者可精確取得所需資料,避免過度或不足獲取,顯著降低網路負載。單一端點設計簡化 API 架構與維護,並方便版本管理。GraphQL 也能聚合多來源資料,適應 Web3 多鏈、多協議的需求。
開發體驗與生態整合
GraphQL 不僅提升查詢效率,也帶來更佳的開發體驗。強型別設計支援自動補全與即時查詢驗證,減少錯誤。GraphiQL、GraphQL Playground 等工具提供互動式查詢與除錯環境。與現代前端框架如 Apollo Client、Relay 的無縫整合,讓開發者能輕鬆管理應用狀態與資料流,提升 Web3 應用的開發效率與品質。
Uniswap 子圖概述
Uniswap 是去中心化交易所的代表項目,它利用 The Graph 索引和組織其智能合約數據。Uniswap 為其每個版本都創建了專用的子圖。
Uniswap 子圖版本和端點
Uniswap 主要版本的子圖端點如下:
- Uniswap v4(主網): https://gateway.thegraph.com/api/subgraphs/id/DiYPVdygkfjDWhbxGSqAQxwBKmfKnkWQojqeM2rkLb3G
- Uniswap v3(主網): https://gateway.thegraph.com/api/subgraphs/id/5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV
- Uniswap v2(主網): https://gateway.thegraph.com/api/subgraphs/id/A3Np3RQbaBA6oKJgiwDJeo5T3zrYfGHPWFYayMwtNDum
每個子圖都有相應的 GitHub 代碼庫,可用於深入了解其架構。
為 Uniswap 編寫 GraphQL 查詢
基本查詢結構
在 The Graph 中,每個實體類型都會生成相應的查詢字段。例如,為名為 Token
的實體類型,會自動生成 token
和 tokens
查詢字段。
基本查詢結構如下:
{
entity(id: "specificID") {
attribute1
attribute2
nestedObject {
nestedAttribute1
nestedAttribute2
}
}
}
Uniswap 單筆交換查詢範例
舉個例子,例如我們想要查詢交易 ID 為 0x000007e1111cbd97f74cfc6eea2879a5b02020f26960ac06f4af0f9395372b64#66785
的某筆交換細節,我們希望可以獲得交換的參與者、代幣金額、交易細節等資訊。我們可以使用以下 GraphQL 查詢:
{
swap(
id: "0x000007e1111cbd97f74cfc6eea2879a5b02020f26960ac06f4af0f9395372b64#66785"
) {
sender
recipient
amount0
amount1
transaction {
id
blockNumber
gasUsed
gasPrice
}
timestamp
token0 {
id
symbol
name
decimals
}
token1 {
id
symbol
name
decimals
}
pool {
id
feeTier
}
}
}
你可以透過 fetch 發送查詢請求,並獲取回應的 JSON 格式數據。以下是使用 JavaScript 的 fetch API 的範例:
const v3 = "5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV";
const url = `https://gateway.thegraph.com/api/subgraphs/id/${v3}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <YOUR_API_KEY>",
},
body: JSON.stringify({
query: `<!--完整 GraphQL 查詢代碼-->`,
}),
});
const data = await response.json();
此查詢可獲得以下詳細資訊:
- 發送者 (sender):交換操作的發起地址,通常是用戶的錢包地址或智能合約地址。
- 接收者 (recipient):交換後代幣的接收地址,可能與發送者相同或不同。
- 交換金額:
amount0
:第一種代幣的交換數量,負值表示賣出,正值表示買入。amount1
:第二種代幣的交換數量,與 amount0 相反。
- 交易細節:
transaction.id
:唯一標識此交易的鏈上交易哈希,可用於在區塊鏈瀏覽器中查詢完整交易信息。blockNumber
:交易被打包的區塊高度,反映交易在鏈上的時間順序。gasUsed
:此交易實際消耗的 gas 量,反映交易的複雜度。gasPrice
:交易的 gas 價格,影響交易的確認速度和總成本。
- 時間戳 (timestamp):交換發生的精確 Unix 時間戳,可轉換為人類可讀的日期和時間。
- 代幣資訊:對於 token0 和 token1,均包含:
id
:代幣的智能合約地址。symbol
:代幣的簡稱,如WBTC
或WETH
。name
:代幣的全名,如Wrapped BTC
或Wrapped Ether
。decimals
:代幣的小數位數,用於正確解析代幣金額。
- 流動性池資訊:
pool.id
:流動性池的合約地址,唯一標識特定的交易對。feeTier
:交易手續費等級,如500
表示 0.05% 的費率。
範例回應
這是 Uniswap V3 的交換回應範例,其中包含多個重要字段的詳細信息:
{
"swap": {
"amount0": "-0.01713487",
"amount1": "0.25",
"pool": {
"feeTier": "500",
"id": "0x4585fe77225b41b697c938b018e2ac67ac5a20c0"
},
"recipient": "0xd4d63cb0e661df7bd54b27d8ee38013d68e8cc8f",
"sender": "0xe592427a0aece92de3edee1f18e0157c05861564",
"timestamp": "1628534827",
"token0": {
"decimals": "8",
"id": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
"name": "Wrapped BTC",
"symbol": "WBTC"
},
"token1": {
"decimals": "18",
"id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"name": "Wrapped Ether",
"symbol": "WETH"
},
"transaction": {
"blockNumber": "12992661",
"gasPrice": "75000000000",
"gasUsed": "177045",
"id": "0x000007e1111cbd97f74cfc6eea2879a5b02020f26960ac06f4af0f9395372b64"
}
}
}
此筆交易發生於 2021 年 8 月 9 日 11:47:07(UTC),由地址 0xe592427a0aece92de3edee1f18e0157c05861564 發起,將 0.01713487 顆 WBTC 兌換為 0.25 顆 WETH,並將 WETH 發送至地址 0xd4d63cb0e661df7bd54b27d8ee38013d68e8cc8f。這筆交換是在 Uniswap V3 的流動性池(ID:0x4585fe77225b41b697c938b018e2ac67ac5a20c0)中完成,手續費等級為 0.05%。交易在以太坊區塊高度 12992661 被確認,消耗了 177,045 單位的 gas,gas 價格為 75 Gwei。這代表交易者用 WBTC 成功換得 WETH,並為此支付了相應的鏈上手續費,整個過程安全且透明地記錄在區塊鏈上。
最佳實踐與優化策略
查詢效能優化
-
避免使用大的 skip 值:
- 原因:大 skip 值會導致查詢效能顯著下降。
- 建議:對於大量數據,採用基於特定屬性(如 ID 或時間戳)的游標分頁方法。
- 示例:使用
where: { id_gt: "lastId" }
替代skip: 1000
。
-
精確指定所需字段:
- 原則:只查詢應用程序實際需要的字段,避免過度獲取。
- 好處:減少數據傳輸量,降低網絡負載,提升查詢速度。
- 技巧:使用 GraphQL 片段(Fragments)來組織和重用常用字段集。
-
優化排序和分頁策略:
- 參數使用:靈活運用
orderBy
、orderDirection
、first
和skip
。 - 性能考慮:對大數據集進行排序可能會影響查詢速度,考慮在索引器端預先排序。
- 分頁最佳實踐:結合
first
和orderBy
實現高效分頁,如first: 20, orderBy: createdAt, orderDirection: desc
。
- 參數使用:靈活運用
-
利用複合查詢:
- 概念:在單一請求中組合多個相關查詢。
- 優勢:減少網絡往返,提高數據獲取效率。
- 示例:同時查詢用戶信息和其最近交易,而不是發送兩個獨立請求。
-
實施緩存策略:
- 客戶端緩存:利用 Apollo Client 等工具的內建緩存機制。
- 服務器端緩存:對於變化不頻繁的數據,考慮在應用服務器層實施緩存。
- 緩存失效:制定合理的緩存更新策略,確保數據及時性與查詢效能的平衡。
實際應用場景
The Graph 在 Uniswap 等 DeFi 應用中的典型用例包括:
特定池子數據
在使用 The Graph 查詢 Uniswap 區塊鏈數據時,開發者常見的一個操作就是針對某個特定的流動性池進行資訊擷取,例如查詢其價格、流動性與交易對細節等。這些資訊對於建構前端 DApp 介面、執行價格分析或實作自動化交易策略而言,都是不可或缺的基礎資料。
舉例來說,假設我們想查詢知名的 USDC/WETH 交易對在 Uniswap V3 上的流動性池,其池子合約地址為 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8
,我們可以透過以下 GraphQL 查詢來擷取池子的核心指標:
{
pool(id: "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8") {
tick
token0 {
symbol
id
decimals
}
token1 {
symbol
id
decimals
}
feeTier
sqrtPrice
liquidity
}
}
這份查詢會回傳如下資訊:
{
"pool": {
"feeTier": "3000",
"liquidity": "3143901592156277673",
"sqrtPrice": "1961583311187181647181742186234869",
"tick": "202348",
"token0": {
"decimals": "6",
"id": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC"
},
"token1": {
"decimals": "18",
"id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"symbol": "WETH"
}
}
}
這些資料涵蓋了以下幾個層面:
- token0 與 token1 資訊:分別代表此交易對的兩種資產(在此例中為 USDC 與 WETH),包含代幣地址、簡稱與小數位數,對於解析與格式化金額極為重要。
- feeTier:該池子的手續費等級(如 3000 表示 0.3%),對於預測用戶交易成本與流動性策略有指標意義。
- tick:Uniswap V3 使用的價格刻度表示,與 sqrtPrice 搭配可進一步解析價格與範圍定位。
- sqrtPrice:當前價格的平方根,用於推導實際價格,採用 Q96 固定點格式儲存。
- liquidity:目前在該價格範圍內的有效流動性,用於判斷此池子資金深度與市場承載力。
綜合以上數據,我們可以快速描繪出這個池子的現況,不僅能了解其提供交易的成本與價格區間,也能進一步進行套利機會分析或資金配置決策。
而 sqrtPrice
需要進行以下的處理,才能得到實際價格:
import Decimal from "decimal.js";
/**
* 解碼 Uniswap V3 的 sqrtPriceX96 為實際價格。
*
* @param {string} sqrtPriceX96 - 固定點表示的 sqrt(price),以字串避免精度誤差。
* @param {number} decimalsToken0 - token0 的 decimals(例如 USDC 為 6)
* @param {number} decimalsToken1 - token1 的 decimals(例如 WETH 為 18)
* @returns {Object} - 實際價格(token1/token0)與其倒數(token0/token1)
*/
function decodeUniswapV3Price(
sqrtPriceX96: string,
decimalsToken0: number,
decimalsToken1: number
) {
const Q96 = new Decimal(2).pow(96);
const sqrtPrice = new Decimal(sqrtPriceX96);
// 還原價格(未考慮小數位差)
const rawPrice = sqrtPrice.div(Q96).pow(2);
// 考慮 decimals 修正
const decimalAdjustment = new Decimal(10).pow(
decimalsToken0 - decimalsToken1
);
const adjustedPrice = rawPrice.mul(decimalAdjustment);
return {
token1PerToken0: adjustedPrice.toFixed(10),
token0PerToken1: adjustedPrice.eq(0)
? "Infinity"
: new Decimal(1).div(adjustedPrice).toFixed(10),
};
}
使用這個函數,我們可以得到實際價格:
const { token1PerToken0, token0PerToken1 } = decodeUniswapV3Price(
"1961583311187181647181742186234869",
6,
18
);
token1PerToken0
的值為 1631.34,表示 1 WETH 為 1631.34 USDC,而 token0PerToken1
的值為 0.00061,表示 1 USDC 為 0.00061 WETH。
結論
The Graph,作為去中心化索引協議,解決了區塊鏈資料檢索的核心挑戰,為 Web3 生態系統提供關鍵基礎設施支持。透過結合 GraphQL 的強大功能,它實現了高效、靈活且開發者友好的區塊鏈資料查詢方案。隨著 Web3 應用的不斷發展,對高效資料檢索的需求只會增加,而 The Graph 的去中心化特性不僅提供技術優勢,還與 Web3 的核心理念保持一致,創建更安全、透明和開放的去中心化網絡。
對開發者而言,The Graph 代表了一種範式轉變,從手動處理繁瑣的區塊鏈資料索引,到使用專業設計的協議來無縫處理這些複雜任務。這不僅提高了開發效率,也為創建更複雜、更用戶友好的去中心化應用鋪平了道路。隨著區塊鏈技術和 Web3 生態系統的持續演進,The Graph 將繼續扮演連接區塊鏈原始資料與實用應用之間的橋樑角色,為開發者提供所需工具,以構建下一代去中心化互聯網的願景。