TON DNS resolvers
Introduction
TON DNS is a powerful tool. It allows not only to assign TON Sites/Storage bags to domains, but also to set up subdomain resolving.
Relevant links
- TON smart-contract address system
- TEP-0081 - TON DNS Standard
- Source code of .ton DNS collection
- Source code of .t.me DNS collection
- Domain contracts searcher
- Simple subdomain manager code
Domain contracts searcher
Subdomains have practical use. For example, blockchain explorers don't currently provide way to find domain contract by its name. Let's explore how to create contract that gives an opportunity to find such domains.
This contract is deployed at EQDkAbAZNb4uk-6pzTPDO2s0tXZweN-2R08T2Wy6Z3qzH_Zp and linked to resolve-contract.ton
. To test it, you may write <your-domain.ton>.resolve-contract.ton
in the address bar of your favourite TON explorer and get to the page of TON DNS domain contract. Subdomains and .t.me domains are supported as well.
You can attempt to see the resolver code by going to resolve-contract.ton.resolve-contract.ton
. Unfortunately, that will not show you the subresolver (that is a different smart-contract), you will see the page of domain contract itself.
dnsresolve() code
Some repeated parts are omitted.
(int, cell) dnsresolve(slice subdomain, int category) method_id {
int subdomain_bits = slice_bits(subdomain);
throw_unless(70, (subdomain_bits % 8) == 0);
int starts_with_zero_byte = subdomain.preload_int(8) == 0; ;; assuming that 'subdomain' is not empty
if (starts_with_zero_byte) {
subdomain~load_uint(8);
if (subdomain.slice_bits() == 0) { ;; current contract has no DNS records by itself
return (8, null());
}
}
;; we are loading some subdomain
;; supported subdomains are "ton\\0", "me\\0t\\0" and "address\\0"
slice subdomain_sfx = null();
builder domain_nft_address = null();
if (subdomain.starts_with("746F6E00"s)) {
;; we're resolving
;; "ton" \\0 <subdomain> \\0 [subdomain_sfx]
subdomain~skip_bits(32);
;; reading domain name
subdomain_sfx = subdomain;
while (subdomain_sfx~load_uint(8)) { }
subdomain~skip_last_bits(8 + slice_bits(subdomain_sfx));
domain_nft_address = get_ton_dns_nft_address_by_index(slice_hash(subdomain));
} elseif (subdomain.starts_with("6164647265737300"s)) {
subdomain~skip_bits(64);
domain_nft_address = subdomain~decode_base64_address_to(begin_cell());
subdomain_sfx = subdomain;
if (~ subdomain_sfx.slice_empty?()) {
throw_unless(71, subdomain_sfx~load_uint(8) == 0);
}
} else {
return (0, null());
}
if (slice_empty?(subdomain_sfx)) {
;; example of domain being resolved:
;; [initial, not accessible in this contract] "ton\\0resolve-contract\\0ton\\0ratelance\\0"
;; [what is accessible by this contract] "ton\\0ratelance\\0"
;; subdomain "ratelance"
;; subdomain_sfx ""
;; we want the resolve result to point at contract of 'ratelance.ton', not its owner
;; so we must answer that resolution is complete + "wallet"H is address of 'ratelance.ton' contract
;; dns_smc_address#9fd3 smc_addr:MsgAddressInt flags:(## 8) { flags <= 1 } cap_list:flags . 0?SmcCapList = DNSRecord;
;; _ (HashmapE 256 ^DNSRecord) = DNS_RecordSet;
cell wallet_record = begin_cell().store_uint(0x9fd3, 16).store_builder(domain_nft_address).store_uint(0, 8).end_cell();
if (category == 0) {
cell dns_dict = new_dict();
dns_dict~udict_set_ref(256, "wallet"H, wallet_record);
return (subdomain_bits, dns_dict);
} elseif (category == "wallet"H) {
return (subdomain_bits, wallet_record);
} else {
return (subdomain_bits, null());
}
} else {
;; subdomain "resolve-contract"
;; subdomain_sfx "ton\\0ratelance\\0"
;; we want to pass \\0 further, so that next resolver has opportunity to process only one byte
;; next resolver is contract of 'resolve-contract<.ton>'
;; dns_next_resolver#ba93 resolver:MsgAddressInt = DNSRecord;
cell resolver_record = begin_cell().store_uint(0xba93, 16).store_builder(domain_nft_address).end_cell();
return (subdomain_bits - slice_bits(subdomain_sfx) - 8, resolver_record);
}
}