A name registry for Nimiq built without a virtual machine, without contracts,
and without changing consensus. Registrations travel inside ordinary
transaction data. A deterministic indexer replays them into a
name → address mapping. A Nimiq Pay mini app is the client.
The chain is used as what it already is — an ordered, finalised message bus. Everything else is interpretation, and the interpretation rules are published so anyone can run them.
| No contracts required | Works on Nimiq as it exists today. Nothing to deploy, nothing to upgrade |
| Independently verifiable | Anyone with a Nimiq node can replay the same history and derive the same state. Not "trust our API" |
| Anchored | Checkpoint roots are published to an EVM chain on every change, with a daily floor, so past claims cannot be quietly rewritten |
| Delegated subdomains | An exchange registers one name and issues user.exchange addresses for free, with zero on-chain state |
| Cheap | One governed fee for names of twelve characters or more; shorter ones cost a frozen multiple of it |
Nimiq chain Indexer Clients
┌────────────────┐ ┌────────────────┐ ┌──────────────────┐
│ NNS1Grico │ │ replay rules │ │ resolve("rico") │
│ NNS1Sfoo │ ──> │ §4 validity │ ──> │ + Merkle proof │
│ NNS1Xbar │ │ §7 reducer │ │ + quorum check │
│ (64-byte data) │ │ §8 Merkle │ │ + anchor check │
└────────────────┘ └───────┬────────┘ └──────────────────┘
│
▼
root → EVM chain (on change)
log → IPFS (content-addressed)
Two clocks, and only the first decides whether a name works:
| Clock | Interval | Gates |
|---|---|---|
| Finality — the macro block that closes a batch | ~1 min | The name is registered, resolves, and carries a Merkle proof. A checkpoint is cut at every finalised macro block, so "final" and "provable" are one moment |
| Anchor | on change, daily floor | The root is notarised on an EVM chain |
Your payee field takes NQ64 VFXQ TPAS 5Q7S ADEX 072S CR2M QCQ4 8P8M. It
could take rico, in two minutes and one script tag — and you can hand every
one of your own users a name for free, with no per-user registration and no
on-chain state. INTEGRATE.md is the one-page version;
docs/integration.md is the whole of it.
- Anyone, no node, seconds — fetch the log by its CID, replay it, compare the root to the anchor.
- Any Nimiq node — verify proofs against a checkpoint root.
- Any history node — recompute the entire state from the chain itself. This is the tier that catches a dishonest operator, and the one the project actively wants other people running.
The spec is deliberately blunt about what this does not guarantee — see §2.1 and §2.2. Proofs and anchoring stop equivocation and retroactive rewriting; they do not stop a sole operator who is also the sole publisher. What defeats that is independent replay, client quorum, and multi-publisher anchoring.
Everything is a NNS1 prefix, one type character, and a payload — inside
Nimiq's 64-byte transaction data limit.
G register |
S set target |
E link EVM address |
X transfer |
D delegate |
K cancel |
N renew |
O offer |
B buy |
A auction |
M settlement |
P governance |
U unreserve |
F burn attestation |
Fourteen types. There is no recovery message: R was removed in r20,
because every version of it the owner key could cancel was
theatre, and every version it could not outranked the owner. A lost owner key
is a lost name, as in ENS.
| Path | |
|---|---|
docs/nns-spec-v1.md |
The protocol specification. Authoritative |
docs/integration.md |
The integration guide — the HTTP API from any language, the @nimiqnames/resolver library, verifying proofs yourself, subdomains for exchanges, writing to the registry, running a resolver, names on EVM chains |
docs/runbooks/ |
operators.md (the role map), release.md (publishing the three npm packages) |
packages/ |
Twelve packages — packages/README.md explains each one and how they stack |
deploy/ |
One directory per operator role — deploy/README.md picks the right one and covers what they share |
A pnpm workspace: TypeScript strict throughout, one Vitest run across every
package, dependency versions pinned once in the workspace catalog. Amounts are
bigint luna; heights are number.
Pre-launch. All twelve packages are built and conform to spec r29. The wire format is settled and empirically verified against mainnet. 2,268 tests pass with a database attached, none skipped.
What remains before a mainnet launch is deployment and one irreversible input,
not code: completing RESERVED_NAMES, a full mainnet battery, and a second
freeze that pins LAUNCH_HEIGHT to a future height and regenerates the four §3
role addresses. §12 is what remains undecided in the
protocol itself.
core is the reference implementation, and writing it is how the spec got past
r15: it surfaced ten places where two conforming implementations would have
derived different roots — same-height effect order, the log's <data> field,
the checkpoint layout byte for byte, seven more. All ten are pinned in the spec
and held by a vector, and nineteen further check-order choices stay deliberately
unratified, pinned only by packages/core/vectors/reduce.json — which records
the reading taken for each, so a second implementation can match it byte for
byte. That this list exists at all is the best evidence available that the
design is being taken seriously.
Spec revisions are numbered, and the spec says which ones moved bytes — those
required every database derived under the old rules to be rebuilt, because
configFingerprint covers configuration and not rules, so nothing refuses the
resume for you.
Names are [a-z0-9-], 5–24 characters, with a positional digit rule that
removes the n1m1q / nimiq class of impersonation entirely.
The point of the project. One container, one RPC URL pointed at a Nimiq node you already run — no new storage, no resync. An independent replay is worth more to this protocol than any assurance its authors can offer about themselves.
cp .env.example .env # fill in NNS_RPC_URL
docker compose upPostgres comes with it, published on 127.0.0.1:5433 so it does not clash
with a local server. Nothing else needs setting: since the launch freeze every
§3 value — LAUNCH_HEIGHT, the four role addresses, RESERVED_NAMES, the O
listing fee — is a constant in @nimiqnames/core, not configuration, because a value
an operator can set is a value two indexers can disagree about.
To run it outside a container instead, packages/indexer/.env.example is the
same set of variables pointed at a local database.
An indexer answers nobody on its own. deploy/ has one directory per operator
role — compose file, .env.example, README — so nobody reads configuration
belonging to a role they do not run:
- A resolver (
deploy/resolver) — Postgres, the indexer and the read-only API, with only the API published. This is what §8.5's quorum is made of: clients ask several independent ones and compare, so the protocol stops depending on us exactly to the degree that other people run these. - A delegate (
deploy/delegate) — one container and a JSON file, for a name owner who wantsshop.theirnameto resolve. No node, no database, no key. - Both, on one box (
deploy/collaborator) — the two above in one compose project, one.envand oneup. Everything a third party can run; it merges nothing, and the resolver's proofs and the delegate's unproven answers stay as different as they were. - An anchor publisher (
deploy/anchor) — notarises checkpoint roots on the EVM contract. The contract is permissionless on purpose: a second, independent party anchoring is what turns timestamping into §8.5's anchor quorum, and recruiting one is a launch deliverable.
docs/runbooks/operators.md is the map: what each
role is, what it needs, and the things that are easy to get wrong — starting
with the fact that a delegate is not a resolver, and that "resolver" names both
a server (packages/api) and the client library that queries several of them
(packages/resolver).
The mini app needs nothing but this repository — no node, no database, no
configuration. @nimiqnames/resolver ships the endpoints it asks, so a dev server is
pointed at the live registry from the first render:
pnpm install
pnpm --filter @nns/app devpnpm install
pnpm build # first — see below
pnpm typecheck # strict, and wider than the build: tests and tooling too
pnpm test # one Vitest run over every package — 2,268 testsAll three, before calling anything done. The build config is narrow (src/
only) and typecheck is wide, so a build can be broken while typecheck is green
— a broken core build survived several commits exactly that way.
The build has to come first on a fresh clone, and that is not a stylistic
preference: every package consumes @nimiqnames/core through the dist/ its
exports point at, never through src/. Until it exists, typecheck cannot
resolve the module and 67 of the 103 test files fail. Once built, the order
stops mattering.
72 of those tests are gated on a database — the SQL seam, the migrations, the
§8.1/§8.2 persistence rules, the settlement ledger's idempotency — and skip
unless NNS_TEST_DATABASE_URL points at a Postgres. Give them one:
docker compose up -d postgres
NNS_TEST_DATABASE_URL=postgres://nns:nns@127.0.0.1:5433/some_throwaway pnpm testEach suite drops its own schema on arrival, so point it at a throwaway database — never one holding a replay you would rather not rebuild.
The specification is the place to start. If something in it is wrong, ambiguous, or will age badly, that is the most valuable issue you can open — more valuable than a patch, because a wire format is expensive to change and cheap to argue about beforehand.
MIT. Including the specification: an interpretation layer that anyone may reimplement is the only kind worth trusting.
