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
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:
| Component | Why |
|---|---|
| The license fee | priceRegister(emojiCount, numYears) |
| The NFT mint | The registrar forwards value to the collection — most of it becomes prepaid rent inside the buyer's own item |
| Resolver syncs | One outgoing message per registered resolver |
| Network fees | Compute 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.
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
| Getter | Returns |
|---|---|
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.
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:
- 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.
- Attest the canonical form, never raw user input.
- 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.
- Bind to the registrar. The signature is valid for exactly one registrar contract. Pass its address into the attestation.
- 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.
- Match the tier to the bytes. Attest
emojiCountequal toemojiUnitsOf(rawName). The contract segments the clusters itself and refuses any disagreement.
Error codes
| Code | Meaning | Suggested copy |
|---|---|---|
| 402 | Bad signature | "Signature expired — please retry." |
| 405 | Name taken | "Already registered." |
| 406 | Underpaid | "Not enough GRAM — try again." |
| 408 | Expired | "This name has lapsed and cannot be renewed." |
| 413 | No commitment | "Start over — the reservation was not found." |
| 414 | Commit too new | "Please wait a few more seconds." |
| 415 | Commit expired | "Reservation expired — start over." |
| 425 | Attestation expired | "Session timed out — retry." |
| 427 | Single locked | "Single emoji are released through drops only." |
| 432 | Bid too low | "Bid at least {minNextBid}." |
| 434 | Unavailable | "Not available — in a drop or still held." |
| 440 | Skin tone | "Skin-tone modifiers aren't part of the namespace." |
| 442 | Already permanent | "This name never expires." |
| 443 | Permanent singles only | "Only single-emoji names can be made permanent." |
| 448 | Term too long | "Maximum total registration is 10 years." |
| 450 | Confusable | "Not available — {survivor} is the registrable form." |
| 451 | Too long | "Too long — this name exceeds the DNS byte limit." |
| 456 | Tier mismatch | Internal — the attested tier disagrees with the bytes |
| 457 | Malformed UTF-8 | Internal — the raw name is not well-formed |
| 460 | Bad metadata | Internal — the content cell is not a valid URI suffix |
| -14 | Out of gas | "Not enough GRAM for network fees." |
Invariants you can build on
These are tested properties, not intentions:
- Renewal prices never change. No setter exists.
- Auction escrow is never spendable. A withdrawal that would touch a live bid is refused.
- A sold name never resolves to the seller. Records are wiped on owner change.
- Singles never enter the public pool, including after a lapse.
- A permanent name is never re-issuable by any path, including admin ones.
- An attestation is valid for one registrar and one policy. It cannot be replayed onto another deployment or across a policy change.
- The tier equals the bytes. The contract segments emoji clusters itself.
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.