Skip to content

How to interact with the subdomain manager contract

The TON monorepo provides dns-manual-code.fc, a subdomain manager contract that stores DNS records for arbitrary subdomains. This page documents every message and get-method the contract exposes.

After deploying the subdomain manager, bind it to a .ton domain by sending op::change_dns_record to the domain item contract:

  • key: SHA-256("dns_next_resolver")
  • value: cell containing dns_next_resolver#ba93 with the subdomain manager address
dns_next_resolver#ba93 resolver:MsgAddressInt = DNSRecord;

All queries for *.domain.ton are then delegated to the subdomain manager.

When resolving sub.domain.ton:

  1. Root DNS delegates to the .ton collection
  2. Collection resolves domain to the item contract
  3. Item contract returns dns_next_resolver pointing to the subdomain manager
  4. Subdomain manager resolves sub and returns the DNS record
FieldSizeDescription
contract_id32 bitsContract identifier
last_cleaned64 bitsTimestamp for replay protection cleanup
public_key256 bitsOwner Ed25519 public key for message authentication
rootdictPrefix tree: domain name to category dictionary
old_queriesdict64-bit query IDs for replay protection

All messages are external (signed by the owner private key).

FieldSizeDescription
signature512 bitsEd25519 signature over the rest of the message
contract_id32 bitsMust match stored contract_id
query_id64 bitsMust be >= now() << 32. Old queries are cleaned up after 64 seconds. The lower 32 bits form a unique message ID within a second.
op6 bitsOperation code (see table below)
payloadvariableOperation-specific data

The name field in VSet, VDel, DSet, and DDel payloads uses an Either encoding:

  • 1-bit flag: if 1, the name is stored in a cell reference; if 0, the name is inlined
  • Inline format: 6-bit length (in bytes) followed by length × 8 bits of name data
  • The name must be at least 2 bytes (16 bits), and the last byte must be 0x00 (null terminator)
OpcodeHexNamePayloadDescription
00x00Noop(none)No operation (used as separator in chained operations)
10x01SMsg8-bit mode + cell ref (message)Send a raw internal message from the contract
90x09CodeUpgradecell ref (new code)Replace the contract code
110x0BVSetuint256 category + domain name + maybe_ref valueSet a DNS record for a subdomain + category
120x0CVDeluint256 category + domain nameDelete a DNS record for a subdomain + category
210x15DSetdomain name + maybe_ref (category dict)Replace the entire category dictionary for a subdomain
220x16DDeldomain nameDelete all records for a subdomain
310x1FTSetmaybe_ref (new root dict)Replace the entire domain table
320x20TDel(none)Clear the entire domain table
510x33OSetuint256 (new public key)Change the owner public key

The contract’s process_ops function iterates in a loop, allowing multiple operations in a single external message. After reading an op and its payload from the current slice, if remaining data or references exist, parsing continues. When the current slice is exhausted, the next cell reference is loaded and processing continues from there.

VSet, VDel, DSet, DDel, TSet, TDel, SMsg, CodeUpgrade, and Noop operations can all be batched into one signed message.

To point sub.domain.ton to a wallet address:

  1. Encode the domain: sub becomes sub\0 in TON DNS internal format (null-terminated, reversed component order)
  2. Compute the category key: SHA-256("wallet")
  3. Build the value cell: dns_smc_address#9fd3 with the target wallet address
  4. Send an external message with op 0x0B, the category, the encoded domain name, and the value as a maybe_ref

Same as VSet but with op 0x0C and no value cell. Removes the record for the specified category + domain combination.

TON DNS uses a reversed, null-terminated encoding. Domain components are split by ., each component is null-terminated, and the order is reversed.

DomainEncoded bytes
subsub\0
a.bb\0a\0
x.y.zz\0y\0x\0

The dnsresolve get-method expects this encoding in the subdomain slice.

MethodSignatureReturns
get_contract_id() -> int32-bit contract identifier
get_public_key() -> int256-bit owner public key
dnsresolve(slice, int) -> (int, cell)(bits_consumed, record_cell)

dnsresolve parses the subdomain slice, looks up the domain in the root dictionary, and returns either:

  • The full category dictionary if category is 0
  • A specific record if category matches a stored key
  • A dns_next_resolver if the subdomain has remaining components to resolve