Skip to main content
This reference describes the native-muxed deployment verified on 5 September 2026 at ledger 4,521,644. Match the contract’s current Wasm hash to the public deployment manifest before decoding storage with this schema. An upgrade can change an upgradeable contract’s implementation while keeping its address. Use Universal Lookup for authoritative name and payment answers. Raw ledger entries are useful for explorers, storage tools and independent inspection. They are not a substitute for the contracts’ ownership, generation, routing and payment checks.

Deployment scope

Registrar and Resolver are namespace-specific. The other four contracts are shared. For another namespace, discover its current Registrar and Resolver through Lookup/Registry; do not use nova’s pair. See release status for the verification timestamp, deployment transactions and historical deployments.

Ledger and XDR conventions

The tables use Rust-style types to describe the stored schema. These names are not extra wrappers in XDR. All application storage in these six contracts is instance or persistent storage. None uses temporary storage. Persistent application entries use a LedgerKey::ContractData containing the contract address, encoded DataKey, and persistent durability. Instance keys live inside the storage map of the contract’s single SCContractInstance; they are not separate persistent entries named Config. The instance ledger key uses SCV_LEDGER_KEY_CONTRACT_INSTANCE. Contract Wasm is a separate contract-code ledger entry. Stellar storage reference Names use canonical lowercase ASCII labels: 1–63 bytes from a-z, 0-9, and an internal -. Let H mean SHA-256 and zero32 mean 32 zero bytes:
label_hash and ns_node are different values. A printable hex hash is not the stored BytesN<32> value: decode its hex into 32 bytes first. Addresses, labels, memo values and profile data are public chain data; removal or retraction does not erase transaction history.

Registry

Clean and Tainted are single-symbol vectors. There are no current RegistrarTainted or ResolverTainted storage keys: both statuses are fields of Record. Ordinary owner/pointer updates preserve them. Tainted marks provenance ineligible for permanence, including an upgrade or post-hoc Registrar attestation; restoring an earlier Wasm hash does not clear it. A fresh Clean field does not prove a contract has been attested. Resolver attestation and the current resolver pointer must be distinguished. The reservation Merkle root is stored, but the complete label list and allocation witnesses are not. Namespace deployment salts and vanity-search candidates are not stored application records. Salt version 1 is a code-defined namespace-and-role binding rule, not a DataKey or storage-schema version. Namespace transfer proposals expire after 604800 seconds. A keeper can call touch_node(ns_node) to extend the namespace record and existing Registrar, Resolver and ResolverLock entries. It does not extend Pending or the Registry instance; keep_alive() covers the instance and code. A raw missing owner entry does not authorize reallocation: the contract also checks related state.

Allocator

Use label_hash = H(label_bytes) in this section. Objections and upfront claim fees use separate accounting, even when their configured token happens to be the same asset.
ClaimState and FeeOutcome use their named variants, not 0, 1, or other numeric state IDs. Objection fields are meaningful while state = Objected; do not interpret leftover numeric obj_* fields as an active objection after settlement. announced_at can be adjusted when a frozen window resumes, so it is not always the original submission timestamp. rejected_at = 0 means no recorded rejection timestamp in that field. There are no Expired or Stuck ClaimState variants: those exits store Withdrawn in the claim while recording Expired or Stuck in the fee receipt. Missing Config, SchemaVersion or required FeePolicy is an error, unlike an intentionally absent optional claim. time_remaining = 0 alone does not prove that a claim can execute. Fee amounts and credits are signed 128-bit integers in the configured token’s smallest units, with nonnegative accounting invariants. Decimals are token metadata, not part of these records. The current fee is 50000000000 stroops, or 5,000 testnet XLM. Receipt outcomes have these refund entitlements: The equivalent refund basis points are 0, 10000, and 8000; those numbers describe policy arithmetic, not the stored enum encoding. refund_amount and treasury_amount describe entitlements, including any undelivered pull-credit. A settled receipt does not by itself prove that every transfer was delivered. A later claim can overwrite the label’s old Claim/FeeReceipt; use confirmed events for history. ObjCount is capped at 3 objection cycles per claim epoch. The configured live-time budget and per-claimant ParkedAge survive different transitions from the per-label cycle counter. Rejection locks and claimant-specific reopen proposals are separate from the label’s latest claim. Reopen tuples bind a proposal to its precise rejection timestamp; a historical reopen does not necessarily free the label. Config.window is bounded to 864003153600000 seconds. max_claim_age is at most 3153600000 and must exceed the window by at least 3600 seconds. Rejection cooldown, objection timeout and the minimum banked concession budget each use 2592000 seconds. Those are timing/budget rules, not TTLs or automatic deletion deadlines. Claim basis accounting requires sum(entry.len() + 8) <= 4096, including the per-element overhead; an objection basis is at most 4096 raw bytes. touch_claim(label) extends the existing Claim, legacy ReopenEta, FeeReceipt and ObjCount plus the instance/code. It does not enumerate claimant-keyed history or all payee credits. touch_reopen_for(label, claimant) maintains that claimant’s proposal and matching rejection lock plus the instance/code; touch_fee_credit(address) maintains that fee credit plus the instance/code. There is no general enumeration of every persistent key. Known keys can also be maintained through Stellar’s ledger-entry TTL operations. Schema 4 requires fee receipts for claims. Schema 3 had no claim-fee receipt requirement; schema 2 used a single u64 ReopenEta value rather than the two-element tuple; legacy schema 1 had no SchemaVersion entry and different Claim/Config layouts. The current deployment was created fresh. There is no general raw-storage migration method: an upgrade must preserve the existing schema or implement a separately reviewed migration. Changing a version getter or stored version number does not convert old values. Allocator upgrade proposals retain their own configured claim-window delay; Lookup’s zero delay does not change this rule.

Registrar

Config.owner is the constructor-time owner; current namespace authority follows Registry.owner_of(namespace_node). treasury_set_by identifies who configured the treasury. After namespace ownership changes, a treasury set by a previous owner is not the current owner’s authorized reclaim destination. Name.address is a built-in G/C destination, not a complete native payment instruction. The namespace Resolver may supply an explicit destination and memo. expires_at = 0 means no ownership expiry; it does not make a reclaimable namespace permanent. With a finite expiry, the name expires when ledger time is greater than expires_at. Storage TTL is independent. Generation starts at 0 for first issuance and increases on reissue, accepted transfer and reclaim. A holder address update or renewal does not change it. Resolver entries from another generation stop being current even if still present in the ledger. A transfer proposal carries both from and generation; its acceptance deadline is 604800 seconds after proposal. Acceptance changes holder and built-in address to the recipient and increments generation. Policy terms are either 0 or 864003153600000 seconds. trade_fee_bps is 010000; it is a basis-point field, not proof that a marketplace feature is enabled. Making a namespace permanent is one-way, requires no-expiry terms, disables reclaim and freezes Registrar upgrades. Name writes extend the name entry and instance/code. Transfer proposal writes extend the proposal entry. touch(label) extends the Name entry only; keep_alive() maintains instance/code. Neither is a blanket renewal of every PendingTransfer. Renewal changes the ownership clock; a TTL extension does not.

Resolver

Config.authority is the Registrar. admin remains in the layout but is not the current upgrade-authority source; current authority follows the Registrar’s live owner lookup. Provenance is instance data and cannot expire independently of Config.

Payment storage

There is no DataKey::Payment entry. set_payment and set_muxed atomically write these three entries for the same name and generation:
  1. Addr(name_node)AddrRec with the destination G/C address; for muxed payments this is the base G account.
  2. Text(name_node, Symbol("payment"))TextRec with one complete versioned string below.
  3. PaymentConfigured(name_node, generation)true.
The backslashes above only escape Markdown table separators; they are not stored. For example, an actual ID record is 1|GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ|id|420. Only the first three | characters delimit fields; text memo content may itself contain |. Direct records use a 56-character G/C StrKey. IDs use canonical decimal digits from 0 through 18446744073709551615, without leading zeros. Text memos contain 1–28 UTF-8 bytes. Hash memos contain exactly 64 lowercase hex characters. The decoder bounds the entire record to 128 bytes. A muxed record stores the base account and the exact u64 routing ID on chain; it does not store an M string as a Soroban Address. The SDK reconstructs the full checksummed M StrKey and returns {address: "M…", memo: {type: "none"}}. This operation-level ID is not a transaction memo. Reading only Addr would lose it. The published payment encoder/decoder implements the public SDK representation.
For example, resolve_destination returns [Symbol("Muxed"), {account: Address(G), id: U64(420)}]. resolve_v2 wraps that result in its resolution metadata. Neither return value is the raw TextRec stored on the Resolver. An untouched generation can resolve its current effective address with no memo. Once configured, missing, empty, malformed or mismatched payment metadata must not be treated as a memo-free destination. Presence of the marker is significant; the implementation never removes these generation-specific markers. Older generations do not configure a new holder’s generation. Generic set_text cannot edit the reserved payment key; use an atomic payment write.

Profiles, reverse names and maintenance

Profile keys are symbols of 1–32 letters, digits or underscores. Values are public strings. Retraction writes an empty string; it does not delete the ledger entry or erase history. Standard profile readers treat empty values as unset. AddrRec and TextRec are current only when their generation matches the Registrar’s current active name. Reverse records contain plaintext label.namespace, its node and generation. A raw reverse entry is only a declaration: verified reads also check the current generation and forward match. Legacy reverse records without the name string, or with a differently typed name, are not current RevRec values; holders must set a valid declaration again. The older two-field Resolver Config is likewise not the current three-field layout. This deployment was created fresh; do not assume an older Resolver can adopt it through a Wasm swap alone. put extends the modified persistent record and instance/code. keep_alive() maintains instance/code. touch_node(node), touch_text(node, key) and touch_reverse(address) extend their existing entry plus instance/code. touch_payment(name) covers the current-generation marker, address, payment text and instance/code together; maintaining the address alone does not maintain its payment metadata. None of these changes ownership or payment content.

Primary

All PrimaryRec fields are an election-time snapshot. primary_of checks the Registry’s current resolver pointer and that Resolver’s verified display name. The cached node is informational, not an independent proof. A stored declaration can remain present while the verified answer is absent after a transfer, destination change, reverse clear or resolver change. An unavailable dependent display-name read can also yield no verified primary; that does not prove no raw declaration exists. Universal Lookup’s primary_name independently rechecks the returned name against current forward resolution. Its reverse path also verifies the complete native destination and refuses a muxed destination as an account-identity proof. Do not replace these checks with a raw PrimaryRec or an isolated Resolver address record that exposes only a muxed destination’s base G account. set_primary extends the record and instance/code. clear_primary removes the current entry, not its historical transactions. touch_primary(address) extends the entry if present and maintains instance/code. Primary has no Wasm-upgrade entry point or general storage migration method.

Lookup

Lookup stores no per-name answers, payment records, reverse records or cached namespace data. It queries the Registry and the current namespace contracts. Resolution structs, namespace/name metadata and the internal Provenance mirror are ABI data, not additional Lookup storage entries. An absent optional anchor is not a failed read; decoding or RPC failures must be handled separately. version() and destination_version() are code-defined getters; neither has a stored DataKey. ABI 2 selects complete Direct/Muxed destinations. A successful native Resolver payment-version 1 response uses its direct-payment interface; version 2 requires destination-version 2. Failed or unknown capability responses do not justify a legacy fallback. The old Address-returning methods cannot represent a muxed ID and refuse that result. All Lookup configuration and pending-upgrade data share the instance lifetime. keep_alive() extends instance/code. The current upgrade delay is 0: governance still proposes an exact Wasm hash, and execution checks that proposal. Code replacement does not automatically convert the stored Config or other instance values.

TTL and restoration

Ownership expiry uses Unix seconds in stored values. Storage TTL uses ledger numbers in Stellar’s TTL entries and RPC liveUntilLedgerSeq. An ownership term of zero is not an infinite storage TTL. Ordinary reads do not provide a persistent keeper service, and read simulations do not commit storage changes. Registry, Allocator, Registrar, Resolver and Primary use these dynamic extension parameters:
Lookup uses threshold = min(2_500_000, max_ttl) and extend_to = min(3_000_000, max_ttl). These are ledger targets, not a promised number of days. A bump only extends an entry below its threshold; it can be a no-op for an already-long TTL. Instance extension checks the instance and Wasm-code TTLs separately. Instance TTL semantics Persistent entries and contract instances can archive and later be restored. Archival does not establish that a namespace is unowned, a name has expired, a rejection/credit never existed, or a payment needs no memo. A raw getLedgerEntries result that omits a key is not a sufficient payment or ownership decision. A transaction accessing archived persistent data must include the required restoration work or fail; RPC simulation can identify what is needed. Restoration costs and subsequent TTL extension are distinct from name-renewal or claim fees. Stellar state archival Use the relevant contract read after restoration to recheck current meaning. Restoring an old-generation payment or a stale primary declaration does not make it current. Do not translate an RPC failure, archived entry or malformed ScVal into None, false, zero credit, or a memo-free G address.

Read raw keys with Stellar RPC

This read-only Node example uses @stellar/stellar-sdk@17.0.1 and @sorandomains/lookup@0.6.0. It reads a Registry namespace record and the Resolver’s raw address, payment text and generation marker. It does not sign, submit, restore, or construct a payment. Replace the example name with an actually issued name.
The helper key("Text", …, symbol("payment")) uses a symbol for the reserved key. Replacing it with a string, using the namespace hash instead of the name node, or querying the marker with a previous generation addresses a different ledger key. scValToNative is a decoder, not a schema validator or payment verifier. Keep the TextRec.value and matching AddrRec/generation together when inspecting them, and use resolvePayment for the validated payment answer. To inspect instance data instead, fetch its special footprint and decode each internal key/value pair:
The second snippet continues the first snippet’s server and deployment variables. It reads Config inside instance storage; querying persistent(registry, key("Config")) would look for a different, unused application entry. RPC returns current ledger data and TTL metadata, not a complete historical archive. getLedgerEntries reference