The name policy.
Status: draft for review, 9 Oct 2026. Nothing here is deployed and no name has been minted. It follows the implementation plan of 9 Oct 2026 and its independent review (findings B3, I8, I13 and I14 are the ones answered here). The parts that become permanent at the first Mainnet mint are marked so; they are confirmed by the founder at the 19 October freeze.
This is the public rule book: what a name is, which names can be had, how a name is written into the ledger, and how anyone can tell which NFT is a name. The service that applies it is docs/registrar-api.md; the service that answers lookups is docs/resolver.md; the claim itself is docs/genesis-claim-spec.md.
The rules are also code. registrar/names/ (crate openwallet-names) implements §2 to §5, §8 and §9 with no network access, and its tests are the vectors for any second implementation. Where this text and that crate disagree, it is a bug in one of them: fix both in one change.
Items marked DEFAULT — founder to confirm are choices the plan left open. Each is listed in docs/genesis-claim-spec.md §12.
1. Words
- A name is a label, a dot, and the suffix:
alice.on. - The suffix is a build constant,
ontoday (openwallet_names::SUFFIX). A fallback may replace it before the first mint. No rule, no code and no test may spell the suffix out; they read the constant. This document writes.onin examples only. - The issuer is the XRP Ledger account that names are issued from, one per network, published in
GET /v1/status(docs/registrar-api.md§3.1) and pinned in the wallet (docs/genesis-core-api.md). A minter is an account the issuer has authorised to mint for it. - A name is held as an XLS-20 NFT. Taxon 2 is the launch form (not transferable). Taxon 3 is the later, tradable form. Taxon 1 is the Genesis pass, which is not a name.
2. Which strings are names
2.1 The rules
A label:
- uses only
a–z,0–9and the hyphen-(U+002D); - does not start or end with a hyphen, and has no two hyphens in a row;
- is 1 to 32 characters long;
- does not have the shape of an address (§5).
A name on offer is, in addition, one that can be asked for: its label does not contain transactiontype, txjson, signingpubkey, txnsignature or lastledgersequence, and is not 32 characters of 0–9 and a–f. A wallet proves its address by signing the sentence "Hold <name> for this account.", and the wallet's key core refuses to sign any text that reads like a transaction or a hash, which those do. Such a name could be reserved and never claimed, so it is refused at the start (unsignable, step N9). It is still a well-formed name: one found on the ledger resolves like any other.
Of those, 5 to 32 characters are on offer at launch. 1 to 4 are held back for a later release (an auction, with a private window for Genesis members). A held-back name is a well-formed name: if one is ever issued, it resolves under the rules below with no change to them.
There are no subnames (pay.alice.on), no upper case, no Unicode, no emoji. Restricting names to this alphabet removes the whole class of cross-script and invisible-character look-alikes that other name services have had to chase.
2.2 The normalisation algorithm
Input: a string S of Unicode code points, exactly as given. Output: a name or one error. The steps are applied in this order, and the first error found is the answer. Positions are 0-based and count code points of S (not bytes, not UTF-16 units).
| Step | Rule | Error |
|---|---|---|
| N1 | If S is empty, stop. | empty |
| N2 | Replace each of A–Z (U+0041–U+005A) with a–z. Change nothing else: no Unicode case folding, no NFC/NFKC, no trimming of spaces. | |
| N3 | If S contains a dot (U+002E): let T be everything after the last dot. If T is not exactly the suffix, stop. The label L is everything before that dot. If S has no dot: where a full name is required (§2.3), stop; otherwise L = S. | wrong_suffix (with T); missing_suffix |
| N4 | If L is empty, stop. | empty |
| N5 | Find the first character of L that is not a–z, 0–9 or -. If there is one, stop and report its position and the character as it was given. | bad_character |
| N6 | If L starts with -, stop. If it ends with -, stop. If it contains --, stop and report the position of the first hyphen of the first pair. | leading_hyphen, trailing_hyphen, double_hyphen |
| N7 | If L has the shape of an address (§5), stop. | looks_like_address (with the kind) |
| N8 | Let n be the length of L. If n > 32, stop. If n is below the shortest length the rules in force allow, stop. If n is a held-back length, stop. | too_long, too_short, held_back_length |
| N9 | Only where a name is being registered (not where one is resolved): if L contains transactiontype, txjson, signingpubkey, txnsignature or lastledgersequence, or 32 characters in a row from 0–9 a–f (with N8: L is 32 such characters), stop. | unsignable |
The name is L, a dot, the suffix.
Notes that an implementer needs:
- N2 is ASCII only. JavaScript's
toLowerCase()turns U+212A (KELVIN SIGN) intokand U+0130 intoiplus a combining dot. An implementation that used it would accept names this policy refuses. Map the 26 letters and nothing else. - Nothing is trimmed. A leading or trailing space is a
bad_character(N5) or part of awrong_suffix(N3). A user interface may trim before it calls the algorithm; the algorithm does not. - N5 before N6 before N7 before N8.
-al!ceis abad_character, not aleading_hyphen. A pasted address islooks_like_address, nottoo_long. - At launch the lengths are: shortest 1, held back 1 to 4, longest 32. So
too_shortcannot occur at launch; it exists for a later rule set (for example "3 and 4 on offer, 1 and 2 not names"). - The algorithm is idempotent: normalising a name, with or without its suffix, in any letter case, gives the same name.
- N9 follows the wallet's key core (
core/src/login.rs,looks_like_transaction): the five words are those of its list that the label alphabet can spell, and 32 is its hex run. If the core's list changes, N9 changes with it; the registrar's tests run the core's own check over the sentences of every generated name (registrar/service/tests/unsignable_names.rs), so a drift fails the build. Thirty-two of one letter (aaaa…a) is refused by this step when the letter isatof.
2.3 Where the suffix is required
| Context | Suffix |
|---|---|
| Registering or searching for a name (the label is what is being chosen) | optional: Alice, alice.on and ALICE.ON are one name |
| Anywhere a string could be something other than a name: the wallet's "To" field, a resolver query, a URI | required: alice alone is not a name |
In the crate: normalise (launch lengths, suffix optional), Name::parse (any well-formed name, suffix optional), Name::parse_full (suffix required).
3. The reserved list
3.1 What it is
A list of labels that are either blocked (issued to nobody) or held (held for a named person or organisation and issued only to them). It lives in one reviewed file in this repository:
registrar/names/data/reserved.txt
It is embedded in the openwallet-names crate at build time, so every build of the registrar and resolver carries exactly one version of it, and the version is the commit.
3.2 Structure
One entry per line: <category> <label> <disposition> [stem|word] # note, and allow <label> # note for the allow list of §3.3.
| Category | What goes in it |
|---|---|
system | Words that read as the service or a role in it: admin, support, help, root, official, security, team, staff, wallet, refund, airdrop, … |
brand | Brands: OpenApps' own (openwallet, openapps, product names already public), and the exchanges, wallets and stores a newcomer meets |
ecosystem | Organisations, products and assets of the XRP Ledger ecosystem (ripple, xrpl, xaman, bithomp, …) and the best-known other chains |
person | Notable people. Always held, always "held for the named person" |
sensitive | Words that impersonate authority (police, bank, mas) and a short set of plainly abusive words |
A held entry must say who it is held for. Being listed as held is not an endorsement by, or a relationship with, that person or organisation, and nobody is told a name is held for them until they have agreed to it (the plan's go-to-market rule: a name is held only with consent).
3.3 How a name meets the list
A new name is refused when any of these is true (in this order of report). Every comparison is of skeletons (§4.1), in both look-alike modes of §4.2, so each rule holds through 0 for o, rn for m, a doubled letter and an added hyphen.
- Exact. Its label is on the list.
- Look-alike. Its skeleton equals the skeleton of a listed label: listing
supportrefusessupp0rtandsup-port. - A protected word inside it. Either one hyphen-separated part of the name, or several parts in a row, looks like a listed label (every entry:
ledger-wallet,real-binance,the-official,x-r-p-team); or the name's skeleton contains the skeleton of an entry markedstem(xbinancex,coinbasehelp,admins). A stem's skeleton must be at least 6 characters, because a short stem refuses too much. When a name contains several protected words the longest is the one reported. - A near miss. The name, or a part of it, is one slip away from a listed label of 5 letters or more that is not marked
word: a letter dropped (coinbas), doubled (opennwallet), two neighbours swapped (conibase), or one further look-alike pair (bimance,ledqer; the pairs are a fixed list inregistrar/names/src/reserved.rs).
An entry marked word is an ordinary word as well as a protected one (circle, claim, press): it is matched whole and as a part (rules 1 to 3, without the stem), never inside a longer word and never one slip away, because too many ordinary words are (encircle, acclaim).
The allow list. A stem refuses badminton (it contains admin) and a near miss refuses sport. An allow line lets such a word through: a hyphen-separated part that is exactly an allowed word is set aside before rules 3 and 4 look at the rest. Rules 1 and 2 do not consult it, so an allowed word can never free a listed name, and badminton-admin is still refused by its other part.
DEFAULT — founder to confirm: which entries are stem and which are word, and the allow list. Both are a first proposal made from two public word lists, not a decision; what each rule costs in ordinary words refused, and what still gets past, is in Appendix A.
A held name is released to its holder by an audited operator action (docs/registrar-api.md §11); nothing else lets a reserved name through.
3.4 Governance
- The file in the repository today is a starter. It is short on purpose, it is marked as not reviewed, and it must be read and corrected by the founder and by counsel before the first Mainnet mint. It is not a trademark search.
- A change is a pull request to
reserved.txt, with a reason, reviewed by a second person. Adding apersonentry needs the founder. A private individual is added only at that person's own request. - Adding an entry does not undo a registration. A name registered before the entry was added stays, unless it is revoked under §7.
- Removing an entry (releasing a blocked name to the public) needs the founder and is announced before it takes effect, so nobody gains from knowing first.
- The parser is strict: an entry that looks like another entry, a label that is not in normal form, or a
heldentry with no holder fails the build. - The list is public, because the repository is. Rights holders can ask for an entry, or for a
heldname, at the address in §7.3.
4. Look-alikes
4.1 The skeleton
The skeleton of a label is computed in four steps. Each step has exactly one answer for every label, so every implementation gives the same skeleton.
Step 1. Hyphens are removed and every character is rewritten on its own.
| Character | Becomes | Why |
|---|---|---|
- | (removed) | open-wallet and openwallet |
0 | o | zero and the letter o |
1, i | l | one, i and lower-case L |
2 | z | |
3 | e | |
4 | a | |
5 | s | 5upport |
6, 8 | b | |
7 | t | |
9 | g | |
m | nn | nn and rn read as m |
w | vv | vv and uu read as w |
u | v | u and v, and so uu is vv |
d | cl | cl (and ci, c1) read as d |
| any other | itself |
Step 2. Every r that stands directly before an n becomes n, working from the end of the label to its start. So rn becomes nn, which is what m became, and rrn becomes nnn, the same as rm.
Step 3. A letter repeated in a row is written once, except n and v, whose doubles are the letters m and w.
Step 4. cl repeated in a row is written once, so dd (which step 1 made clcl) is the same as d.
Examples:
| Labels | Skeleton |
|---|---|
alice, a1ice, allce, al-ice, alicee, a11ice, alce | alce |
support, 5upport, suport, supportt, sup-port, supp0rt | svport |
admin, adrnin, adnnin, acimin, addmin, 4dm1n | aclnnln |
modern, rnodern, nnodern | nnoclenn |
openwallet, openwalet, openuuallet, 0penwa11et | openvvalet |
vw, wv, vvv, uvu | vvv |
(m, w and d are expanded, not rn, vv and cl contracted, because expansion has one answer for every input. A left-to-right contraction would give vw and wv different skeletons although both read as three v's.)
The skeleton of a skeleton is itself. Any number of the substitutions and doublings above, applied in any order anywhere in a label, leave its skeleton unchanged. That property is tested (registrar/names/tests/ properties.rs), and was checked exhaustively for short labels when the rule was written.
The rule is on the cautious side, and that has a cost. Because i reads as l and a doubled l is one l, the labels ali, all, ail and al share a skeleton; so do bill and bil, boot and bot, handle and hande. Only one name of each such family can exist at a time (§4.2). The founder should read this list of consequences before the freeze: DEFAULT — founder to confirm.
4.2 The rule
A new name is refused if its skeleton equals the skeleton of a reserved name (§3.3) or of a name that is registered, held, reserved by someone else, or in quarantine (§6.4).
That is the strict mode, and it is what is built into the default build (policy_version 2: the rule as it stands after 10 Oct 2026, including the wider reserved rules of §3.3). A second mode exists in the same code behind one build-time switch, because the choice between them is the founder's and has not been made: in the narrow mode (policy_version 3) the second half of the sentence reads "or it is a name that is registered, held, reserved by someone else, or in quarantine". The reserved list is applied identically in both. What each mode costs and allows, measured, is in Appendix A. DEFAULT — founder to confirm: strict.
The relation is symmetric and transitive: it is equality of skeletons. So a family of look-alikes has room for exactly one name, and whoever registers first has it.
4.3 What this catches, and what it does not
It catches, in any combination, anywhere in the label, between any two names of this service:
- the digit-for-letter spellings of the table (
0 1 2 3 4 5 6 7 8 9); i/l,u/v,rn/nn/m,vv/uu/w,cl/ci/d;- a doubled letter (
supportt), and a missing letter where the name had it doubled (suport), for every letter except those in the next list; - added or removed hyphens.
It does not catch:
- a doubled or missing
n,m,v,uorw(sumerforsummer,anaforanna,savyforsavvy): the doubles ofnandvare the lettersmandw, so they cannot be folded; - an
rdoubled directly before annorm; - other look-alike pairs:
q/g,c/e,h/b,vv/yand the like; - other typing mistakes: a letter dropped where it was not doubled (
opnapps), a letter added, two letters swapped (aliec), a neighbouring key; - meaning: plurals, translations,
the-real-alice,alice-official(unless a stem is hit); - names in other services or with other suffixes (
alice.xrp), and anything typed outside a wallet that follows these rules; - names minted by someone who has stolen a minter key. The rule is applied by the registrar when it registers a name. The ledger does not apply it.
Anyone can audit it: compute the skeleton of every live name from the ledger (docs/resolver.md §6.2) and check that no two are equal. Nobody can enforce it but the registrar. A wallet should therefore still show the resolved address and warn on a first payment to a name; the rule is one layer, not the whole defence.
The rule is permanent in one direction. A pair added later cannot un-register two names that already collide under it. The four steps above are frozen with the first mint; additions afterwards apply to new registrations only, and the registrar's stored skeletons would have to be recomputed (docs/genesis-registrar-notes.md §12).
Changed 10 Oct 2026, before any name was issued (security review L10): the digits 2 to 9, u, d, the r-before-n step and the two repeat steps are new; m now expands to nn, not rn. Every copy of the rule must change together: registrar/names, registrar/resolver-client-ts, the wallet's extension/src/shared/names.ts and the site's names-wasm.
5. Names shaped like an address
A label is refused (looks_like_address) when, after N2–N6, it matches any of these, tried in order. They test shape only: after lower-casing no checksum can be verified, so the rules are deliberately broad.
| # | Kind | Shape of the label |
|---|---|---|
| A1 | xrpl_classic | 25 to 35 characters, the first is r, every character is a–z or 1–9 (so: no 0, no hyphen) |
| A2 | xrpl_x_address | exactly 47 characters, the first is x or t, every character is a–z or 1–9 |
| A3 | evm_hex | starts with 0x, at least 3 characters, everything after 0x is 0–9 or a–f |
| A4 | bitcoin_base58 | 26 to 35 characters, the first is 1 or 3, every character is a–z or 1–9 |
| A5 | bitcoin_bech32 | 14 to 90 characters, starts with bc1 or tb1, and every character after that is 0, 2–9 or a letter other than b, i, o |
Why: name services have been abused by registering a name made of a victim's address, so that an interface which matches names shows a stranger's name when the address is pasted.
What this costs, said plainly: a long name that starts with r and has no hyphen and no zero is refused although it is nobody's address (robertjohnsonthethirdofnewyork, 30 characters). One hyphen makes it a name again. With the launch maximum of 32, A2 can never be the reason a name is refused (47 is already too long); it is there so that a pasted X-address is called an address, and for the day the maximum grows. A3 refuses 0x followed by hex at any length (0xdead), not only 40 digits, because wallets show addresses shortened. DEFAULT — founder to confirm (the breadth of A1 and A3).
Separately, and whatever a name looks like: a wallet must treat an input that parses as an address as an address, and never look it up as a name.
6. Having a name
6.1 One name per OnWallet ID
At launch an OnWallet ID can claim one name. The limit is enforced by the claim voucher (docs/registrar-api.md §4): one voucher per ID, one name per voucher. A second name per person is a later, paid feature.
6.2 Genesis names do not expire
A name claimed with a Genesis voucher has no term and no renewal. It ends only if its holder burns it or it is revoked under §7.
6.3 Paid names: term, notice, grace
These rules are decided now so that the first paid name is sold under published terms. No paid name exists until the founder sets the fees (§10).
| Parameter | Value |
|---|---|
TERM | 365 days from registration or from the end of the previous term. DEFAULT — founder to confirm |
| Notices | 30, 7 and 1 day before the term ends, and on the day. In the wallet for the account that holds the name, and by email if the holder gave an address for notices |
GRACE | 90 days after the term ends. DEFAULT — founder to confirm |
| During grace | The name still resolves to its holder. It can be renewed at the ordinary renewal fee, by anyone on the holder's behalf. Nobody else can register it |
| After grace | The registrar burns the NFT (it is issuer-burnable). The name resolves to nobody and enters quarantine (§6.4) |
The term is not on the ledger: the URI is fixed and holds only the name. The ledger shows a paid name as live until it is burned. The registrar publishes each paid name's term end (docs/resolver.md §4.1, expires_at) so that a wallet can warn a sender that a name is in grace.
6.4 Quarantine
When a name's NFT is burned, for whatever reason, the name cannot be registered by anyone else for QUARANTINE = 90 days (DEFAULT — founder to confirm). During quarantine it resolves to nobody, so a payment sent "to the name" fails in the sender's wallet instead of reaching a stranger. Its skeleton stays taken for the same period.
| The NFT was burned because | Then |
|---|---|
| A paid term and its grace ran out | Quarantine. The previous holder may register it again during quarantine at the ordinary fee; after it, anyone may |
| The holder burned it | Quarantine. The same ID may take a new name once quarantine has ended, at most once in 12 months. DEFAULT — founder to confirm |
| Revoked for phishing, or under a trademark ruling (§7) | No quarantine clock: the name moves to the reserved list (blocked, or held for the rights holder) and does not return to the public |
| Lost key, re-issued to the same person (§7.2) | A published wait of REISSUE_WAIT = 7 days, then re-issue to the new address. DEFAULT — founder to confirm |
| The move to the tradable form (§9.3) | None is needed: the name never stops resolving |
An unclaimed offer expired (docs/registrar-api.md §6.6) | None: the name was never held by anyone |
6.5 Destination tags: not in a name
Decision: a name carries no destination tag, and a name points only to an account whose key its holder has.
- A name is an NFT in the holder's own account. Claiming one needs a signature from that account (
docs/registrar-api.md§5). An exchange deposit address cannot give one and cannot accept an NFT offer, so a name can never point at a shared, tagged address in the first place. - The name's URI is fixed and holds only the name (§8). A tag kept anywhere else would be a second record to trust, and a wrong tag loses money.
- A wallet that resolves a name must read the holder account's flags. If the account has
lsfRequireDestTagset, the wallet refuses to send to the name and says why: it has no tag to supply.
Tags may return with the signed records of §12, where the holder's own key signs them. Until then: no tag. DEFAULT — founder to confirm.
7. Revocation
Names are issuer-burnable (§8.3), so the registrar can end any name. This section is the promise about when it will.
7.1 Grounds
| Ground | What it covers | Notice before the burn |
|---|---|---|
| Phishing or fraud | The name is being used to impersonate a person, organisation or service in order to take money or credentials | None when the fraud is in progress; the holder is told at once and may appeal |
| Trademark ruling | A court order, or a decision under the complaints process in §7.3, that the name infringes a mark | 14 days for the holder to answer, unless an order sets another period |
| Re-issue at the holder's request | The holder has lost the key, or it is compromised, and asks for the name at a new address (§7.2) | The published wait (§6.4) |
| Minted wrongly | Minted by mistake, or by a stolen key, against these rules | None |
| Legal requirement | The registrar is required by law to end it | As the law allows |
Not grounds: that someone else wants the name, that the name is unused, that it is rude but not on the reserved list, that its holder criticises OpenApps.
7.2 Re-issue after a lost key
The holder proves control of the OnWallet ID that claimed the name (a fresh full sign-in), names the new address, and proves control of that address. The registrar burns the old NFT, publishes the entry in the log, waits REISSUE_WAIT, and mints the name to the new address. During the wait the name resolves to nobody.
The wait exists because, on the ledger, a re-issue is indistinguishable from a takeover by someone who holds the minter key: both are a burn followed by a new mint (§9.4, §11). The wait and the log are what let the real holder, and wallets, notice.
A Genesis pass is re-issued the same way, keeping its number, only if the pass is issuer-burnable. That is a founder decision (docs/genesis-claim-spec.md §12); if the pass is not burnable, a pass on a lost key stays there for good.
7.3 Process and what is published
- Complaints go to the address published on the policy page (the support mailbox the release checklist requires; not yet created).
- A revocation is decided by two people and carried out by an operator action that needs both (
docs/registrar-api.md§11). The holder may appeal within 30 days; a successful appeal re-issues the name. - Logged (internal, kept): who decided, the evidence, the complaint, the holder's answer.
- Published (a public revocation log, append-only): the name, the date, the ground (one of the five above), the NFTokenID, and the hash of the burn transaction. Never the complainant's or the holder's personal data. A burn that is not in the log is, by this policy, an incident.
8. How a name is written into the ledger
Permanent at the first mint. Names are not mutable, so these bytes are on the ledger for as long as the NFT exists.
8.1 A name's URI
The NFT's URI is the ASCII bytes of
openwallet-name:<label>.<suffix>
that is: the fixed prefix openwallet-name: (16 bytes), then the full normalised name of §2. Nothing else: no path, no query, no terminator, no padding, no upper case.
For alice.on:
| Text | openwallet-name:alice.on |
| Length | 24 bytes |
URI field (hex) | 6F70656E77616C6C65742D6E616D653A616C6963652E6F6E |
- The longest name gives 16 + 32 + 1 + (suffix) bytes, far under the ledger's limit of 256 bytes (rippled
kMaxTokenUriLength). - Matching is byte for byte, on the decoded bytes. A URI is the URI of a name
Nif and only if it equals the encoding ofN. There is no prefix match, no suffix match, no case folding.malice.onends inalice.onand is a different string, so it is a different name (review B3). Servers print hex in upper case; compare bytes, not hex text. - The decoder is total: given any bytes it returns the one name they encode, or nothing (
openwallet_names::name_from_uri).
Why a scheme of our own and not a web address: a name's URI can never change, so it should not depend on a domain name staying ours; a text URI is the name, readable in any explorer with nothing to fetch; and nothing is fetched, so nobody learns who looked at a name. The cost is that a generic NFT viewer shows no picture for a name. DEFAULT — founder to confirm (the prefix string; the alternative is https://<host>/…/<name>).
8.2 A Genesis pass's URI
The pass is mutable so that the registrar can write its number and level. Its URI is a metadata address under a fixed prefix P (per network; on Mainnet https://meta.openapps.network/genesis/, DEFAULT — founder to confirm):
| State | URI |
|---|---|
| Minted and offered, no number yet | P + unnumbered.json |
Accepted: number n, level v | P + <v>/<n>.json |
n is the Genesis number in decimal, from 1, with no leading zeros. v is the level, 1 to 4 (1 at claim). Again nothing else, and the decoder is exact (pass_from_uri): …/1/0042.json is not a pass URI. The metadata those addresses serve is in docs/registrar-api.md §13.
The number is read from the URI. It is the registrar's word: the ledger does not stop a minter key writing any number on any pass (§11).
8.3 Flags
Fixed at mint, for the life of the NFT.
| Genesis pass | Name, launch form | Name, tradable form | |
|---|---|---|---|
| Taxon | 1 | 2 | 3 |
tfBurnable (0x0001) | founder decision | on | on |
tfTransferable (0x0008) | off | off | on |
tfMutable (0x0010) | on | off | off |
TransferFee | field omitted | field omitted | set once; founder decides |
The first two bytes of an NFTokenID are these flags, so anyone can read them from the ID alone.
9. The canonical rule
The ledger does not compare URIs: an issuer can mint any number of NFTs with the same one. So "which NFT is alice.on" needs a rule that anyone can apply to the ledger and get the same answer.
9.1 The rule
Take the NFTs issued by the names issuer. The name N is the NFT that passes C1–C3 and wins C4. If none passes, N does not exist.
| An NFT qualifies only if | |
|---|---|
| C1 | it is not burned; |
| C2 | its NFTokenID says: issuer = the names issuer, and either taxon 2 with flags exactly 0x0001 and transfer fee 0, or taxon 3 with burnable and transferable set, mutable not set, and no flag other than those two and tfOnlyXRP; |
| C3 | its URI is exactly the encoding of N (§8.1). |
| C4 | Of the NFTs that qualify, the one with the lowest mint sequence is the name. |
| C5 | The name points to the account that holds that NFT — unless that account is the issuer or one of its minters, in which case the name is minted but not yet claimed, and points to nobody. |
The mint sequence is the last 4 bytes of the NFTokenID. It is the issuer's own counter: it only goes up, and no two NFTs of one issuer share a value. The taxon is bytes 24–27 XORed with 384160001 × sequence + 2459 (mod 2³²). Everything C2 and C4 need is in the 32-byte ID; C1, C3 and C5 need the ledger's record of the NFT.
Why each clause:
- C2, "mutable not set". A minter key could mint an NFT with a throwaway URI today and rewrite the URI to
alice.onnext year. Its mint sequence would be lower than the honestalice.onminted in between, and it would win C4. Refusing every mutable NFT closes that. - C4, "lowest". A duplicate minted later never becomes the name while the first one exists. Minting a second
alice.ontherefore gains nothing. - C5.
NFTokenMintwith an offer puts the new NFT in the minter's account until the member accepts. Without C5, every name would briefly "point to" the registrar.
In the crate: canonical(nfts, issuer, name), canonical_names(nfts, issuer); C5 is applied by the caller, who knows the accounts.
9.2 Ties
There are none. Two NFTs of one issuer cannot have the same mint sequence, and there is one names issuer per network. (An implementation handed a malformed list with two different IDs of equal sequence takes the lower NFTokenID, so that its answer never depends on the order of the list.)
If the names issuer ever has to be replaced, that is a new version of this policy, which will say how the two issuers are ordered. Version 1 has one.
9.3 The move to the tradable form
Later, a holder may exchange the launch form (taxon 2) for the tradable form (taxon 3). Both carry the same URI. The order of operations matters:
- The registrar mints the taxon-3 NFT with an offer to the holder. Its mint sequence is higher, so the taxon-2 NFT is still the name (C4).
- The holder accepts. They now hold both; the name still points to them.
- The registrar burns the taxon-2 NFT. The taxon-3 NFT is now the lowest that qualifies, and is the name. It points to the same holder.
The name resolves, to the same account, at every step. (The plan's first sketch burned before minting, which leaves the name pointing at nobody in between; the canonical rule makes that unnecessary.) The registrar burns in step 3 as soon as it sees the acceptance validated; a holder who sold the taxon-3 NFT in that moment would find the name still pointing to themselves until the burn, a matter of seconds.
9.4 What the rule does when the first NFT is burned
The next-lowest qualifying NFT, if there is one, becomes the name. That is what makes re-registration after quarantine, and re-issue after a lost key, work. It is also the rule's limit: whoever can burn the first NFT and mint another can move the name. The holder can burn their own. The issuer and its current minter can burn any name, because names are issuer-burnable (§8.3). See §11.
10. Fees
Fees are parameters, set by the founder and published before they apply. None is set today, and nothing in the code holds a number.
| Parameter | Meaning |
|---|---|
FEE_REGISTER[length] | Price of a new name, by label length class |
FEE_RENEW[length] | Price of one more TERM |
FEE_ASSET | What fees are paid in (XRP, RLUSD) and how the price is quoted |
GENESIS_WAIVER_ENDS | The date after which a new claim is no longer free. Names claimed before it stay free and do not expire (§6.2) |
TRANSFER_FEE | The transfer fee written into taxon-3 NFTs at mint (permanent per NFT) |
TERM, GRACE, QUARANTINE, REISSUE_WAIT | §6 |
A fee is paid with the mint, in one transaction (NFTokenMint with a priced offer, which the buyer accepts). Accepting a priced offer is not something the launch wallet can sign: docs/genesis-core-api.md allows only a zero-price offer. Paid names therefore need a new wallet release and its own review.
11. What is trustless, and what is not
Said plainly, because the public pages must say it in these words.
Checkable by anyone, against the ledger, without trusting OpenApps:
- that a given NFT exists, was issued by the names issuer, has the flags of §8.3, carries exactly a given name, and is held by a given account;
- that it has the lowest mint sequence among the issuer's live NFTs with that URI (by listing the issuer's NFTs:
docs/resolver.md§6); - so: a resolver's answer is checkable. A resolver that lies is caught.
Depends on the registrar behaving:
- Uniqueness. The ledger lets the issuer mint duplicates. The canonical rule makes a later duplicate lose, but only the registrar stops them being minted.
- Revocation. The issuer and its current minter can burn any name, and after a burn can mint the name to someone else. §7 is a promise, the log makes breaking it visible, and the waits of §6.4 give time to notice. None of that is enforced by the ledger.
- The reserved list and the look-alike rule. Applied at registration, by the registrar. Auditable afterwards; not enforced.
- Pass numbers and levels. Whatever the minter writes.
What a stolen minter key can do to names: mint duplicates (they lose, C4), mint names that break §2–§5, burn any name, and after burning one mint it to an address of its choosing — which then is the name under §9. It cannot change a name's URI, move an NFT out of a member's account without burning it, or touch anyone's funds. The response is in docs/registrar-api.md §9. (The plan's risk table says a stolen key "cannot rename anyone". That is true of the URI and not of the name: burn and re-mint moves a name. This document is the correction.)
Because of that, a wallet that sends to a name should treat a name whose NFT replaced a burned one held by a different account less than 30 days ago as changed hands, and say so before the first payment (docs/resolver.md §6.3). DEFAULT — founder to confirm (30 days).
12. Records for other chains (not built; direction only)
Sending to alice.on on another chain will not put anything new on the XRP Ledger. The plan is a signed record:
- a list of
(chain, address)pairs, where the chain is named in CAIP-2 form (eip155:1), with a version number and an expiry; - signed by the key of the XRP Ledger account that holds the name at the time, over a domain-separated message that cannot be a transaction;
- served by resolvers next to the name, and verified by the wallet: the signature, that the signing account is the current holder under §9, and the version (a lower version than one already seen is refused).
A record dies with the holding: when the name moves or is burned, records signed by the previous holder are void. Nothing in version 1 of this policy depends on records, and no resolver serves them yet. The record format, the signature's message, key rotation for accounts that use a regular key or a signer list, and whether a record may carry an XRP Ledger destination tag (§6.5) each need their own spec before any of it is built.
Appendix A. The two look-alike modes, measured (for the founder's decision)
Produced by cargo run -p openwallet-names --example look_alike_report (add --features narrow-look-alikes for the narrow mode), 10 Oct 2026, from the code as it stands. The word lists are in registrar/names/testdata/ with their sources: 4,348 first names of five letters or more (U.S. Census 1990) and 57,360 English words (the 12dicts lists). They stand in for "ordinary names people will ask for"; they are not a sample of real demand.
A.1 The choice
Strict (default, policy_version 2) | Narrow (policy_version 3) | |
|---|---|---|
| Two ordinary names collide when | their skeletons are equal | they are the same name |
A look-alike of a member's name (a1ice beside alice, sheila beside shelia) | refused | free |
A look-alike of a reserved name (supp0rt, c0inbase) | refused | refused |
| First names that share a skeleton with another first name on the list | 647 of 4,348 (14.9%), in 298 families | none |
| … so cannot be had once the first of each family is taken | 349 (8.0%) | none |
| English words that share a skeleton with another | 2,292 of 57,360 (4.0%), in 1,107 families | none |
| … cannot be had once the first of each family is taken | 1,185 (2.1%) | none |
Families the strict mode makes one name: aileen / aleen / alleen / allen; carol / caroll / carrol / carroll; melisa / melissa / mellisa / mellissa; sheila / shela / shelia / shella; clelia / delia / delila / della.
In one sentence each. Strict: nobody can register a look-alike of anyone's name, and about one first name in twelve will be refused because a different, real name got there first, with an answer that names the other name. Narrow: every ordinary name is available until that exact name is taken, and a member's name has no protection against a look-alike registered later; only the reserved list is protected. The wallet's warning on a first payment to a name is then the only defence for ordinary names.
The switch can be thrown from strict to narrow later without harm (names that were refused become available). It cannot be thrown from narrow to strict once two look-alike names are registered: the rule cannot un-register either (§4.3).
A.2 What the reserved rules cost, in both modes alike
| List | Listed themselves | Refused without being listed | Of which by a stem | by a near miss |
|---|---|---|---|---|
| First names | 1 (genesis) | 0 | 0 | 0 |
| English words | 65 | 71 (0.12%) | 69 | 0 |
The 71 are inflections of protected words, which is the rule working as written: administration, contacts, founders, officially, recovered, services, supports, tethered, verifying, and two look-alikes (goggle of google, illegal of legal). The allow list lets 25 further English words and 3 first names through that the rules would otherwise refuse (badminton, count, ether, ledge, papal, resolve, sport, supporter, stella, …). Against a dictionary the allow list was not drawn from (/usr/share/dict/words, 227,985 names) the rules refuse 0.21% and the allow list accounts for 20 words, so it is not fitted to its own test.
Measured and not kept, because of what they refuse: any one letter replaced by any other around a protected word (102 more English words: ballet, admit, apply, count); any one letter added (39 more: apples, contract, emails).
A.3 What still gets past the reserved rules, in both modes alike
The second-round review's moves, applied to all 162 reserved labels of five letters or more. "Free" is the number of variants a stranger could still register.
| Move | Free | Caught | Where the free ones are |
|---|---|---|---|
Swaps the skeleton covers (0/o, 1/l/i, rn/m, …) | 0 | 1,327 | |
| A hyphen inserted | 0 | 1,148 | |
| A doubled letter | 9 | 1,260 | Only around word entries (systemm, claimm) |
Other ASCII look-alikes (m/n, q/g, c/e, …) | 202 | 2,372 | 175 around word entries; the rest mostly 6 for g (led6er, 6oogle), which the skeleton reads as b |
| A dropped letter | 76 | 1,101 | 69 around word entries; 6 are allowed words (ledge, papal, ether) |
| Two letters swapped | 77 | 1,030 | All around word entries |
A prefix (the-, my, real-, x, …) | 357 | 2,235 | None around a stem. 238 around the 31 entries with no flag (mystaff, realaccount, xripple), 119 around word entries |
A suffix (-official, help, app, 1, s, …) | 585 | 7,191 | None around a stem. staffhq, ripple123, systemapp |
Embedded (xNAMEx) | 45 | 440 | None around a stem |
| A covered swap and a suffix | 230 | 2,458 | None around a stem |
| A covered swap and a dropped letter | 63 | 714 | 9 around stems (refuni, xrpnare) |
Read by flag: around the 116 entries marked stem (every brand of OpenApps, the exchanges and wallets, the roles of this service) a prefix, a suffix, an embedding, a doubled or swapped letter all fail, and what is left is 6 for g and a handful of dropped letters. Around the 31 entries with no flag (staff, account, login, ripple, google, apple, bitcoin, the people) a bare prefix or suffix without a hyphen still passes: mystaff, ripple123, xgooglex; with a hyphen it does not (my-staff, ripple-team). Around the 15 marked word (system, billing, claim, press, circle, …) nearly every misspelling passes, by design.
Every case the review named is refused in both modes: openwallet-support, xopenapps, coinbase-help, binance-official, real-binance, official-coinbase, xbinancex, ledger-wallet, metamask-io, xaman-app, ripple-team, security-team, the-official, opennwallet, suupport.
What to decide here, besides the mode: whether ripple, google, apple, bitcoin, ethereum, solana, stellar and the role words staff, account, login should be stems (it closes the 238 + 393 + 30 + 162 above and refuses words such as accountant, staffing, cripple, grapple, pineapple, which would then need allow lines); and whether the g/6 pair is worth boggle and boogie becoming near misses of google.