Skip to content

Commit 46c5052

Browse files
committed
TonRecipes with RootDnsRecipes
1 parent 5fc339c commit 46c5052

File tree

7 files changed

+776
-60
lines changed

7 files changed

+776
-60
lines changed
Lines changed: 30 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Microsoft.Extensions.Logging;
2-
using TonLibDotNet.Types;
32
using TonLibDotNet.Types.Msg;
43
using TonLibDotNet.Types.Smc;
54

@@ -26,79 +25,58 @@ public async Task Run(bool useMainnet)
2625

2726
await tonClient.InitIfNeeded();
2827

29-
// Step 1: find some actual domain auction.
30-
// We need to check last IN transactions on ".ton DNS" address
31-
var dnsAddress = new AccountAddress("EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz");
32-
var dnsState = await tonClient.GetAccountState(dnsAddress);
33-
var dnsTransactions = await tonClient.RawGetTransactions(dnsAddress, dnsState.LastTransactionId);
34-
var dnsTx = dnsTransactions.TransactionsList.FirstOrDefault(x => x.InMsg != null && x.InMsg.MsgData is DataText dt && !string.IsNullOrEmpty(dt.Text));
35-
if (dnsTx == null)
28+
// First, we need to find some actual domain auction.
29+
var (domainName, domainAddress) = await FindDomainOnAuction();
30+
if (string.IsNullOrEmpty(domainName))
3631
{
37-
logger.LogError("Failed to find last Domain transaction");
32+
logger.LogError("Failed to find last Domain-on-Auction transaction");
3833
return;
3934
}
4035

41-
TonUtils.Text.TryDecodeBase64((dnsTx.InMsg.MsgData as DataText).Text, out var domain);
42-
43-
var domainAddress = dnsTx.OutMsgs[0].Destination;
44-
var bid = TonUtils.Coins.FromNano(dnsTx.OutMsgs[0].Value);
45-
46-
logger.LogInformation("Last auction found: name {Name}.ton (address {Address}), last bid = {Value} TON, bidder is {Address}", domain, domainAddress.Value, bid, dnsTx.InMsg.Source.Value);
47-
48-
// Step 2: Get some data from NFT
49-
var smc = await tonClient.SmcLoad(domainAddress);
50-
51-
// Method 1: Call get_auction_info, it returns:
36+
// Method 1: Call get_auction_info manually, it returns:
5237
// ;; MsgAddressInt max_bid_address
5338
// ;; Coins max_bid_amount
5439
// ;; int auction_end_time
40+
var smc = await tonClient.SmcLoad(domainAddress);
5541
var smcgai = await tonClient.SmcRunGetMethod(smc.Id, new MethodIdName("get_auction_info"));
5642
var adr = smcgai.Stack[0].ToTvmCell().ToBoc().RootCells[0].BeginRead().LoadAddressIntStd();
5743
var coins = long.Parse(smcgai.Stack[1].ToTvmNumberDecimal());
5844
var endTime = long.Parse(smcgai.Stack[2].ToTvmNumberDecimal());
5945
logger.LogInformation("Auction info (method 1): last bid = {Value} TON, bidder is {Address}, auction ends at {Time}", TonUtils.Coins.FromNano(coins), adr, DateTimeOffset.FromUnixTimeSeconds(endTime));
6046

61-
// Method 2: Parse contract data
62-
// structure is (from explorer source code)
63-
// ;; uint256 index
64-
// ;; MsgAddressInt collection_address
65-
// ;; MsgAddressInt owner_address
66-
// ;; cell content
67-
// ;; cell domain -e.g contains "alice"(without ending \0) for "alice.ton" domain
68-
// ;; cell auction - auction info
69-
// ;; int last_fill_up_time
70-
var data = await tonClient.SmcGetData(smc.Id);
47+
// Method 1-bis: use TonRecipes to call 'get_auction_info' method.
48+
var ai = await TonRecipes.RootDns.GetAuctionInfo(tonClient, domainAddress);
49+
logger.LogInformation("Auction info (method 1-bis): last bid = {Value} TON, bidder is {Address}, auction ends at {Time}", ai!.MaxBidAmount, ai.MaxBidAddress, ai.AuctionEndTime);
7150

72-
var slice = data.ToBoc().RootCells[0].BeginRead();
51+
// Method 2: Use TonRecipes to parse all DNS Item data.
52+
var di = await TonRecipes.RootDns.GetAllInfo(tonClient, domainName);
53+
logger.LogInformation("Auction info (method 2): last bid = {Value} TON, bidder is {Address}, auction ends at {Time}", di.AuctionInfo!.MaxBidAmount, di.AuctionInfo.MaxBidAddress, di.AuctionInfo.AuctionEndTime);
54+
}
55+
56+
private async Task<(string name, string address)> FindDomainOnAuction()
57+
{
58+
// We need to check last IN transactions on ".ton DNS" address
59+
var rootDnsAddress = "EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz";
7360

74-
// skip index
75-
slice.SkipBits(256);
61+
var rootDnsState = await tonClient.GetAccountState(rootDnsAddress);
62+
var rootDnsTransactions = await tonClient.RawGetTransactions(rootDnsAddress, rootDnsState.LastTransactionId);
7663

77-
// load (and compare) collection address
78-
var ca = slice.LoadAddressIntStd();
79-
if (ca != dnsAddress.Value)
64+
// Look for transaction with text comment
65+
var dnsTx = rootDnsTransactions.TransactionsList.FirstOrDefault(x => x.InMsg != null && x.InMsg.MsgData is DataText dt && !string.IsNullOrEmpty(dt.Text));
66+
if (dnsTx == null)
8067
{
81-
throw new Exception("Address mismatch. Something went wrong...");
68+
return (string.Empty, string.Empty);
8269
}
8370

84-
// owner address (usually empty, as auction is in progress)
85-
slice.TryLoadAddressIntStd();
71+
TonUtils.Text.TryDecodeBase64((dnsTx.InMsg!.MsgData as DataText)!.Text, out var domain);
72+
domain += ".ton";
8673

87-
// skip Content cell for now
88-
slice.LoadRef();
74+
var domainAddress = dnsTx.OutMsgs![0].Destination;
75+
var bid = TonUtils.Coins.FromNano(dnsTx.OutMsgs[0].Value);
8976

90-
// domain name is in second cell
91-
var domainName2 = System.Text.Encoding.ASCII.GetString(slice.LoadRef().Content);
92-
if (domainName2 != domain)
93-
{
94-
throw new Exception("Domain name mismatch. Something went wrong...");
95-
}
77+
logger.LogInformation("Last auction found: domain '{Name}' (address {Address}), last bid = {Value} TON, bidder is {Address}", domain, domainAddress.Value, bid, dnsTx.InMsg.Source.Value);
9678

97-
var aucinfo = slice.LoadDict().BeginRead();
98-
var adr2 = aucinfo.LoadAddressIntStd();
99-
var coins2 = aucinfo.LoadCoins();
100-
var endTime2 = aucinfo.LoadLong();
101-
logger.LogInformation("Auction info (method 2): last bid = {Value} TON, bidder is {Address}, auction ends at {Time}", TonUtils.Coins.FromNano(coins2), adr2, DateTimeOffset.FromUnixTimeSeconds(endTime2));
79+
return (domain, domainAddress.Value);
10280
}
10381
}
10482
}

TonLibDotNet.Demo/Samples/ResolveDomains.cs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.Extensions.Logging;
1+
using System.Text;
2+
using Microsoft.Extensions.Logging;
23
using TonLibDotNet.Types.Dns;
34

45
namespace TonLibDotNet.Samples
@@ -8,6 +9,13 @@ public class ResolveDomains
89
private readonly ITonClient tonClient;
910
private readonly ILogger logger;
1011

12+
// Values below may change over time. Verify them using explorers as first troubleshooting step.
13+
private const string DomainName = "toncenter";
14+
private const string DomainNameFull = "toncenter.ton";
15+
private const string CollectionAddress = "EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz";
16+
private const string DomainNftAddress = "EQAiIsvar4OYBn8BGBf9flfin6tl5poBx4MgJe4CQJYasy51";
17+
private const string OwnerAddress = "EQCh-GaMveITkw41cHEvi13ZzAXNVtRksHq_PvGuMFENnhrT";
18+
1119
public ResolveDomains(ITonClient tonClient, ILogger<ResolveDomains> logger)
1220
{
1321
this.tonClient = tonClient ?? throw new ArgumentNullException(nameof(tonClient));
@@ -25,14 +33,14 @@ public async Task Run(bool useMainnet)
2533
await tonClient.InitIfNeeded();
2634

2735
// Method 1: use non-empty TTL
28-
var res1 = await tonClient.DnsResolve("toncenter.ton", ttl: 9);
36+
var res1 = await tonClient.DnsResolve(DomainNameFull, ttl: 9);
2937
var adnl1 = (res1.Entries[0].Value as EntryDataAdnlAddress)?.AdnlAddress.Value;
3038

3139
// Method 2: Use zero TTL and recurse yourself
3240
var res2 = await tonClient.DnsResolve("ton", null, null, null);
3341
// Now we have NFT Collection (EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz) in 'NextResolver'
3442
// Let's ask it for next part
35-
res2 = await tonClient.DnsResolve("toncenter", (res2.Entries[0].Value as EntryDataNextResolver).Resolver, null, null);
43+
res2 = await tonClient.DnsResolve(DomainName, (res2.Entries[0].Value as EntryDataNextResolver).Resolver, null, null);
3644
// Now we have NFT itself (Contract Type = Domain, toncenter.ton, EQAiIsvar4OYBn8BGBf9flfin6tl5poBx4MgJe4CQJYasy51) in 'NextResolver'
3745
var nftAccountAddress = (res2.Entries[0].Value as EntryDataNextResolver).Resolver;
3846
// Let's ask it about actual ADNL of this domain
@@ -43,16 +51,51 @@ public async Task Run(bool useMainnet)
4351
logger.LogInformation("Results:\r\nADNL (method 1): {Val}\r\nADNL (method 2): {Val}", adnl1, adnl2);
4452

4553
// Some experiments. Try TTL=1
46-
var res3 = await tonClient.DnsResolve("toncenter.ton", null, 1);
54+
var res3 = await tonClient.DnsResolve(DomainNameFull, null, 1);
4755
// We now have again 'EQAiIsvar4OYBn8BGBf9flfin6tl5poBx4MgJe4CQJYasy51' in 'NextResolver' !
4856
// You see? TTL is a number of 'recursive iterations' to 'NextResolvers' that is performed by LiteServer itself.
4957
// Checking with TTL=2, we should receive final ADNL record:
50-
var res4 = await tonClient.DnsResolve("toncenter.ton", null, 2);
58+
var res4 = await tonClient.DnsResolve(DomainNameFull, null, 2);
5159
var adnl4 = (res4.Entries[0].Value as EntryDataAdnlAddress)?.AdnlAddress.Value; // Yes, we do!
5260

53-
// Unfortunately, asking for account state of NFT itself returns raw.AccountState, not Dns.AccountState, I don't know why :(
54-
_ = await tonClient.GetAccountState(nftAccountAddress);
55-
// But check ReadInfoFromSmartContracts() to know how to get owner of this NFT!
61+
// and get records
62+
var entries = await TonRecipes.RootDns.GetEntries(tonClient, nftAccountAddress.Value);
63+
logger.LogInformation("Wallet: {Value}", entries.Wallet);
64+
logger.LogInformation("Storage: {Value}", entries.Storage);
65+
logger.LogInformation("Site (ADNL): {Value}", entries.SiteToAdnl);
66+
logger.LogInformation("Site (Storage):{Value}", entries.SiteToStorage);
67+
logger.LogInformation("Next Resolver: {Value}", entries.DnsNextResolver);
68+
69+
70+
// Method 3: Use TonRecipes and recieve all info with one call
71+
var di = await TonRecipes.RootDns.GetAllInfo(tonClient, DomainNameFull);
72+
73+
logger.LogInformation("TonRecipes info for '{Domain}':", di.Name);
74+
logger.LogInformation(" Collection is: {Value}", di.CollectionAddress);
75+
logger.LogInformation(" Index is: {Value}", Convert.ToBase64String(di.Index));
76+
logger.LogInformation(" NFT address: {Value}", di.Address);
77+
logger.LogInformation(" Deployed?: {Value}", di.IsDeployed);
78+
79+
if (di.IsDeployed)
80+
{
81+
logger.LogInformation(" Owner: {Value}", di.EditorAddress);
82+
logger.LogInformation(" In auction: {Value}", di.AuctionInfo != null);
83+
if (di.AuctionInfo != null)
84+
{
85+
logger.LogInformation(" Max Bid: {Value}", di.AuctionInfo.MaxBidAmount);
86+
logger.LogInformation(" Max Bidder: {Value}", di.AuctionInfo.MaxBidAddress);
87+
logger.LogInformation(" End time: {Value}", di.AuctionInfo.AuctionEndTime);
88+
}
89+
90+
logger.LogInformation(" Last fill-up: {Value}", di.LastFillUpTime);
91+
92+
logger.LogInformation(" DNS Entries:");
93+
logger.LogInformation(" Wallet: {Value}", di.Entries.Wallet);
94+
logger.LogInformation(" Site (ADNL):{Value}", di.Entries.SiteToAdnl);
95+
logger.LogInformation(" Site (Stor):{Value}", di.Entries.SiteToStorage);
96+
logger.LogInformation(" Storage: {Value}", di.Entries.Storage);
97+
logger.LogInformation(" Next resolv:{Value}", di.Entries.DnsNextResolver);
98+
}
5699
}
57100
}
58101
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
namespace TonLibDotNet.Recipes
2+
{
3+
/// <remarks>
4+
/// Based on <see href="https://github.com/ton-blockchain/TEPs/blob/master/text/0081-dns-standard.md">TEP 81: TON DNS Standard</see>
5+
/// and <see href="https://github.com/ton-blockchain/dns-contract">TON DNS Smart Contracts</see>.
6+
/// </remarks>
7+
public partial class RootDnsRecipes
8+
{
9+
/// <remarks>
10+
/// TL-B: <code>dns_smc_address#9fd3 smc_addr:MsgAddressInt flags:(## 8) { flags &lt;= 1 } cap_list:flags . 0?SmcCapList = DNSRecord</code>
11+
/// </remarks>
12+
private const ushort DnsEntryTypeWallet = 0x9fd3;
13+
14+
/// <remarks>
15+
/// TL-B: <code>dns_next_resolver#ba93 resolver:MsgAddressInt = DNSRecord</code>
16+
/// </remarks>
17+
private const ushort DnsEntryTypeNextDnsResolver = 0xba93;
18+
19+
/// <remarks>
20+
/// TL-B: <code>dns_adnl_address#ad01 adnl_addr:bits256 flags:(## 8) { flags &lt;= 1 } proto_list:flags . 0?ProtoList = DNSRecord</code>
21+
/// </remarks>
22+
private const ushort DnsEntryTypeAdnl = 0xad01;
23+
24+
/// <remarks>
25+
/// TL-B: <code>dns_storage_address#7473 bag_id:bits256 = DNSRecord</code>
26+
/// </remarks>
27+
private const ushort DnsEntryTypeStorageBagId = 0x7473;
28+
29+
private const string CategoryNameSite = "site";
30+
private const string CategoryNameWallet = "wallet";
31+
private const string CategoryNameStorage = "storage";
32+
private const string CategoryNameNextDnsResolver = "dns_next_resolver";
33+
34+
private static readonly byte[] CategoryBytesSite = EncodeCategory(CategoryNameSite);
35+
private static readonly byte[] CategoryBytesWallet = EncodeCategory(CategoryNameWallet);
36+
private static readonly byte[] CategoryBytesStorage = EncodeCategory(CategoryNameStorage);
37+
private static readonly byte[] CategoryBytesNextDnsResolver = EncodeCategory(CategoryNameNextDnsResolver);
38+
39+
/// <summary>
40+
/// From <see href="https://github.com/ton-blockchain/dns-contract/blob/main/func/dns-utils.fc">dns-utils.fc</see>
41+
/// </summary>
42+
private const int OPChangeDnsRecord = 0x4eb1f0f9;
43+
44+
public static readonly RootDnsRecipes Instance = new();
45+
46+
protected static byte[] EncodeCategory(string categoryName)
47+
{
48+
return System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.ASCII.GetBytes(categoryName));
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)