китаев.tech

The Graph — запросы к субграфу

Как отправлять GraphQL-запросы к The Graph subgraph, получать DeFi-данные и работать с variables, pagination и errors в TypeScript.

Subgraph индексирует события и состояние

The Graph превращает blockchain-данные в GraphQL API.

Вместо ручного чтения logs, декодирования events и хранения индексов вы запрашиваете готовые сущности: pools, swaps, positions, tokens, volume, TVL и другие данные, которые subgraph уже собрал из сети.


Запрашиваем Uniswap V3 pools

Официальный Gateway The Graph требует API key. Храните ключ на сервере или в локальной переменной окружения, не в frontend bundle.

Создайте проект:

bash
mkdir the-graph-example
cd the-graph-example

npm init -y
npm pkg set type="module"
npm install tsx
mkdir src

Создайте файл src/query-uniswap-v3.ts:

typescript
const API_KEY = process.env.THE_GRAPH_API_KEY;

if (!API_KEY) {
  throw new Error("Set THE_GRAPH_API_KEY environment variable");
}

const UNISWAP_V3_SUBGRAPH_ID = "HUZDsRpEVP2AvzDCyzDHtdc64dyDxx8FQjzsmqSg4H3B";

const endpoint = `https://gateway.thegraph.com/api/${API_KEY}/subgraphs/id/${UNISWAP_V3_SUBGRAPH_ID}`;

type GraphResponse<T> = {
  data?: T;
  errors?: Array<{
    message: string;
  }>;
};

type PoolsQuery = {
  pools: Array<{
    id: string;
    feeTier: string;
    liquidity: string;
    totalValueLockedUSD: string;
    volumeUSD: string;
    token0: {
      symbol: string;
    };
    token1: {
      symbol: string;
    };
  }>;
};

const query = /* GraphQL */ `
  query TopPools($first: Int!, $skip: Int!, $minTvl: BigDecimal!) {
    pools(
      first: $first
      skip: $skip
      orderBy: totalValueLockedUSD
      orderDirection: desc
      where: { totalValueLockedUSD_gt: $minTvl }
    ) {
      id
      feeTier
      liquidity
      totalValueLockedUSD
      volumeUSD
      token0 {
        symbol
      }
      token1 {
        symbol
      }
    }
  }
`;

const response = await fetch(endpoint, {
  method: "POST",
  headers: {
    "content-type": "application/json",
  },
  body: JSON.stringify({
    query,
    variables: {
      first: 5,
      skip: 0,
      minTvl: "1000000",
    },
  }),
});

if (!response.ok) {
  throw new Error(`Graph request failed: ${response.status} ${response.statusText}`);
}

const json = (await response.json()) as GraphResponse<PoolsQuery>;

if (json.errors?.length) {
  throw new Error(json.errors.map((error) => error.message).join("\n"));
}

if (!json.data) {
  throw new Error("Graph response does not contain data");
}

for (const pool of json.data.pools) {
  console.log(`${pool.token0.symbol}/${pool.token1.symbol}`);
  console.log(`  pool: ${pool.id}`);
  console.log(`  fee: ${Number(pool.feeTier) / 10_000}%`);
  console.log(`  TVL: $${Number(pool.totalValueLockedUSD).toLocaleString("en-US")}`);
  console.log(`  volume: $${Number(pool.volumeUSD).toLocaleString("en-US")}`);
}

Перед запуском сохраните реальный Gateway API key в переменную окружения THE_GRAPH_API_KEY.

Запустите:

bash
npx tsx src/query-uniswap-v3.ts

На Windows PowerShell:

powershell
npx tsx src/query-uniswap-v3.ts

Скрипт выведет примерно такой результат:

text
WETH/USDC
  pool: 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640
  fee: 0.05%
  TVL: $421,902,410.91
  volume: $927,124,193,520.44
WBTC/WETH
  pool: 0xcbcdf9626bc03e24f779434178a73a0b4bad62ed
  fee: 0.3%
  TVL: $312,604,118.18
  volume: $68,934,519,901.12

Данные будут отличаться: subgraph постоянно индексирует новые blocks.

Как это работает

UNISWAP_V3_SUBGRAPH_ID — идентификатор subgraph в The Graph Gateway.

query TopPools(...) — именованный GraphQL-запрос. Имена помогают читать логи и ошибки.

$first, $skip, $minTvl — variables. Так вы не склеиваете GraphQL строку руками и можете безопасно менять параметры.

first — сколько сущностей вернуть за один запрос.

skip — offset для pagination. Для больших списков лучше использовать cursor-подход через id_gt, но skip удобен для простого старта.

where — фильтр на стороне subgraph. В примере мы берём только pools с TVL больше 1,000,000.

orderBy и orderDirection — сортировка. Здесь pools идут по totalValueLockedUSD от большего к меньшему.

json.errors — GraphQL может вернуть HTTP 200, но при этом содержать ошибки выполнения запроса. Проверяйте errors, а не только response.ok.

Subgraph не заменяет RPC

Subgraph хорош для данных, которые неудобно получать через один eth_call:

  • история swap и mint events
  • списки pools и positions
  • агрегированные volume, TVL и fees
  • фильтрация и сортировка по indexed fields

Но subgraph не является финальным источником текущего State. Он может отставать на несколько blocks, переиндексироваться или иметь schema-специфичные правила расчёта.

Если нужно принять финансовое решение в транзакции, проверяйте критичные данные on-chain: через контракт, oracle feed или собственную validation-логику.

Частые ошибки

Держать API key в frontend → Gateway key должен жить на сервере, в backend route или в локальном dev окружении.

Считать subgraph данные мгновенными → индексатору нужно время, чтобы обработать новые blocks. Для pending-state используйте RPC.

Игнорировать errors в GraphQL ответе → запрос может вернуть 200 OK и одновременно список ошибок.

Использовать skip для очень глубокой pagination → большие offsets становятся медленными. Для production используйте cursor по id_gt или другому indexed field.


Что дальше

Материалы китаev.tech публикуются в образовательных целях и не являются инвестиционной рекомендацией. Примеры кода и описания протоколов — для обучения; использование в продакшне на ваш собственный риск. Дисклеймер и политика конфиденциальности.