For developers

Integration

What an integrator needs — resolution, message values, the getters worth reading, error codes, and the invariants you can safely build on.

Everything below is contract behaviour, not a service we operate. If our servers are down, resolution still works and names still transfer.

Resolving a name

The resolver implements TEP-81. Call dnsresolve(subdomain, category) with the emoji label and the category you want; category 0 returns everything.

Resolution is root-agnostic — the resolver answers on the emoji label itself, not on the full path, so the same records work regardless of which parent domain the query walked in through. Byte-for-byte, including the variation selectors, exactly as the name was registered.

🚀🌙.emojiverse.ton
└─ parent .ton domain sets sha256("dns_next_resolver") → Emojiverse resolver
   └─ resolver answers on the label 🚀🌙
      └─ wallet category → TON address
Normalise before you hash nameHash is sha256 over the canonical UTF-8 bytes. Run the user's input through the normalization module first — the canonical form is not always what they typed, because variation selectors are normalised into the name. Two copies of the normalization logic will drift; import the shipped module rather than reimplementing it.

A name that is expired past grace, or never registered, resolves to nothing. Do not treat "no record" as "not registered" — call isRegistered(nameHash) if you need to tell those apart.

Message value

The price is not the whole cost. A registration message must cover:

ComponentWhy
The license feepriceRegister(emojiCount, numYears)
The NFT mintThe registrar forwards value to the collection — most of it becomes prepaid rent inside the buyer's own item
Resolver syncsOne outgoing message per registered resolver
Network feesCompute and forward fees for the whole chain

The operational budget on top of the license fee is 0.538 + 0.003 × nameBytes GRAM (with the collection wired), where nameBytes is the UTF-8 length of the canonical name. Read it from the registerOpsBudget getter and add headroom rather than hardcoding a total. Overpayment is refunded; underpayment is refused with 406.

Quote the getter — after wiring registerOpsBudget returns a figure 0.5 GRAM lower while the NFT collection is still unwired during a deploy window. Only cache the getter's answer once the deployment is complete. And for operators: freeBalance() runs as a get method with no storage phase, while the withdrawal handler runs after one — an exact-freeBalance() withdrawal reverts with 449, so leave a small margin.

Getters worth reading

GetterReturns
isRegistered(nameHash)Whether the name is currently held
isAvailable(nameHash)Whether it can be registered right now
ownerOf(nameHash)Current owner address
expiresAt(nameHash)Expiry timestamp — check this before any secondary purchase
emojiCountOf(nameHash)Stored tier
emojiUnitsOf(rawName)The tier the contract computes for those exact bytes
priceRegister(count, years)First-year + renewals. Returns 0 for a single
priceRenew(count, years)Renewal cost
auctionOf(nameHash)(highBidder, highBid, startBid, endsAt, numYears)
freeBalance()Registrar balance minus live auction escrow
normalizationPolicy()(policyHash, locked)
traction()(totalRevenue, totalNames, totalRegistrations, totalRenewals)
pendingSigner()(stagedKey, appliesAt)(0, 0) when nothing is staged
adminTimelock()The timelock that will actually apply

emojiUnitsOf is the one that saves you a support ticket: it asks the contract what tier a name is, rather than re-deriving it and hoping the two agree. An attestation whose emojiCount differs from it is guaranteed to be refused, and you can catch that before a user pays for anything.

No index getters names, resolvers and auctions are maps with point lookups only. There is no nameAt(i). If you need to sweep the registry — "which names expire this week" — index the message stream off-chain. This is a known gap and is tracked in the audit register.

Signing service

If you run your own front end you will still use our signer, or run one with a key we authorise. What the signer must do:

  1. Normalise and refuse. Run the shipped normalization module. It rejects skin tones, non-RGI sequences, invisible characters, blocked look-alikes, over-length names and over-count names. Anything it refuses cannot be registered, so refusing early saves the user gas.
  2. Attest the canonical form, never raw user input.
  3. Use short validity windows. Minutes, not months. An attestation is a bearer capability for its whole window, and the contract does not currently cap that window for you.
  4. Bind to the registrar. The signature is valid for exactly one registrar contract. Pass its address into the attestation.
  5. Read the live policy hash per request rather than caching it. Changing the normalization policy revokes every outstanding attestation — that is the point of the lock.
  6. Match the tier to the bytes. Attest emojiCount equal to emojiUnitsOf(rawName). The contract segments the clusters itself and refuses any disagreement.

Error codes

CodeMeaningSuggested copy
402Bad signature"Signature expired — please retry."
405Name taken"Already registered."
406Underpaid"Not enough GRAM — try again."
408Expired"This name has lapsed and cannot be renewed."
413No commitment"Start over — the reservation was not found."
414Commit too new"Please wait a few more seconds."
415Commit expired"Reservation expired — start over."
425Attestation expired"Session timed out — retry."
427Single locked"Single emoji are released through drops only."
432Bid too low"Bid at least {minNextBid}."
434Unavailable"Not available — in a drop or still held."
440Skin tone"Skin-tone modifiers aren't part of the namespace."
442Already permanent"This name never expires."
443Permanent singles only"Only single-emoji names can be made permanent."
448Term too long"Maximum total registration is 10 years."
450Confusable"Not available — {survivor} is the registrable form."
451Too long"Too long — this name exceeds the DNS byte limit."
456Tier mismatchInternal — the attested tier disagrees with the bytes
457Malformed UTF-8Internal — the raw name is not well-formed
460Bad metadataInternal — the content cell is not a valid URI suffix
-14Out of gas"Not enough GRAM for network fees."

Invariants you can build on

These are tested properties, not intentions:

Record categories are an open map

Categories are a map, not an enum. A new integration — a social handle, a payment pointer, a game profile — needs no contract change and no upgrade. Pick a category key, write the record, read it back through dnsresolve.