Для любой технической задачи на TON, вам необходимо использовать индексаторы. Индексаторы это сервисы, которые агрегируют внутри себя транзакции блокчейна, обогащают данные и позволяют получить эти данные в необходимом виде. 

Без использования таких сервисов, для каждого запроса информации, вам бы пришлось парсить кучу блоков блокчейна, чтобы вернуть данные. В данной статье, я покажу вам как делать GraphQL запросы в dton.io на блокчейне TON. Возьмем простую задачу и пройдем весь путь формирования запроса и параллельно рассмотрим основные возможности индексатора.

Что такое dton.io?

Dton.io индексатор блокчейна, что значит, он собирает информацию из каждого нового блока в свою базу данных. В эту базу данных можно делать GraphQL запросы и таким образом собирать историческую информацию без парсинга всей цепочки блоков.

Две самых важных источника данных dton.io это таблица транзакций и представление последнего состояния кошельков/аккаунтов в сети. Делая запросы в данной представление и таблицу, можно собрать почти любую информацию. 

Большим плюсом dton.io, является обогащение данных, помимо нативных данных транзакций и блоков, dton.io собирает информацию из регистра данных смарт-контрактов, а также обогащает данными популярные стандарты токенов на TON, такие как NFT - стандарт не взаимозаменяемых токенов и Jetton - стандарт взаимозаменяемых токенов.

Обогащение данных позволяет уменьшить количество необходимых запросов.

Как происходит процесс формирования запроса

Верхнеуровневый алгоритм всегда следующий:

  1. понять как задача выглядит на уровне смарт-контрактов

  2. иттеративный сбор запроса + борьба с возникающими проблемами(вроде таймаута на очень крупных запросах), валидация правильности данных

В силу специфики архитектуры смарт-контрактов на TON, первая часть зачастую является самой сложной - одной логической операции, например поставка ликвидности в пул, может участвовать несколько контрактов и конечно же искать, где там среди 5 контрактов передается нужная вам информация получиться, только погружаясь в смарт-контракты.

Кстати запросы, которые мы будем рассматривать ниже, вы можете запутить тут - https://dton.io/graphql/

Формулируем задачу

В рамках данного туториала, мы возьмем понятную широкому кругу задачу: найти самых крупных покупателей NFT для выбранной коллекции.

Предпосылкой такого запроса может быть, желание найти, кому продать свою нфт. Но в данном туториале это просто удобная задача, через которую можно показать большую часть функционала dton.io.

Что это значит с точки зрения смарт-контрактов

Прежде чем прыгать с головой в поля доступные в индексаторе и думать, как нам агрегировать данные по сумме и количеству сделок, давайте поймем как вообще происходят продажи NFT в TON.

Почти любая сделка подразумевает некий контракт продажи. на который “перемещают NFT”, данный контракт реализует логику продажи, это может быть как просто продажа любому желающему заплатить сумму, так и сложная логика в виде аукциона. 

Соответственно с точки зрения данных продажей можно назвать транзакцию в которой меняется собственник NFT. Посмотреть примеры разных контрактов продаж можно тут - https://github.com/ton-blockchain/token-contract/blob/main/nft/nft-sale.fc. Это простейший контракт продажи написанный на языке FUNC.

Теперь попробуем сделать первый запрос.

Достанем последние транзакции с продажами nft

Итак, нас интересуют транзакции с передачей права собственности NFT, значит будем использовать представление транзакций:

      {
        raw_transactions(
        ) {
        }
      }

Теперь попробуем наполнить запрос, посмотреть все доступные поля можно в документации https://docs.dton.io/transactions-and-account-states .

Возьмем для примера коллекцию NFT Telegram Username, а также уже законченные сделки:

      {
        raw_transactions(
          parsed_seller_is_closed: 1
          parsed_nft_collection_address_address: "80D78A35F955A14B679FAA887FF4CD5BFC0F43B4A4EEA2A7E6927F3701B273C2"
        ) {
          nft_address: address
          col_address: parsed_nft_collection_address_address
        }
      }

Поля начинающиеся с parsed это обогащенные, а не нативные поля. Теперь, нам важно убрать транзакции в которые происходят не с NFT, для этого добавим поле  account_state_state_init_code_has_get_nft_data: 1 . Здесь может возникнуть вопрос что за get_nft_data, самым простым способ отличать разные типы смарт-контрактов, является их сигнатура, смарт-контракты определенных стандартов, должны содержать определенные функции и вызовы. Стандарт NFT в TON предполагает наличие GET метода get_nft_data.

      {
        raw_transactions(
          parsed_seller_is_closed: 1
          account_state_state_init_code_has_get_nft_data: 1
          workchain: 0
          page: 0
          page_size: 10
          parsed_nft_collection_address_address: "80D78A35F955A14B679FAA887FF4CD5BFC0F43B4A4EEA2A7E6927F3701B273C2"
        ) {
          prev_owner: parsed_seller_nft_prev_owner_address_address
          new_owner: parsed_seller_nft_new_owner_address_address
          nft_address: address
          col_address: parsed_nft_collection_address_address
          price: parsed_seller_nft_price
        }
      }

Также сразу добавим parsed значения цены  и предыдущего и нового владельца. Предлагаю выполнить запрос.

Проблема таймаут

После выполнения запроса мы увидим:

{
  "data": {},
  "errors": [
    "Timeout exceeded, simplify your query (https://docs.dton.io/query-speed-optimization-timeout)"
  ]
}

Проблема заключается в том, что наш запрос, отрабатывает по всей базе индексатора. Возможные варианты решения проблемы описнны тут https://docs.dton.io/query-speed-optimization-timeout. Самым простым вариантом явлется добавления фильтра по времени. 

Для этого нам понадобятся фильтры.

Фильтруем запрос по времени

Фильтры в dton.io на поля можно добавлть с помощью нижнего подчеркивания. Фильтр на время генерации один из самых распространенных - он позволяет “не бегать” запросом по всей базе.

Сузим нашу задачу и будем искать только за последний месяц:

      {
        raw_transactions(
          parsed_seller_is_closed: 1
          account_state_state_init_code_has_get_nft_data: 1
          workchain: 0
          page: 0
          page_size: 10
          gen_utime__gt: "2024-12-04 00:00:00"
          parsed_nft_collection_address_address: "80D78A35F955A14B679FAA887FF4CD5BFC0F43B4A4EEA2A7E6927F3701B273C2"
        ) {
          prev_owner: parsed_seller_nft_prev_owner_address_address
          new_owner: parsed_seller_nft_new_owner_address_address
          nft_address: address
          col_address: parsed_nft_collection_address_address
          price: parsed_seller_nft_price
        }
      }

Список доступных фильтров здесь - https://docs.dton.io/filters

В запросе вы можете видеть page и page_size, это обычная пагинация. Отображения транзакции и состояний аккаунтов поддерживают максимум 150 записей для страницы.

Попробуем запустить запрос, получим примерно следующее:

{
  "data": {
    "raw_transactions": [
      {
        "prev_owner": "CF01D60CC8924D3C34C54A5787B9175BCD8D45D9ADA91371F93318DF2B76EBDB",
        "new_owner": "69B8D2CA45C14F056FED73236A6A0CB7AB5BA2B1A0F2E1AD784D84F7B4454D81",
        "nft_address": "8C08EBCDDEDB79E6FF3AA1B4F0A65388D16FE2F1D86BFBA46E25833A24A659F4",
        "col_address": "80D78A35F955A14B679FAA887FF4CD5BFC0F43B4A4EEA2A7E6927F3701B273C2",
        "price": 4746195291
      },
      {
        "prev_owner": "50FC81C8CFEA8780CEF8B636D3643ED9F7E5ADD3D4E8045510FD19AF2EAC337D",
        "new_owner": "0E4A5829EEBC85FB049DD63B5CCF801D3DC411780ABEC22F8F8918B66B852337",
        "nft_address": "E20696CD521BF88D729E06827587D2908E0ED94ED5910A79D26069C4F9F261AB",
        "col_address": "80D78A35F955A14B679FAA887FF4CD5BFC0F43B4A4EEA2A7E6927F3701B273C2",
        "price": 8479134769
      }
…
  ]
  },
  "errors": []
}

Убираем отмывочную торговлю

Как я и упоминал в начале статьи, сборка запроса итеративный процесс, а значит пришло время вернуться к бизнес задаче. Для интересующей нас статистики, нам важно убрать отмывочную торговлю, например, когда перепродажа идет между своим же кошельком.

В этом нам помогут фильтры состоящие из логических операторов, в данном случае not. Уберем из запроса транзакции, в которых старый и новый владельце одинаковые.

      {
        raw_transactions(
          parsed_seller_is_closed: 1
          account_state_state_init_code_has_get_nft_data: 1
          workchain: 0
          page: 0
          page_size: 10
          gen_utime__gt: "2024-12-04 00:00:00"
          not__: {parsed_seller_nft_prev_owner_address_address: new_owner}
          parsed_nft_collection_address_address: "80D78A35F955A14B679FAA887FF4CD5BFC0F43B4A4EEA2A7E6927F3701B273C2"
        ) {
          prev_owner: parsed_seller_nft_prev_owner_address_address
          new_owner: parsed_seller_nft_new_owner_address_address
          nft_address: address
          col_address: parsed_nft_collection_address_address
          price: parsed_seller_nft_price
        }
      }

Удобные фичи - преобразуем адреса

Как и в других блокчейнах в TON существуют разные представления адресов, чтобы привести адреса в запросе в чаще всего употребимый вид, нужно поменять поля на такие же поля но с приставкой friendly.

В нашем случае получиться примерно вот так:

      {
        raw_transactions(
          parsed_seller_is_closed: 1
          account_state_state_init_code_has_get_nft_data: 1
          workchain: 0
          page: 0
          page_size: 10
          gen_utime__gt: "2024-12-04 00:00:00"
          not__: {parsed_seller_nft_prev_owner_address_address: new_owner}
          order_by: "parsed_seller_nft_price",
          order_desc: true,
        parsed_nft_collection_address_address: "80D78A35F955A14B679FAA887FF4CD5BFC0F43B4A4EEA2A7E6927F3701B273C2"
        ) {
          prev_owner: parsed_seller_nft_prev_owner_address_address__friendly
          new_owner: parsed_seller_nft_new_owner_address_address__friendly
          nft_address: address__friendly
          col_address: parsed_nft_collection_address_address__friendly
          price: parsed_seller_nft_price
        }
      }

Если вам интересно почитать про разные представления адрессов, в TON есть отдельный стандарт :https://github.com/ton-blockchain/TEPs/blob/master/text/0002-address.md

Агрегация данных

После того как мы собрали простой запрос, посмотрели, что в ответе на запрос мы получаем те транзакции, которые ожидали, можно перейти к сборке агрегированного запроса.

Для агрегации можно использовать endpoint’ы groupTransactions и groupAccountStates для выполнения операций агрегации. Эти endpoint’ы поддерживают различные функции агрегации и позволяют группировать, сортировать и фильтровать результаты.

В агрегационных запросов, довольно много параметров, именно поэтому изначально я советую собирать простые запросы и только потом из просто собирать агрегацию.

Поля, по которым мы фильтровали запрос кладем в filter. остальные же поля заполняем в соответствии с логикой агрегации:

{
  groupTransactions(
    by: ["parsed_seller_nft_new_owner_address_address__friendly"],
    aggregations: [
      {field: "parsed_seller_nft_price", operation: "sum"},
      {operation: "count"}
    ],
    order_by: "parsed_seller_nft_price__sum",
    order_desc: true,
    page: 0,
    page_size: 10,
    filter_by: {or__: [
      {not__: {
        parsed_seller_nft_prev_owner_address_address: parsed_seller_nft_new_owner_address_address
      }},
      {and__: {
          parsed_seller_is_closed: 1
          account_state_state_init_code_has_get_nft_data: 1
          workchain: 0
          gen_utime__gt: "2024-12-04 00:00:00"
          parsed_nft_collection_address_address: "80D78A35F955A14B679FAA887FF4CD5BFC0F43B4A4EEA2A7E6927F3701B273C2"
      }}]}
  ) {
    trader_new_address:parsed_seller_nft_new_owner_address_address__friendly
    deal_count: count
    deal_sum: parsed_seller_nft_price__sum
  }
}

С помощью by агрегируем по адресу нового владельца, в остальных полях прописываем логику подсчета по цене. Для репрезентативности посчитаем и сумму и количество сделок. Подробнее об агрегации здесь - https://docs.dton.io/aggregations.

Отмечу, что для выполения крупных агрегаций, необходим платный план dton.io

Заключение

Мне нравится блокчейн TON своей технической изящностью, как минимум это не очередная копия Ethereum, которую разгоняют с помощью большого капитала без оглядки, а вообще зачем это нужно пользователю. Если вы хотите узнать больше о блокчейне TON, у меня есть опенсорсные уроки, благодаря которым вы научитесь создавать полноценные приложения на TON.

https://github.com/romanovichim/TonFunClessons_ru

Новые туториалы и дата аналитику я кидаю сюда: https://t.me/ton_learn

Комментарии (1)


  1. 2b3q
    16.12.2024 07:35

    похоже на сабграфы https://thegraph.com/docs/en/developing/creating-a-subgraph/ для индексирования и организации данных EVM БЧ. В сабграфах определяется схема данных и мэппинг событий смарт контрактов, которые индексирует сабграф. Доступ к данным через GraphQL. Методы индексирования и обработки данных разные.

    На первый взгляд dton.io более надежен и быстрей для EndUser'a, чем сабграфы, из-а меньшей кастомизации и большей скорости самой сети TON, напрямую агреггирует данные из сети. Обогащенные данные сразу доступны без дополнительной обработки.

    Обвязка и кастомизация сабграфов на EVM поддерживает много EVM чейнов и имеет большее кол-во абстракций агрегации данных, что снижает скорость синхронизации особенно в сетях с высокой нагрузкой.