resolve
resolve(name) returns the address the name currently pays to, or null. The null cases are decided by the contract, not by SDK guesswork: records are bound to the name’s ownership generation on chain, so an expired, reissued, or transferred name stops resolving at the source.
Under the hood, resolution mirrors the contracts byte for byte:
- Split
label.namespace— both parts must be canonical ([a-z0-9-], 1–63 chars, no leading/trailing hyphen). Input is lowercased first. - Compute the namespace node:
sha256(ZERO32 ‖ sha256(namespace)). - Compute the name node:
sha256(nsNode ‖ sha256(label)). - Ask the Registry for the namespace’s resolver (
resolver_of). - Ask that resolver for the address (
addr) — generation-checked on chain. - If the resolver holds no explicit record for the name, fall back to the namespace’s Registry-attested Registrar and ask its built-in
resolve— issuance initializes the built-in target to the holder, so freshly issued names resolve immediately, before their holder ever writes a resolver record. The Registrar consulted here is the one the immutable Registry itself deployed and attests; the resolver in step 5 follows the owner-set pointer, andassurance()tells you whether that pointer is the attested one.
registrars: { theirns: "C…" } in the options and the SDK asks that Registrar’s resolve directly.
record
When you want more than the address,record(name) returns the full resolution record:
node is the hex-encoded on-chain node hash; resolver is the resolver contract that answered — or, when address is null, the one consulted (null when the answer came from the Registrar’s built-in resolution or closed resolution).
Text records
Names can carry text records (url, avatar, and so on):
null when the record is unset or the namespace has no public resolver.
verify — the confirm-time re-check
Names can move between keystrokes and confirmation: they expire, transfer, get reissued. A wallet that resolves once at typing time and pays later is trusting a stale answer. Re-check at confirm time:verify(name, address) is exactly resolve(name) === address — the address itself is always a fresh chain read, never cached. Note that the namespace→resolver pointer may still be served from the resolverCacheTtlMs cache (default 30 seconds); set it to 0 on an instance used for confirm-time checks if you must also notice a resolver repoint instantly.
For high-value payments, consider also checking
assurance(): verify confirms what the name resolves to right now, while assurance tells you whether that mapping can be changed underneath you at all.Error semantics: null vs SoranError
The SDK draws a hard line between “the chain says no” and “I couldn’t ask the chain”:
This matters most in pay-to-name flows: a transient RPC failure must never be silently mistaken for “unregistered”. Catch
SoranError and retry or surface it — don’t fall through to a default.
SoranError, thrown before any network call: parseName (exported, if you want the same validation client-side) requires exactly label.namespace with canonical labels.
Caching
The namespace→resolver pointer is cached forresolverCacheTtlMs (default 30 seconds, 0 to disable). Addresses themselves are never cached — every resolve/verify reads live state. The pointer cache can only delay noticing a namespace repointing its resolver; it cannot fabricate a wrong address for a name.
Next
Reverse lookup & primary names
Address → name, contract-verified.
Assurance
Is this resolution immutable?