On-chain resolution
Call Universal Lookup to resolve any Soran name. It follows the Registry to the namespace’s current contracts and checks the result. You can use this ABI directly, without a Soran SDK or HTTP API. For payments, callresolve_destination(name). It returns the complete destination, including a memo or muxed routing ID when needed. The example below uses Stellar SDK 17 and the current testnet deployment.
For addresses on other blockchains, use the separate chain_address interface. Supported subnames such as pay.alice.nova use the same name-based payment, text and identity reads after capability checks. The network address and subname guides provide application examples.
Shared testnet contracts
Universal Lookup
Reading and writing fees
An RPC read uses simulation without submitting a transaction, so it does not charge an on-chain fee. Calling Lookup inside a submitted transaction still contributes to that transaction’s resource costs. Display-name setters and clears in Lookup and Primary pay transaction costs and any required record or instance storage rent. They do not renew shared Wasm code. Explicit maintenance calls handle code renewal, paid by their transaction’s fee payer. Maintenance extends code by at most 17,280 ledgers per call, within the configured target and network limit. See storage lifetime. Simulate each write before approval; fees vary with network and storage state.Read through Soroban RPC
This unsigned example checks the Registry anchor and ABI, then readsalice.nova. It does not submit a transaction or make a payment. Use an active name from the live examples, or one you have issued.
resolve_destination call checks its dependent records within the same simulation.
Forward result
resolve_destination(name:String) -> PaymentDestination returns a direct G/C payment or a muxed destination. Use resolve_v2(name:String) -> ResolutionV2 when you also need the name, routing contracts and ownership generation:
Direct contains a G account with an optional transaction memo, or a C contract with None. Muxed contains a G account and its exact unsigned 64-bit muxed ID. These two fields completely define the standard M address; an integrator can encode it without a Soran SDK or database. A muxed ID is not a transaction memo.
NativePayment is a complete instruction. LegacyAddress explicitly leaves memo capability unknown. Strict resolve_destination(name) -> PaymentDestination rejects legacy results.
The original resolve(name) -> Resolution and resolve_payment(name) -> Payment retain their V1 shapes for direct G/C instructions but reject muxed records with MuxedDestination. resolve_address(name) -> Address also rejects required memos. Never use a rejected old read as permission to substitute the base G address.
Canonical resolution names contain two labels (alice.nova) or three (pay.alice.nova). Each label uses 1–63 bytes of lowercase ASCII letters, digits or interior hyphens. Three-label names need subname-capable contracts; deeper nesting is rejected. Classic G destinations permit None, unsigned 64-bit ID, 1–28 UTF-8 byte text or 32-byte hash. Contract C destinations support None only. Muxed destinations require a G base and a u64 ID, with no separate memo in this ABI. See address types and complete examples.
Writing payment instructions
The namespace Resolver stores payment instructions.set_payment writes a G/C destination and its optional memo atomically; set_muxed writes the base G account and exact routing ID atomically. Both require holder authorization and bind records to the current ownership generation.
During a native claim or recipient acceptance, the bound Registrar calls initialize_destination with holder authorization. It refuses an already configured generation. See the native claim ABI for those write flows.
Compatibility
Lookup accepts native Resolver payment version1 through its direct-payment method. Version 2 also requires destination version 2. Unknown versions or failed reads return an error; they do not trigger a downgrade.
Decoding results
With Stellar SDK 17,scValToNative returns ordinary objects for structs and arrays for enum variants. For example:
PaymentMemo::None is ["None"], while a missing Option<String> is null. An ID is a JavaScript bigint, not a number. Symbols are strings after decoding, but must be Symbols in the original XDR.
The JavaScript decoding guide lists every destination variant, nested ResolutionV2 results, generated bindings, and downloadable XDR test vectors with expected outputs and rejection cases. It includes a standalone decoder using only Stellar SDK. A Soran SDK is optional.
Normalize name input
Normalize user-entered names before calling the contract. Trim surrounding whitespace, reject non-ASCII name characters, lowercase, then validate two or three labels. For example," Pay.Alice.Nova " becomes "pay.alice.nova". Calling the contract with uppercase letters directly returns MalformedName (4).
Each label must contain 1–63 ASCII letters, digits or interior hyphens; the full name is at most 191 bytes. Reject empty labels, deeper nesting, underscores, internal spaces and leading/trailing hyphens. Do not perform Unicode lookalike folding, IDNA conversion or URL parsing. Namespace arguments remain single labels. Show the resulting canonical name in payment confirmation. Never lowercase or trim wallet addresses, memo text or profile values. A normalization function is included with the decoder example.
Other read methods
Network address payloads use typed binary encodings: EVM addresses are 20 bytes, Bitcoin uses scriptPubKey, and Solana uses a 32-byte public key. The network SDK codecs encode and decode the supported catalogue. A disabled network returns
ChainDisabled; a successful None means no current address for the enabled network. Namespace policy lives in its Resolver and is controlled by the namespace owner.
Subname lifecycle reads and writes live in the namespace Registrar. Its subname_policy, subname_record and subname_labels methods support the SDK and HTTP interfaces. The parent holder controls child records. Receiving, profile, network address and identity reads validate the child’s current parent binding and namespace policy.
Interpreting metadata
Use metadata for ownership and profile views, not as a payment fallback.builtin_address can differ from effective Resolver instructions. Finite expiry occurs when ledger time is strictly greater than expires_at; zero means no ownership deadline. Metadata can remain available with active=false after expiry or while a child is suspended. Child metadata reports the parent holder and lifetime; it does not establish independent child ownership.
text accepts a nonempty Symbol of at most 32 bytes and returns at most 4,096 UTF-8 bytes; larger historical values return ReadTooLarge. The raw payment text key is not a replacement for typed payment reads.
G/C display names
The G/Creverse and primary_name results receive a current forward check, including required memos as valid account routes. A muxed payment does not prove base-G identity and is rejected by those paths. Their underlying contracts may collapse absent/stale/failed proof into None; a returned None does not diagnose every downstream failure. The dedicated M methods below preserve the complete route. See identity limits.
History screens and multiple addresses
Callprimary_names for cross-namespace elections, or reverse_names for elections in one namespace. Check batch_read_version() == 3, then read the method-specific limit: batch_read_limit() is 32 for one namespace, and primary_batch_limit() is 16 across namespaces. Both preserve order and duplicates:
Direct for G/C and Muxed for the exact G-plus-ID route. None means no verified election was returned, with the same underlying G/C proof limitations as the single read. Failed(code) preserves an ordinary Lookup error for that item. A malformed namespace or input longer than the selected method’s limit rejects the whole call; oversize is BatchTooLarge (27). Empty batches return an empty results vector.
The optimized native path shares namespace checks and reads records in groups. It still verifies current Registry routing, live Registrar generations and expiry, explicit G/C forward records, and both fields of an M destination. Repeated complete identities are verified once within the invocation and restored to their original positions. The input limit applies before deduplication.
Compiled-contract tests cover 32 populated records in one namespace and 16 populated Primary records across namespaces, including maximum-length names and G, G with memo, C and M destinations. These limits are input bounds, not a guarantee for every dependency or network condition. Older or custom namespace contracts use the existing full verification path and can require smaller batches.
After a top-level host Budget/ExceededLimit error, halve the batch and retry. A one-item failure, restoration requirement, transport failure or ABI error remains a failure; never convert it to None. A successful batch gives all its results one ledger and timestamp. Separate calls can observe different ledgers.
Lookup SDK 0.11.0 provides primaryNames(addresses) and reverseMany(namespace, addresses) for up to 256 inputs. They start at the deployed method limit, halve only after a leading host budget error, and retain each row’s observation ledger/time. See history examples.
Deduplicate repeated addresses when your screen permits it. Key a short-lived display cache by network, Lookup contract and complete identity. Do not retain failed reads as permanent absence. Re-resolve payment instructions before payment; these methods return display names, not payment destinations.
Muxed identity extension
Requiremuxed_identity_version() == 1 before using these methods. The ordinary Lookup and destination versions remain 2; they do not by themselves establish M identity support.
Universal Lookup stores opt-in display names for a base G account plus its exact u64 ID. Each read checks the current name, namespace routing and complete M payment destination. G/C elections remain in the Resolver and Primary contracts.
All setters, clears and touches return
Result<(), Error>. The argument named account must be a classic G account. A M address is decoded into that G account and id; it is not passed as Address. The base G account authorizes all selection and clearing arguments. If a custodian controls G, that custodian signs; a deposit customer cannot authorize it merely by holding a name or receiving an M address.
When an election is valid
A reverse election snapshots the name, Registrar, Resolver and ownership generation. A valid read requires an active name, unchanged snapshot, current Registry pointers andNativePayment(Muxed { account, id }) matching both input fields. Direct(G, Memo::Id(id)) is a different destination and never satisfies this proof. IDs 0 and 18446744073709551615 are valid and distinct. There is no fallback to G, a different ID, a memo record, a legacy payment or another namespace.
Primary additionally requires the same currently valid M reverse snapshot. A missing or proven stale election returns None. Invalid stored records, unsupported capabilities, unavailable dependencies and malformed payments fail. Clearing reverse stops the old Primary from verifying while its required election is absent. Touch methods maintain existing storage; they do not elect names or validate stale proof. See storage and lifetime.
Read an M display name
Continue the RPC example above with itsview helper and deployment configuration:
Option<String> strictly and verify any nonempty name against the full requested M destination. For writes, construct a transaction with set_reverse_muxed(account,id,name) or set_primary_muxed(account,id,name), obtain the base G account’s authorization, and submit it. A read simulation does not save an election. The Holder SDK supplies these write flows.
Numeric errors
Errors preserve their numeric ABI. Codes23–26 belong to the muxed identity extension.
A dependency failure never means a memo-free result. Unsupported methods must not trigger a silent direct-read fallback.
Error 7: missing or expired name
Payment methods returnNameInactive (7) when the Registrar has no live holder record. The error alone does not distinguish an unissued or expired name from a suspended child or an invalidated parent binding.
For a precise UI explanation, check name_status_version() == 1, then call name_status(name) through the same Lookup. Its result includes name, ledger, timestamp and one state, observed in a single invocation:
RegisteredName contains registrar: Address, node: BytesN<32>, holder: Address, generation: u64 and expires_at: u64. For expired records, holder and generation describe the stored historical record. Expiry is inclusive: a record is active at timestamp == expires_at and expired after it. Zero means no deadline, not a permanent-ownership guarantee.
This read verifies Registrar anchors and consistency with its live holder record. It does not depend on payment configuration, and does not promise that a name can be claimed: reservations and registration policy still apply. A successful status is an observation, not a lock against subsequent changes. Fetch fresh payment instructions when needed. Existing payment methods and numeric errors, including 7, remain compatible. See exact JavaScript shapes.
Raw state and discovery
Raw ledger entries support indexing and inspection, but they bypass contract validation. Separate reads may mix generations or routing; an entry missing from live state may be archived. Use typed contract methods for payment answers and events for discovery. Lookup stores configuration and exact-M election snapshots. Payment and memo records remain in each namespace’s Resolver. See the complete storage reference.Constructor and upgrades
The constructor takes(registry: Address, governance: Address, legacy: Option<LegacyCodeHashes>, primary: Option<Address>). LegacyCodeHashes contains exact Registrar and Resolver Wasm hashes. An optional Primary must be an active Wasm contract anchored to the same Registry.
The current deployment sets legacy = None; it does not route to a previous Registry. The current implementation fixes its anchors, but governance can replace its code without a mandatory delay. See governance.