Для любой технической задачи на TON, вам необходимо использовать индексаторы. Индексаторы это сервисы, которые агрегируют внутри себя транзакции блокчейна, обогащают данные и позволяют получить эти данные в необходимом виде.
Без использования таких сервисов, для каждого запроса информации, вам бы пришлось парсить кучу блоков блокчейна, чтобы вернуть данные. В данной статье, я покажу вам как делать GraphQL запросы в dton.io на блокчейне TON. Возьмем простую задачу и пройдем весь путь формирования запроса и параллельно рассмотрим основные возможности индексатора.
Что такое dton.io?
Dton.io индексатор блокчейна, что значит, он собирает информацию из каждого нового блока в свою базу данных. В эту базу данных можно делать GraphQL запросы и таким образом собирать историческую информацию без парсинга всей цепочки блоков.
Две самых важных источника данных dton.io это таблица транзакций и представление последнего состояния кошельков/аккаунтов в сети. Делая запросы в данной представление и таблицу, можно собрать почти любую информацию.
Большим плюсом dton.io, является обогащение данных, помимо нативных данных транзакций и блоков, dton.io собирает информацию из регистра данных смарт-контрактов, а также обогащает данными популярные стандарты токенов на TON, такие как NFT — стандарт не взаимозаменяемых токенов и Jetton — стандарт взаимозаменяемых токенов.
Обогащение данных позволяет уменьшить количество необходимых запросов.
Как происходит процесс формирования запроса
Верхнеуровневый алгоритм всегда следующий:
-
понять как задача выглядит на уровне смарт-контрактов
-
иттеративный сбор запроса + борьба с возникающими проблемами(вроде таймаута на очень крупных запросах), валидация правильности данных
В силу специфики архитектуры смарт-контрактов на 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
ссылка на оригинал статьи https://habr.com/ru/articles/866744/
Добавить комментарий