For any technical task on TON, you need to use indexers. Indexers are services that aggregate blockchain transactions within themselves, enrich the data and allow you to get this data in the required form.
Without using such services, for each request for information, you would have to parse a bunch of blockchain blocks to return the data. In this article, I will show you how to make GraphQL queries in dton.io on the TON blockchain. Let's take a simple task and go through the entire path of forming a request and at the same time consider the main features of the indexer.
What is dton.io?
Dton.io is a blockchain indexer, which means it collects information from each new block into its database. You can make GraphQL queries to this database and thus collect historical information without parsing the entire chain of blocks.
The two most important sources of dton.io data are the transaction table and the view of the latest state of wallets/accounts in the network. By making queries to this view and table, you can collect almost any information.
A big plus of dton.io is data enrichment, in addition to native transaction and block data, dton.io collects information from the smart contract data registry, and also enriches popular token standards on TON with data, such as NFT - the standard of non-fungible tokens and Jetton - the standard of fungible tokens.
Data enrichment allows you to reduce the number of necessary queries.
How does the request formation process work
The top-level algorithm is always the following:
understand how the task looks at the smart contract level
iterative request collection + combating emerging problems (like timeout on very large requests), validating the correctness of the data
Due to the specifics of the smart contract architecture on TON, the first part is often the most difficult - one logical operation, for example, supplying liquidity to the pool, can involve several contracts and, of course, searching for where among the 5 contracts the information you need is transmitted can only be obtained by delving into smart contracts.
By the way, you can run the requests that we will consider below here - https://dton.io/graphql/
Formulating the task
Within the framework of this tutorial, we will take a task that is understandable to a wide range: find the largest NFT buyers for the selected collection.
The premise of such a request may be the desire to find someone to sell your NFT to. But in this tutorial, this is just a convenient task through which you can show most of the functionality of dton.io..
What does this mean in terms of smart contracts
Before jumping headlong into the fields available in the indexer and thinking about how we aggregate data on the amount and number of transactions, let's understand how NFT sales in TON occur.
Almost any transaction implies a certain sales contract to which the NFT is โmovedโ, this contract implements the logic of the sale, it can be either a simple sale to anyone willing to pay the amount, or complex logic in the form of an auction.
Accordingly, from the data point of view, a sale can be called a transaction in which the owner of the NFT changes. You can see examples of different sales contracts here - https://github.com/ton-blockchain/token-contract/blob/main/nft/nft-sale.fc. This is the simplest sales contract written in the FUNC language.
Now let's try to make the first request.
Let's get the latest transactions with nft sales
So, we are interested in transactions with the transfer of ownership of NFT, so we will use the transaction representation:
{
raw_transactions(
) {
}
}
Now let's try to fill in the request, you can see all available fields in the documentation https://docs.dton.io/transactions-and-account-states .
Let's take the Telegram Username NFT collection as an example, as well as already completed transactions:
{
raw_transactions(
parsed_seller_is_closed: 1
parsed_nft_collection_address_address: "80D78A35F955A14B679FAA887FF4CD5BFC0F43B4A4EEA2A7E6927F3701B273C2"
) {
nft_address: address
col_address: parsed_nft_collection_address_address
}
}
Fields starting with parsed are enriched, not native fields. Now, it is important for us to remove transactions that do not occur with NFT, for this we will add the field account_state_state_init_code_has_get_nft_data: 1 .
Here the question may arise what is get_nft_data, the easiest way to distinguish different types of smart contracts is their signature, smart contracts of certain standards must contain certain functions and calls. The NFT standard in TON assumes the presence of the get_nft_data method.
{
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
}
}
We will also immediately add parsed values โโof the price of both the previous and new owner. I suggest executing the request.
Timeout problem
After executing the request we will see:
{
"data": {},
"errors": [
"Timeout exceeded, simplify your query (https://docs.dton.io/query-speed-optimization-timeout)"
]
}
The problem is that our query is processed across the entire indexer database. Possible solutions to the problem are described here https://docs.dton.io/query-speed-optimization-timeout. The simplest option is to add a time filter.
For this, we need filters.
Filtering the query by time
Filters in dton.io can be added to fields using underscores. The generation time filter is one of the most common - it allows you to โnot runโ the query through the entire database.
Let's narrow our task and search only for the last month:
{
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
}
}
The list of available filters is here - https://docs.dton.io/filters
In the request you can see page and page_size, this is a normal pagination. Transaction and account status displays support a maximum of 150 records per page.
Let's try to run the request, we'll get something like this:
{
"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": []
}
Removing wash trade
As I mentioned at the beginning of the article, query assembly is an iterative process, which means it's time to get back to the business task. For the statistics we are interested in, it is important for us to remove wash trade, for example, when resale occurs between one's own wallet.
Filters consisting of logical operators will help us with this, in this case not. We will remove from the query transactions in which the old and new owners are the same.
{
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
}
}
Let's satisfy your curiosity - what are the largest deals
dton.io allows you to specify the order by any field, for example, you can sort by price, get the largest deals first:
{
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
new_owner: parsed_seller_nft_new_owner_address_address
nft_address: address
col_address: parsed_nft_collection_address_address
price: parsed_seller_nft_price
}
}
Convenient features - converting addresses
As in other blockchains, TON has different representations of addresses. To convert the addresses in the request to the most commonly used form, you need to change the fields to the same fields but with the prefix friendly.
In our case, it will look something like this:
{
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
}
}
If you are interested in reading about different representations of addresses, TON has a separate standard :https://github.com/ton-blockchain/TEPs/blob/master/text/0002-address.md
Data aggregation
After we have collected a simple query, checked that in the response to the query we receive the transactions that we expected, we can proceed to the assembly of the aggregated query.
For aggregation, you can use the groupTransactions and groupAccountStates endpoints to perform aggregation operations. These endpoints support various aggregation functions and allow you to group, sort and filter the results.
There are quite a few parameters in aggregation queries, which is why I initially advise collecting simple queries and only then simply collecting the aggregation from them.
We put the fields by which we filtered the query in filter. The remaining fields are filled in accordance with the aggregation logic:
{
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
}
}
Using by, we aggregate by the address of the new owner, in the remaining fields we write the logic of calculation by price. For representativeness, we will calculate both the amount and the number of transactions. More information about aggregation is here - https://docs.dton.io/aggregations.
I will note that to perform large aggregations, a paid dton.io plan is required
Conclusion
I like the TON blockchain for its technical elegance, at least it is not another copy of Ethereum, which is accelerated with the help of large capital without looking back, and in general why the user needs it. If you want to learn more about the TON blockchain, I have open source lessons, thanks to which you will learn how to create full-fledged applications on TON.
https://github.com/romanovichim/TonFunClessons_Eng
I post new tutorials and data analytics here: https://t.me/ton_learn
Top comments (0)