Call the read functions
Invoke the contracts’ own view functions with
simulateTransaction. The
contracts apply resolver precedence, generation-gating, and expiry
on-chain — you get the exact answer the SDK and API give. Start here.Read ledger state directly
Pull the raw contract-data entries with
getLedgerEntries. Full control
for indexers, but you must replicate the resolution logic yourself — see
the caveats.Testnet only for now — mainnet is not deployed. At mainnet you swap all of
the Registry + PrimaryName IDs, the RPC URL, and the passphrase
(
Public Global Stellar Network ; September 2015) — none are interchangeable
with testnet. RPC https://soroban-testnet.stellar.org, passphrase
Test SDF Network ; September 2015. Examples use @stellar/stellar-sdk v17.Contracts
Only two contracts are platform-level, and they are the only addresses an integration should ever hard-code:
Every namespace (
nova, veil, …) has its own Registrar and Resolver
instances, deployed by the Registry. You never hard-code them — you discover
them from the Registry (registrar_of(nsNode) / resolver_of(nsNode)), so a
single Registry address serves every namespace that exists now or launches
later. A per-namespace address copied from a doc is a liability; the Registry
lookup is the API.
The namehash
Every name maps to a 32-byte node. A namespace hashes from 32 zero bytes; a name hashes from its namespace’s node.label is the raw UTF-8 bytes of a
single canonical label — lowercased, [a-z0-9-], 1–63 chars (the
contracts reject anything else, so Nova and nova are different inputs and
only nova resolves):
Resolve a name to an address
The resolved address isResolver.addr(node) when a current forward record
exists, otherwise the Registrar’s built-in resolve(label). Calling the
contracts applies that precedence — and the generation and expiry gates —
for you (nsNode / subNode and their import { hash } come from
The namehash above):
Resolver.text(node, key)
(a text record), Resolver.name_of(addr) / PrimaryName.primary_of(addr)
(reverse lookups — both re-verified on read), Registrar.holder_of_node(node)
(the holder, which may differ from the pay-to address).
Reading raw state
If you’re indexing and want the raw entries, read them withgetLedgerEntries. A Soran DataKey encodes as an ScVal inside a
LedgerKey.contractData. Every record key in the tables below is
PERSISTENT — the only instance-storage keys (each contract’s Config, the
Resolver’s Provenance) are config, not resolution, and are read from the
contract-instance entry, not by a DataKey lookup:
Storage layout
These record keys are allPERSISTENT contract data, keyed by the 32-byte
node (or an address). A Soroban struct decodes to an object keyed by its
field-name symbols; u64 fields come back as BigInt.
A few encoding notes for raw reads: the
Node record carries two extra
provenance-status fields beyond owner/resolver (only those two matter for
resolution). Registrar(nsNode)/Resolver(nsNode) are the Registry’s
attested deployments — for resolution use the resolver_of pointer, which
equals the attested resolver for reference-stack namespaces but can differ if
an owner repoints it. Text’s second key element is a Symbol
(scvSymbol), so its key is scvVec([scvSymbol("Text"), scvBytes(node), scvSymbol(key)]).
Discovering names
Both read paths resolve a name you already hold — but on-chain state is not enumerable, so you can’t list every name by scanning storage. To learn which nodes exist in the first place, index the contracts’ events: the Registrar emitsissued / transfer / reclaimed, the Registry emits alloc / claim,
and the Resolver emits its record updates. Stream them with the RPC’s
getEvents (filtered to the contract IDs) to build and maintain your node set,
then resolve each node with either method above. For a quick start you can also
seed from GET /v1/namespaces and the directory endpoints.
A resolved address may be a contract
The value you get back is a SorobanAddress — it can be an account
(G…) or a contract (C…); muxed (M…) addresses are never stored.
Branch on the type: a C… result is a contract — pay it through the asset’s
Stellar Asset Contract transfer, not a classic Payment operation; only a
G… address accepts a classic payment.
The authoritative storage format is the contract source — the
DataKey enums
and structs in contracts/{registry,registrar,resolver,primary}/src/lib.rs.
The same data is served, already resolved, by the HTTP API
and the Lookup SDK; the
trust model explains why the Registry’s answers
can’t be edited out from under you.