WAVE Names: Human-Readable Names on Radiant

Version 1.0 Last Updated: June 2026

1. What Are WAVE Names?

A WAVE name is a human-readable name on the Radiant blockchain — for example satoshi.rxd — that points at a payment address. Instead of asking someone to send RXD to 1Rxd…long…address, they can send to satoshi.rxd and your wallet resolves the name to the right address.

Under the hood, every WAVE name is a mutable Glyph NFT. The NFT holds the name's metadata — including its current target address — and whoever holds the NFT controls the name. Because the NFT is mutable, the owner can re-point the name at a new address at any time (e.g. when rotating wallets) without losing the name itself.

Already in production. WAVE names power addressing and profiles inside Photonic Wallet and GlyphGalaxy. This page documents the public resolution flow so any dapp can integrate name lookup with a copy-paste snippet.

2. First-Registration-Wins

WAVE names are claimed on a first-registration-wins basis. If two people both mint an NFT claiming the name satoshi, the indexer treats the earliest registration (lowest block height, then transaction order) as the canonical one. Every later registration of the same name is recorded as a duplicate and is never returned by resolution.

This means resolution is deterministic and unambiguous: resolveWaveName('satoshi') always returns the canonical owner's current address, regardless of how many copycats exist. The canonical NFT can still be transferred or sold — rights follow the token — but a newcomer cannot hijack a name simply by minting a clone after the fact.

Register early. Because the first valid registration is canonical forever, there is no way to "take over" a name that someone else already claimed. Check availability (see the API reference) and register the names you want before someone else does.

3. Registering a Name

The easiest way to register a WAVE name today is Photonic Wallet, the reference Radiant wallet that mints the WAVE NFT for you and lets you set and update its target address:

  1. Open Photonic Wallet and make sure your wallet is funded with a small amount of RXD (registration is an on-chain mint, so it costs a network fee).
  2. Go to the WAVE / Names section and search for the name you want — the wallet checks availability against the indexer.
  3. If the name is free, mint the registration. This creates the mutable Glyph NFT that is your name.
  4. Set the target to the address you want the name to resolve to. You can update this later from the same screen.

Once the registration transaction confirms and the indexer ingests it, the name resolves through the public API immediately — no extra publishing step.

4. Resolving a Name in Code

You don't need to parse any on-chain data yourself. Drop the dependency-free wave-resolver.js module into your dapp. It calls the public RXinDexer REST API and returns a plain { address, ref, owner } object (or null if the name is unregistered). It works in any environment with a global fetch — modern browsers, Deno, and Node.js 18+.

⬇ Download wave-resolver.js (ESM, no dependencies)

const DEFAULT_API_BASE = 'https://radiantcore.org/api';

// Normalize: trim, lowercase, strip an optional ".rxd" suffix.
function normalizeWaveName(name) {
  if (typeof name !== 'string') throw new TypeError('WAVE name must be a string');
  let n = name.trim().toLowerCase();
  if (n.endsWith('.rxd')) n = n.slice(0, -4);
  return n;
}

async function getJson(url) {
  const res = await fetch(url, { headers: { accept: 'application/json' } });
  if (!res.ok) throw new Error(`WAVE API returned HTTP ${res.status} for ${url}`);
  return res.json();
}

// Resolve a name -> { address, ref, owner } | null (null = unregistered).
export async function resolveWaveName(name, apiBase = DEFAULT_API_BASE) {
  const bare = normalizeWaveName(name);
  if (!bare) return null;
  const base = apiBase.replace(/\/+$/, '');
  const data = await getJson(`${base}/wave/resolve/${encodeURIComponent(bare)}`);
  // Unregistered names come back as { available: true, resolved: false }.
  if (!data || data.available || data.resolved === false) return null;
  const address = data.target || (data.zone && data.zone.address) || null;
  if (!address) return null;
  return { address, ref: data.ref ?? null, owner: data.owner ?? null };
}

// Is a name free to register?
export async function isWaveAvailable(name, apiBase = DEFAULT_API_BASE) {
  const bare = normalizeWaveName(name);
  if (!bare) return false;
  const base = apiBase.replace(/\/+$/, '');
  const data = await getJson(`${base}/wave/available/${encodeURIComponent(bare)}`);
  return Boolean(data && data.available);
}

Usage: resolve, then send RXD

import { resolveWaveName } from './wave-resolver.js';

const hit = await resolveWaveName('satoshi.rxd');
if (!hit) {
  console.log('Name is not registered — nothing to pay.');
} else {
  console.log('Pay this address:', hit.address); // current target address
  console.log('Canonical ref:', hit.ref);        // "txid_vout"
  console.log('Owner scripthash:', hit.owner);

  // Build the payment with radiantjs (https://github.com/Radiant-Core/radiantjs).
  // Amounts are in photons (1 RXD = 100,000,000 photons).
  const tx = new radiant.Transaction()
    .from(utxos)
    .to(hit.address, 100_000_000) // send 1 RXD
    .change(myChangeAddress)
    .sign(myPrivateKey);

  await electrum.broadcast(tx.toString());
}
Error vs. not-found. resolveWaveName returns null only when a name is genuinely unregistered. If the indexer is unreachable or returns an error (e.g. HTTP 503 when the WAVE index is offline), it throws — so you can tell "no such name" apart from "couldn't reach the indexer" and avoid sending funds on a failed lookup.

Checking availability

import { isWaveAvailable } from './wave-resolver.js';

if (await isWaveAvailable('myhandle')) {
  // Free — register it in Photonic Wallet: https://photonic-wallet.com/
} else {
  console.log('Taken — try another name.');
}

5. REST API Reference

All endpoints are served by the public RXinDexer instance under the base URL https://radiantcore.org/api. They are GET-only, return JSON, and require no authentication. If you run your own indexer, point apiBase at it instead.

GET /wave/resolve/{name}

Resolve a name to its canonical registration and current target address.

ParameterInDescription
namepathThe name to resolve, 1–63 chars (without the .rxd suffix).
include_duplicatesqueryOptional. true to also list non-canonical duplicate registrations.

Registered → 200

{
  "name": "satoshi",
  "ref": "txid_vout",
  "target": "1RxdPaymentAddress...",   // the address to pay (mutable)
  "zone": {                            // DNS-like records (any field may be null)
    "address": "1RxdPaymentAddress...",
    "display": "Satoshi",
    "avatar": null,
    "url": "https://example.com",
    "TXT": ["hello"]
  },
  "owner": "32-byte-hex-scripthash",
  "available": false,
  "canonical": true,
  "has_duplicates": false
}

Unregistered → 200

{ "name": "nobody", "available": true, "resolved": false }

Index offline → 503  { "detail": "WAVE index not available" }

GET /wave/available/{name}

Check whether a name is free to register.

ParameterInDescription
namepathThe name to check, 1–63 chars.

Available → { "available": true, "name": "myhandle" }

Taken → { "available": false, "ref": "txid_vout", "name": "satoshi" }

GET /wave/names

List all canonical (first-registration) WAVE names. Results are paginated with an opaque cursor.

ParameterInDescription
limitqueryMax results per page. Default 500, maximum 2000.
cursorqueryOpaque pagination token. Pass the next_cursor from the previous response to fetch the next page. Omit for the first page.
include_duplicatesqueryOptional. true adds a has_duplicates flag to each entry.

Response → 200

{
  "names": [
    {
      "name": "satoshi",
      "domain": "rxd",
      "full_name": "satoshi.rxd",
      "target": "1RxdPaymentAddress...",
      "ref": "txid_vout",
      "height": 410100,
      "spent": false,
      "canonical": true
    }
  ],
  "total": 1,
  "next_cursor": "eyJjaGFyX2lkeCI6IDUsInJlZiI6IC..."  // null when no more pages
}

To page through every name, keep calling the endpoint and feeding the previous next_cursor back in until next_cursor is null:

async function* allWaveNames(apiBase = 'https://radiantcore.org/api') {
  let cursor = null;
  do {
    const url = new URL(`${apiBase}/wave/names`);
    url.searchParams.set('limit', '1000');
    if (cursor) url.searchParams.set('cursor', cursor);
    const page = await fetch(url).then(r => r.json());
    for (const entry of page.names) yield entry;
    cursor = page.next_cursor;
  } while (cursor);
}

for await (const name of allWaveNames()) {
  console.log(name.full_name, '->', name.target);
}

Other endpoints

EndpointPurpose
GET /wave/registrations/{name}Canonical registration plus every duplicate registration of a name.
GET /wave/reverse/{scripthash}Reverse lookup: every name owned by a given scripthash.
GET /wave/statsIndex health and totals (names, zones, owners).

6. Notes & FAQ

Does the target address change? Yes — that's the point of a mutable name. Always resolve a name fresh at payment time rather than caching the address indefinitely; the owner may have re-pointed it.

What about the .rxd suffix? The on-chain name is the bare label (e.g. satoshi); .rxd is just the display domain. The resolver strips it for you, so satoshi and satoshi.rxd resolve identically.

Can I run my own resolver backend? Yes. Every endpoint above is part of RXinDexer, which is open source. Run your own instance and pass its base URL as the apiBase argument to keep resolution fully self-hosted.

Is resolution trustless? The canonical-registration rule is computed from on-chain data, so independent indexers converge on the same answer. For high-value payments, resolve against more than one indexer (or your own node) and confirm the ref matches before sending.