Uniswap 數據查詢全攻略:深入理解 The Graph 協議與 GraphQL 範例操作

作者: Calpa Liu
字數:3219
出版:April 15, 2025
分類: Web3

在 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 主要版本的子圖端點如下:

每個子圖都有相應的 GitHub 代碼庫,可用於深入了解其架構。

為 Uniswap 編寫 GraphQL 查詢

基本查詢結構

在 The Graph 中,每個實體類型都會生成相應的查詢字段。例如,為名為 Token 的實體類型,會自動生成 tokentokens 查詢字段。

基本查詢結構如下:

{
  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:代幣的簡稱,如 WBTCWETH
    • name:代幣的全名,如 Wrapped BTCWrapped 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,並為此支付了相應的鏈上手續費,整個過程安全且透明地記錄在區塊鏈上。

最佳實踐與優化策略

查詢效能優化

  1. 避免使用大的 skip 值

    • 原因:大 skip 值會導致查詢效能顯著下降。
    • 建議:對於大量數據,採用基於特定屬性(如 ID 或時間戳)的游標分頁方法。
    • 示例:使用 where: { id_gt: "lastId" } 替代 skip: 1000
  2. 精確指定所需字段

    • 原則:只查詢應用程序實際需要的字段,避免過度獲取。
    • 好處:減少數據傳輸量,降低網絡負載,提升查詢速度。
    • 技巧:使用 GraphQL 片段(Fragments)來組織和重用常用字段集。
  3. 優化排序和分頁策略

    • 參數使用:靈活運用 orderByorderDirectionfirstskip
    • 性能考慮:對大數據集進行排序可能會影響查詢速度,考慮在索引器端預先排序。
    • 分頁最佳實踐:結合 firstorderBy 實現高效分頁,如 first: 20, orderBy: createdAt, orderDirection: desc
  4. 利用複合查詢

    • 概念:在單一請求中組合多個相關查詢。
    • 優勢:減少網絡往返,提高數據獲取效率。
    • 示例:同時查詢用戶信息和其最近交易,而不是發送兩個獨立請求。
  5. 實施緩存策略

    • 客戶端緩存:利用 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 將繼續扮演連接區塊鏈原始資料與實用應用之間的橋樑角色,為開發者提供所需工具,以構建下一代去中心化互聯網的願景。

感謝您閱讀我的文章。歡迎隨時分享你的想法。
關於 Calpa

Calpa 擅長使用 TypeScriptReact.jsVue.js 建立 Responsive Website。

他積極參與開源社區,曾在 2019 年的香港開源大會上擔任講者,提供工作經驗和見解。此外,他也在 GitHub 上公開分享個人博客程式碼,已獲得超過 300 顆星星和 60 個分支的支持。

他熱愛學習新技術,並樂意分享經驗。他相信,唯有不斷學習才能跟上快速演變的技術環境。