|
1 | 1 | using System.Globalization; |
| 2 | +using System.Numerics; |
2 | 3 | using TonLibDotNet.Types.Dns; |
3 | 4 | using TonLibDotNet.Types.Smc; |
| 5 | +using TonLibDotNet.Types.Tvm; |
4 | 6 | using TonLibDotNet.Utils; |
5 | 7 |
|
6 | 8 | namespace TonLibDotNet.Recipes |
7 | 9 | { |
8 | 10 | public partial class RootDnsRecipes |
9 | 11 | { |
10 | 12 | /// <summary> |
11 | | - /// Resolves DNS name into DNS Item NFT address (for both existing/minted and not-minted-yet domains). |
| 13 | + /// Resolves DNS name into DNS Item NFT address (for both existing/minted and not-minted-yet domains) by calling 'get_nft_address_by_index' method. |
12 | 14 | /// </summary> |
13 | 15 | /// <param name="tonClient"><see cref="ITonClient"/> instance.</param> |
14 | | - /// <param name="domainName">Domain name to resolve. Second-level only, e.g. 'alice.ton'.</param> |
| 16 | + /// <param name="domainName">Domain name to resolve, with or without '.ton' suffix.</param> |
15 | 17 | /// <returns>Address of DNS Item NFT for requested domain.</returns> |
16 | | - /// <exception cref="ArgumentOutOfRangeException">Requested domainName is not second-level one.</exception> |
| 18 | + /// <remarks>Only second-level .ton domains are allowed, with or without '.ton' suffix. E.g. 'alice.ton.', 'alice.ton' and 'alice' are allowed, but 'alice.t.me' is not.</remarks> |
| 19 | + /// <exception cref="ArgumentOutOfRangeException">Requested <paramref name="domainName"/> is not second-level one, or not from '.ton' namespace.</exception> |
| 20 | + /// <exception cref="ArgumentNullException">Requested <paramref name="domainName"/> null or white-space only.</exception> |
17 | 21 | public async Task<string> GetNftAddress(ITonClient tonClient, string domainName) |
18 | 22 | { |
19 | | - // Count dots in name, but not count last one. |
20 | | - var depth = domainName.Count(x => x == '.') - (domainName.EndsWith('.') ? 1 : 0); |
21 | | - if (depth != 1) |
| 23 | + if (string.IsNullOrWhiteSpace(domainName)) |
22 | 24 | { |
23 | | - throw new ArgumentOutOfRangeException(nameof(domainName), "Only second-level domains (e.g. alice.ton) are supported"); |
| 25 | + throw new ArgumentNullException(nameof(domainName)); |
24 | 26 | } |
25 | 27 |
|
26 | | - await tonClient.InitIfNeeded(); |
| 28 | + var parts = domainName.ToLowerInvariant().Split('.', StringSplitOptions.RemoveEmptyEntries); |
| 29 | + |
| 30 | + if (parts.Length > 2) |
| 31 | + { |
| 32 | + throw new ArgumentOutOfRangeException(nameof(domainName), "Only second-level domains (e.g. 'alice.ton') are supported."); |
| 33 | + } |
27 | 34 |
|
28 | | - var resolved = await tonClient.DnsResolve(domainName, ttl: 1); // only resolve in root contract |
| 35 | + if (parts.Length == 2 && !string.Equals(parts[1], "ton", StringComparison.InvariantCulture)) |
| 36 | + { |
| 37 | + throw new ArgumentOutOfRangeException(nameof(domainName), "Only '.ton' domains (e.g. 'alice.ton') are supported."); |
| 38 | + } |
29 | 39 |
|
30 | | - return ((EntryDataNextResolver)resolved.Entries[0].Value).Resolver.Value; |
| 40 | + // UTF-8 encoded string up to 126 bytes, https://github.com/ton-blockchain/TEPs/blob/master/text/0081-dns-standard.md#domain-names |
| 41 | + var bytes = System.Text.Encoding.UTF8.GetBytes(parts[0]); |
| 42 | + if (bytes.Length > 126) |
| 43 | + { |
| 44 | + throw new ArgumentOutOfRangeException(nameof(domainName), "Value is too long. No more than 126 chars are allowed."); |
| 45 | + } |
| 46 | + |
| 47 | + var hashSource = new byte[bytes.Length + 2]; |
| 48 | + hashSource[0] = 0; |
| 49 | + hashSource[1] = (byte)(bytes.Length * 2); |
| 50 | + bytes.CopyTo(hashSource, 2); |
| 51 | + var index = System.Security.Cryptography.SHA256.HashData(hashSource); |
| 52 | + |
| 53 | + await tonClient.InitIfNeeded().ConfigureAwait(false); |
| 54 | + |
| 55 | + var smc = await tonClient.SmcLoad(new Types.AccountAddress(TonRootCollection)).ConfigureAwait(false); |
| 56 | + |
| 57 | + // slice get_nft_address_by_index(int index) |
| 58 | + var stack = new List<StackEntry>() |
| 59 | + { |
| 60 | + new StackEntryNumber(new NumberDecimal(new BigInteger(index, true, true).ToString(CultureInfo.InvariantCulture))), |
| 61 | + }; |
| 62 | + var result = await tonClient.SmcRunGetMethod(smc.Id, new MethodIdName("get_nft_address_by_index"), stack).ConfigureAwait(false); |
| 63 | + |
| 64 | + await tonClient.SmcForget(smc.Id).ConfigureAwait(false); |
| 65 | + |
| 66 | + if (result.ExitCode != 0) |
| 67 | + { |
| 68 | + throw new TonLibNonZeroExitCodeException(result.ExitCode); |
| 69 | + } |
| 70 | + |
| 71 | + return result.Stack[0].ToTvmCell().ToBoc().RootCells[0].BeginRead().LoadAddressIntStd(); |
31 | 72 | } |
32 | 73 |
|
33 | 74 | /// <summary> |
@@ -204,24 +245,15 @@ public async Task<DnsEntries> GetEntries(ITonClient tonClient, string nftAddress |
204 | 245 | /// Resolves *.ton domain to DNS Item NFT and parses data of this contract. |
205 | 246 | /// </summary> |
206 | 247 | /// <param name="tonClient"><see cref="ITonClient"/> instance.</param> |
207 | | - /// <param name="domainName">Domain name to get data from. Second-level only, e.g. 'alice.ton'.</param> |
| 248 | + /// <param name="domainName">Domain name to resolve, with or without '.ton' suffix.</param> |
208 | 249 | /// <returns><see cref="DomainInfo"/> with data about domain.</returns> |
209 | 250 | /// <remarks>⚠ Method may fail if future versions of <see href="https://github.com/ton-blockchain/dns-contract/blob/main/func/nft-item.fc">DNS Item smartcontract</see> will change stored data layout.</remarks> |
210 | | - /// <exception cref="ArgumentOutOfRangeException">Requested domainName is not second-level one.</exception> |
| 251 | + /// <remarks>Only second-level .ton domains are allowed, with or without '.ton' suffix. E.g. 'alice.ton.', 'alice.ton' and 'alice' are allowed, but 'alice.t.me' is not.</remarks> |
| 252 | + /// <exception cref="ArgumentOutOfRangeException">Requested <paramref name="domainName"/> is not second-level one, or not from '.ton' namespace.</exception> |
| 253 | + /// <exception cref="ArgumentNullException">Requested <paramref name="domainName"/> null or white-space only.</exception> |
211 | 254 | public async Task<DomainInfo> GetAllInfo(ITonClient tonClient, string domainName) |
212 | 255 | { |
213 | | - // Count dots in name, but not count last one. |
214 | | - var depth = domainName.Count(x => x == '.') - (domainName.EndsWith('.') ? 1 : 0); |
215 | | - if (depth != 1) |
216 | | - { |
217 | | - throw new ArgumentOutOfRangeException(nameof(domainName), "Only second-level domains (e.g. alice.ton) are supported"); |
218 | | - } |
219 | | - |
220 | | - await tonClient.InitIfNeeded().ConfigureAwait(false); |
221 | | - |
222 | | - var resolved = await tonClient.DnsResolve(domainName, ttl: 1); // only resolve in root contract |
223 | | - |
224 | | - var address = ((EntryDataNextResolver)resolved.Entries[0].Value).Resolver.Value; |
| 256 | + var address = await GetNftAddress(tonClient, domainName).ConfigureAwait(false); |
225 | 257 |
|
226 | 258 | var di = new DomainInfo |
227 | 259 | { |
|
0 commit comments