Readme — Technical Reference
A single-file dApp + three-contract suite for the 2015 Frontier-era GlobalRegistrar at
0x33990122638b9132cA29c723BDF037F1a891a70C — register, view, transfer, wrap names as
ERC-721s, and trade them in a trait-categorized marketplace with a merkle-proven Historic registry.
The registrar address is configurable, so the same app drives the Linagee Name Registrar
(0x5564886ca2C518d1964E5FCea4f423b41Db9F561) — same interface family.
The 2015 contract
This is the Frontier GlobalRegistrar (Gav Wood lineage), hardcoded in old go-ethereum as
GlobalRegistrarAddr // frontier; the deployed source was verified against geth v1.3.6
common/registrar/contracts.go. (That client-side common/registrar package
was present through at least late 2016 and was later removed from go-ethereum as the naming
approach moved toward ENS; the on-chain contract itself remains live and unchanged at its original
address.) The hardcoding is corroborated in the channel itself: on
Sep 28, 2015, rfikki surfaced and quoted the exact line —
GlobalRegistrarAddr = "0x33990122…a70c" // frontier from go-ethereum PR #1840
("…I expect geth 1.3.0 to come out next week with this change") — and core dev Viktor Trón
confirmed in real time, "it IS deployed there."
Provenance & significance. Human-readable names on Ethereum weren't an afterthought —
the GlobalRegistrar address was written into the official go-ethereum client itself in the
2015 Frontier era, making on-chain naming part of the earliest client tooling. The design grew out of
open community work: the early Ethereum name-registry discussions (on the Gitter
name-register channel and the ethereum subreddit) among builders including
Linagee, rfikki, and Viktor Trón, working out how a decentralized naming system
should behave in the network's first months. rfikki was among the first to start that
conversation — the opening message in the go-ethereum name-register Gitter channel
(Aug 27, 2015) is his — in the same room where Viktor Trón relayed Gav Wood and Vitalik
Buterin's namereg work, and where, months later, Nick Johnson (Arachnid) first shared his
draft EIP for a new Ethereum Name Service (Apr 2016) and gathered the early feedback that became
ENS — the channel was the through-line from Frontier namereg to ENS. This project continues
that lineage directly: rfikki — its steward — deployed
working instances of this exact GlobalRegistrar code during Frontier, e.g.
0x67b1e40d8e5c28b155e7fa9f7469fa03c693f74d, deployed Aug 25, 2015
(block 138,997, ~4 weeks after mainnet genesis) by collectibletrust.eth. That
deployment is bytecode-identical to the canonical registrar (all twelve function selectors and the
Changed(bytes32)/PrimaryChanged event topics match) and carries Etherscan's
"Global Name Registrar" tag. Every claim here is verifiable on-chain.
Quirks the whole app is built around:
- reserve(bytes32) is free and instant. The short-name auction in the source is a TODO
that never shipped. Any unowned name is one transaction away.
- reserve() and transfer() never revert — they silently no-op on failure. A successful
transaction proves nothing. The app re-reads
owner(name) after every registration and
transfer, with front-run detection on register.
- The
subRegistrar(bytes32) getter was commented out in the deployed source; that
slot is exposed through register(bytes32) instead. The app maps this.
- Events:
Changed(bytes32 indexed name) and
PrimaryChanged(bytes32 indexed, address indexed). Because names are raw bytes32, the
indexed topic is the name itself, not a hash — full history is recoverable from logs.
name(address) is the reverse primary lookup — but it is unauthenticated:
setAddress(name, addr, primary=true) checks only that the sender owns the name, never that
the address consents. Any name owner can plant a reverse record on any address. The app
therefore forward-verifies every reverse result (addr(name) must round-trip) and labels it
✓ verified or ⚠ unverified.
- ENS bridge & capitalized names: with the GlobalRegistrarENSResolver set on
globalregistrar.eth, lowercase names resolve in any ENS wallet as
<name>.globalregistrar.eth. ENS normalizes to lowercase, so capitalized or
unusual-byte names use the hex escape: 0x<bytes>.globalregistrar.eth
(e.g. Gold → 0x476f6c64.globalregistrar.eth) — exact bytes, unambiguous,
universal up to 30-byte names. Lookup shows each name's bridge form.
- No expiry:
renewalDate exists in the struct but nothing in the deployed code
reclaims names. Registration is permanent (until transfer or disown).
- Names are byte-exact. No normalization of any kind:
Satoshi,
satoshi and SATOSHI are three independent records. The same is true of
names differing only by whitespace (a trailing space), invisible characters
(zero-width/control), or lookalike characters (a Cyrillic о vs a Latin
o) — they are distinct names that can look identical, which is a real phishing surface.
The Register tab warns (without blocking) when a name contains these; always treat a name's
bytes, not its appearance, as its identity, and verify ownership on-chain before trusting or
buying a name. The app validates only that a name is non-empty and ≤ 32 bytes; everything else is
DYOR. See the USER_GUIDE for the full breakdown of what the app can and cannot protect against.
Architecture
- One HTML file, ethers v5.7.2 inlined (no CDN, no build step, no server).
- All config lives in the URL hash — RPC, registrar / wrapper / market addresses, and the
watchlist. Nothing touches browser storage. Bookmark the URL to keep a setup; share it to share one.
- Owner scans batch through Multicall3 (
0xcA11bde05977b3631167028862bE2a173976CA11),
120 names per page.
- Default read RPC:
https://ethereum-rpc.publicnode.com; any mainnet RPC can be set
in ⚙ Settings. Wallet transactions go through the injected provider (mainnet chainId enforced, with
a switch request if you're on the wrong network).
Extension architecture (immutable cores, additive growth). The cores — the 2015 registrar,
the wrapper, and the treasury — are immutable and never change. New capabilities attach through two
narrow seams without touching them: the wrapper's four write-once trait-module slots
(metadata), its single write-once animation-module slot (the optional on-chain generative seal,
surfaced as each deed's animation_url), and the treasury's timelocked routing table
(money). A DeedCapabilityRegistry
is the ecosystem's on-chain, read-only directory: it points at the canonical cores and lists
self-describing modules/markets/resolvers so any wallet or integrator can discover the whole system
from one address. It can only list contracts (plain addresses, read-only) — never control your
name or move funds — and the curator can renounce to freeze it forever. Future EIP-driven features,
new historic eras, or (legally-gated) reward mechanics ship as fresh audited contracts into these
seams; see FUTURE_PROOFING.md, FUTURE_ERAS.md, and
GAMIFICATION.md in the repo.
GlobalRegistrarWrapper.sol
ERC-721 (OpenZeppelin v5, Enumerable, ReentrancyGuard) bound to a registrar at deploy time. The
existing LNR wrapper is hard-bound to the Linagee registrar, so GlobalRegistrar names need this fresh
deployment. Wrap flow (LNR-style):
createWrapper(name) — stakes your claim (must own the name)
registrar.transfer(name, wrapper) — hand custody to the contract
wrap(name) — mints the token. Custody is verified by reading
registrar.owner() back, which is what makes the silent-no-op transfer safe.
unwrap(tokenId) reverses; abortWrap(name) bails out mid-flow. While
wrapped, proxySetAddress / proxySetContent / proxySetSubRegistrar keep the name usable
by the token holder. tokenURI is fully on-chain (Base64 JSON + deed-plate SVG) with
base traits Length, Charset (Digits/Letters/Emoji/Unicode/Mixed), Case (lowercase/UPPERCASE/Capitalized/MixedCase — a dimension ENS can't have, since it normalizes everything to lowercase), Club (999/10k/100k digit clubs), Pattern (Repeating/Sequential/Palindrome digit grails), Palindrome, Era, Sealed. The wrapper exposes four
write-once trait-module slots (each openable once by the governor, never re-pointable, read
through a gas-capped hostile-proof staticcall so a broken module can never brick tokenURI). Slot 0
holds the HistoricTraitModule, which once linked via setTraitModule(0, …)
adds Historic (Gold 2015 / Silver 2016), Historic Rank (number) and First
Registered (date), plus a gold or silver frame and a HISTORIC #N mark. The
remaining slots are reserved for future opt-in modules (see the capability registry below).
sealDeed(tokenId) is an irreversible owner-elected fuse that permanently freezes a
deed's records and adds a wax-seal mark, surviving unwrap → rewrap. The wrapper also exposes
contractURI() — fully on-chain collection-level metadata (name, description, banner SVG,
ERC-2981 royalty) so marketplaces like OpenSea populate the collection page automatically; see
MARKETPLACE_LISTING.md in the repo for the listing procedure.
RegistrarGenerativeModule.sol — on-chain animated seal
Delivers each deed's optional generative art, fully on-chain. The art is a Clifford-attractor
canvas rendered by an HTML <canvas> program stored in the contract (a loop,
so byte size is fixed regardless of art density — the escape from per-element SVG cost). The module
only templates the name (and, when historic, the rank) into the renderer and base64-wraps the
result; the actual drawing happens in-browser.
Unique per collection. The art seed is name + DOMAIN, where DOMAIN is this
collection's contract address (set immutably at deploy, typically the wrapper). So the same name
renders a different attractor on each contract instance — a holder's "money" here looks
nothing like "money" on another GlobalRegistrar/Linagee deployment, so the art is genuinely unique to
this collection rather than reproducible by anyone with the name. The app injects the same address
(lowercased), so the in-app seal stays byte-identical to the on-chain output.
- Two faces.
animationURI[Ranked](label[, era, rank]) returns the live HTML
canvas as a data:text/html URI (the marketplace item-page art). fallbackSVG[Ranked](…)
returns an engraved SVG plate (branding + true-case name + charset/case + gold/silver rank) as the
static image for grid/thumbnail views. tokenURIFor[Ranked](…) assembles
the full metadata JSON with both, plus Era / Historic Rank attributes.
- Wrapper integration (write-once). The immutable wrapper gained an optional
animation_url sourced from a module via the write-once setAnimationModule
slot (governor-only, same guarantee as trait slots) and an IDeedAnimationModule
interface. tokenURI appends the animation only when a module is linked, through a
gas-capped, try/catch-guarded path: an EOA, a reverting/garbage/oversized module, or no module all
degrade to no animation_url and can never brick tokenURI. The base plate
image is always present.
- Rank provenance. The seed-derived lines (name, attractor signature, charset/case) need no
external data. Historic rank is not self-derived — it's injected by the caller from the
merkle-proven HistoricAttestor, never fabricated. era 0 (non-historic) draws no rank line and is
byte-identical to the rank-free output.
- App parity. The in-app seal (Lookup view) embeds the byte-identical renderer template, so
what you see in the app is exactly what the contract serves to marketplaces — verified
programmatically on every change.
RegistrarMarket.sol
- Non-custodial, collection-agnostic. Approval-based listings:
list / cancelListing / buy with a stale-listing owner guard.
- Escrowed ETH offers:
makeOffer (auto-refunds outbid offers),
cancelOffer, acceptOffer(minAmount) with a front-run guard.
feeBps capped at 500 (5%). Indexer-friendly events:
Listed, ListingCanceled, Sale, OfferMade, OfferCanceled, OfferAccepted.
In the app: the Marketplace tab is a card storefront over this contract — each
wrapped deed shows its mini-plate (live animated seal on hover), era/trait badges, and a
price/offer badge, with a stats header (listed / offers / floor / wrapped total), sort, filter,
name search, pagination (24/page), a real-event activity feed, and context-aware
Buy / Accept-offer / Manage actions. The Collection tab adds an ◈ All Wrapped mode
listing every wrapped deed across all eras via live ERC-721 enumeration (no archive node).
HistoricAttestor.sol — Gold 2015 / Silver 2016 on-chain
The EVM can't read historical logs, so first-registration data is indexed off-chain (by this app)
and committed as a single merkle root in the constructor. attest(name, firstBlock,
firstTimestamp, rank, proof) is permissionless and permanent; getHistoric(name)
returns (attested, rank, firstTimestamp, firstBlock, era) with era 1 = Gold 2015,
2 = Silver 2016, derived from verified mainnet block boundaries:
| Era | Condition | Boundary fact |
| Gold 2015 | firstBlock ≤ 778,482 | last block of 2015 (ts 1451606392) |
| Silver 2016 | firstBlock ≤ 2,912,406 | last block of 2016 |
Leaf encoding (the app's exporter produces exactly this; double-hash per OZ MerkleProof guidance,
abi.encode for fixed-width fields):
keccak256(bytes.concat(keccak256(abi.encode(
bytes32 name, uint32 firstBlock, uint40 firstTimestamp, uint32 rank))))
Rank #1 is the earliest first-registration on the contract, ordered by
(blockNumber, logIndex) of the name's first Changed() event — which is
necessarily its original reserve(), since every other mutator requires ownership.
Deployed Contracts — Ethereum Mainnet
All contracts deployed 2026-06-18, solc 0.8.26, evmVersion cancun, optimizer 200 runs.
All verified on Etherscan. Governor is a 3-of-5 Safe multisig.
| Contract | Address | Role |
| GlobalRegistrar (2015) | 0x3399…a70C | Pre-existing 2015 Frontier contract |
| GlobalRegistrarWrapper | 0x49F3…0761 | ERC-721 deed minter, on-chain art, trait modules |
| RegistrarMarket | 0xD420…F016 | Non-custodial ETH marketplace (1% fee) |
| DeedTreasury | 0x52A3…2ca2 | Fee receiver, timelocked routing |
| CommitRevealRouter | 0xf7d9…9840 | Front-run-resistant registration router |
| HistoricAttestor | 0x39de…34Ff | Merkle-proven Gold 2015 / Silver 2016 attestations |
| HistoricTraitModule | 0x6752…e839 | Gold/Silver era traits on tokenURI |
| RegistrarGenerativeModule | 0xe232…f4e | Clifford attractor animated seal (fully on-chain) |
| DeedCapabilityRegistry | 0xD153…169A | On-chain ecosystem module directory |
| DeedTextRecords | 0x9d8A…97c0 | ENS-standard text records (avatar, url, twitter…) |
| DeedPermissionsModule | 0xd2C6…Cd10 | Per-deed role delegation (ENSv2-inspired) |
| ENS Resolver v1 | 0x6c5d…9628 | ENSIP-10 wildcard resolver (addr only) |
| ENS Resolver v2 | 0xbac2…03C5 | Active resolver — reads text records, multi-coin |
| Governor (Safe 3-of-5) | 0x4F80…185b | Multisig — controls write-once module slots |
Compiler: solc 0.8.26 · optimizer 200 runs · evmVersion cancun · OZ v5 · no viaIR · deployed 2026-06-18
Hosting a locked-down instance
The app is open by design — any addresses can be pasted, which is right for the public
build but risky for a hosted product: because config lives in the URL hash, a malicious shared
link could pre-fill a counterfeit wrapper/market to phish approvals. (Editing your own settings
only affects you; the danger is a crafted link given to someone else.) To host safely, fill the
PINNED block near the top of the script with your canonical addresses. Pinned fields are
used as-is, protected from URL-hash overrides, and shown read-only in Settings with a 🔒 notice.
Leave a field empty to keep it user-editable; the read RPC is never locked (read-only, harmless). Pin
every core contract you use — an unpinned field stays overridable. This guards the client only;
the contracts enforce their own security on-chain regardless, and no governor/admin function is
exposed in the UI at all — every app action is an ownership-gated call on your own name.
Caveats
- Wallet flows are verified by code review and unit tests (codec round-trips, classifier, merkle
leaf parity with Solidity, compilation) but not against live mainnet —
exercise the reserve front-run path and a full wrap cycle on an Anvil/Tenderly fork first.
- Wrapping transfers registrar custody to the wrapper, so
name(address) reverse
lookup points at the wrapper while wrapped.
User Guide
Everything here works against mainnet. The app opens in read-only mode through a public
RPC — lookups, scans, and the Historic index need no wallet. Connect a wallet (MetaMask or any
injected provider, mainnet) only when you want to transact.
Getting started
- One-paste setup via the registry. If the project has deployed a
DeedCapabilityRegistry, paste its address in ⚙ Settings and click
Auto-fill from registry — the app reads the canonical wrapper, market, router, and resolver
straight from the on-chain directory, so you don't paste each by hand. Review and Save. You can
still set any address manually.
- Connect wallet (top right) — required for register / manage / wrap / trade.
- ⚙ Settings — pick a Read RPC from three public endpoints (PublicNode is the
default; switch to LlamaNodes or dRPC if one is down or throttling) or choose Custom for
your own / archive-grade node; plus the registrar address (GlobalRegistrar by default; paste the
Linagee address to drive that contract instead), and wrapper and market addresses once deployed.
- Your config and watchlist live in the URL. Bookmark it to keep your setup. Share the URL
to share the exact configuration. There is nothing to "log into" and nothing stored in the browser.
Lookup
- Type a name (up to 32 bytes of UTF-8 — emoji count) or paste an
0x… address for a
reverse primary-name lookup.
- The deed plate renders the raw bytes32 storage word: 32 cells, lit = data, hollow = zero
padding. Short names light few cells; 32-byte names fill the plate.
- Live art preview — see it before you register. Below the plate, a generative seal
renders the name's real, final on-chain art — the exact piece it will carry as a deed. Because
the art is computed purely from the name (no random "reveal"), it shows even for unregistered
names: you can browse names by their art and pick the one you love before spending any gas.
What you see is what you get — the only thing that fills in after registering is the historic
RANK #N line, which is earned by claiming the name.
- You'll see status (unregistered / registered / wrapped), owner,
resolved address, content hash, sub-registrar, token + listing if wrapped — and the
first-registration line: exact UTC timestamp and block of the earliest
Changed() event, with a ✦ Gold 2015 or
✦ Silver 2016 badge when it qualifies, plus rank once the Historic
index is built.
- How your name displays — every form. One name, shown different ways depending on
where it appears, all the same ownership:
- Native — just
gold, no suffix. The real identity on the 2015 contract;
needs no .eth. Everything else points back to this.
- Bridged, lowercase —
gold → gold.globalregistrar.eth, so it
resolves in any ENS wallet (e.g. MetaMask's send field).
- Bridged, hex escape — because ENS lowercases input, a capitalized/odd-byte name can't
safely use the plain form (
Gold and gold would collide). The hex escape
encodes exact bytes: Gold → 0x476f6c64.globalregistrar.eth. The app
shows the right form for you.
- Deed (if wrapped) — the same name also as an ERC-721 token for marketplaces; unwrap and
you're back to forms 1–3 unchanged.
This is a read-only resolution convenience; ownership always lives on the 2015 contract.
- Like Basenames, but fee-immune. Coinbase's Basenames (
name.base.eth) mints a
real ENS subname per holder — your identity lives in ENS. We mint nothing in ENS:
gold.globalregistrar.eth is resolved on the fly (wildcard) by reading the 2015 contract,
so there's no per-name ENS entry, no per-name fee ENS could ever charge, and if ENS changed or
vanished your name still resolves natively through this app. The .eth form is a window
onto your name, never the deed. See ENS_INTEGRATION.md for the full comparison and future-fee
analysis.
- Reverse lookups are forward-verified. Anyone can plant a "primary" name on any address
on this contract (only the name's owner is checked, not the address). So a reverse result is
labeled ✓ verified only when the name resolves back to the address;
otherwise ⚠ unverified — treat those as untrusted.
- Case matters. If your query contains capitals, a "Case variant" row shows the lowercase
record's status with a one-click jump — they are different names on this contract.
- Names owned by an address. Paste an
0x… address and, alongside its reverse
primary, a panel offers to find every name that address currently owns. The 2015 contract has
no owner→names index, so this is derived from Changed() event logs and then re-checked
against live owner() — so transfers are reflected correctly (a name transferred away
won't show; one received will). Two ranges: 2015–2016 (fast, reuses the historic index if
built) or full history → latest (complete but a much longer crawl). The result always states
which range produced it, so an empty result is never mistaken for "owns nothing" when it really
means "owns nothing in that range." Wide scans want an archive-grade RPC.
- Faster repeat scans (cache + delta). The set of names from
Changed() logs is
immutable history, so it's cached for the session and reused — repeat scans only fetch new
blocks (a delta), not all of history. Save index file downloads the cache as JSON
(gr-name-index-v1) so you can Load it next session and skip scanning entirely;
Update to latest (delta) extends a loaded index to the current block. The scanner itself runs
several getLogs in parallel with an adaptive, rate-limit-aware back-off: it
automatically shrinks ranges and cools down when an RPC throttles (429 / "rate limit"), so it speeds
up on archive endpoints yet degrades gracefully on free public ones. Coverage is gap-free by
construction regardless of how ranges get split.
- Hosted warm-start (optional). Because the 2015–2016 historic set is a closed, immutable
list, an operator can scan it once and serve the resulting
gr-name-index.json
beside the app on the same host. On startup the app fetches it (toggle auto-load hosted index,
on by default) and every visitor starts warm — no per-user cold scan, so RPC/archive usage stays
near zero no matter how many holders visit. To make the file: in Market → ✦ Historic, click
Build historic index, then Save name-index (for hosting) — it downloads exactly
gr-name-index.json (a v2 file that warms both Lookup and the Market
ranked list, so neither tab needs to scan), ready to upload beside the app (no rename needed).
Build historic index stays available to regenerate on demand. Regenerate and
re-upload to extend coverage. Fails silently if absent (the app just scans on
demand), and is validated/registrar-guarded exactly like a manually loaded file.
Register
- Check availability first; the Reserve button unlocks only for free names. Registration
costs gas only — the contract charges nothing.
- After the transaction confirms, the app re-reads ownership. This matters: the 2015
contract silently no-ops instead of reverting, so a "successful" tx can still mean someone
front-ran you. The app tells you plainly: Reserved ✓ or
Front-run ✗.
- Capitalized names get a case-sensitivity note showing the lowercase twin's status before you
commit.
- Batch reserve: paste up to 20 names, one per line — each is checked, reserved, and
verified individually.
- Register & wrap: after a successful reserve, an inline panel offers to continue
straight into wrapping the new name into a tradeable ERC-721 deed — no tab switch. It's still three
on-chain transactions (createWrapper → transfer → wrap), the minimum the 2015 contract allows, each
verified before the next unlocks and fully non-custodial. Dismiss it to keep the name unwrapped.
- Secure register (commit–reveal): with a CommitRevealRouter configured in Settings,
step 1 publishes only a hash of (name, salt, you) — bots watching the mempool learn nothing.
After the router's minimum block delay, step 2 reveals: the router reserves the name and hands it to
you atomically, verifying owner() at every step. Pending commitments (name + salt) persist in the URL
hash so the reveal survives a reload — don't share the URL while commitments are pending. Honest
limits: because the 2015 registrar is permissionless, the reveal tx itself can still be sniped by a
direct reserve(); the router then reverts with NameTaken rather than losing silently. For maximum
protection, switch your wallet to a private RPC (Flashbots Protect, rpc.flashbots.net) before
revealing.
Manage
- Load a record you own to transfer it, set its resolved address (optionally as
your primary, enabling reverse lookup), set a 32-byte content hash, or a
sub-registrar.
- Disown deletes the record permanently — the name returns to the
open pool and its current data is gone. The historic first-registration timestamp survives (it's
log history), but ownership does not.
- Text Records — below the core record controls, a Text Records panel lets you
attach ENS-standard metadata to any name you own: avatar, url, description, com.twitter,
com.github, email, notice, location, keywords, cross-chain addresses (_btc, _sol), or any custom
key. Records are stored in the standalone
DeedTextRecords contract, keyed to the
name's bytes32 — they survive unwrap/rewrap and appear in any ENS-compatible wallet once the
ENS Resolver v2 is active.
- Delegate Permissions (wrapped names only) — grant specific record-edit rights to
another address without transferring the deed. Choose which roles to delegate: Address, Content,
Sub-registrar, or Admin (which can sub-delegate but never escalate). Roles reset automatically
when the deed transfers — delegates can never outlive your ownership.
ENS Wallet Integration — what holders get
Your name connects to the ENS ecosystem via a wildcard resolver set on
globalregistrar.eth. This is how it differs from Basenames or ENS subnames:
nothing is registered per-name in ENS — the resolver reads the 2015 contract on the fly,
so every name works instantly, with no per-name ENS fee ever possible.
✓ Works today (once the resolver is set on globalregistrar.eth):
- Send ETH / tokens to
name.globalregistrar.eth — any ENS-compatible
wallet (MetaMask, Rainbow, Coinbase Wallet, most modern wallets) resolves the name to the
address set in your name's Resolution address field (Manage tab → setAddress). Set that
address first, then anyone can send to your name directly.
- Wallet shows your name instead of the hex address — in transaction history and
send flows, wallets that support ENS forward resolution display
name.globalregistrar.eth
instead of the raw 0x… address.
- Avatar and profile metadata — wallets and dApps that read ENS text records will
show your avatar, website, social handles, and description once you set them in the Text Records
panel (Manage tab). These resolve through the ENS Resolver v2.
- Cross-chain addresses — set a Bitcoin address via the
_btc text key,
or a Solana address via _sol. Wallets that query multi-coin resolution will find
them.
- Content hash — set a 32-byte content hash (Manage tab) to point your name at an
IPFS or IPNS site. ENS-aware browsers (Brave, MetaMask Flask) can resolve it.
- Capitalized names — ENS lowercases all labels, so
Gold would collide
with gold. The app gives you the hex escape form:
0x476f6c64.globalregistrar.eth — exact bytes, normalization-proof. Paste this
into any ENS wallet's send field and it resolves correctly.
⚠ Not supported / limitations:
- Wallet's displayed primary name — MetaMask shows "Your ENS name" based on the ENS
reverse standard (ENSIP-19). This requires a separate reverse record on the ENS reverse
registrar, which is deferred pending ENSIP-19 finalisation. Your name resolves correctly
forward (send ETH to it) but won't yet appear as your wallet's displayed primary name
in MetaMask's header. This will be added as a dApp button in a future update.
- ENS profile page —
app.ens.domains shows profiles for names that are
actual ENS registrations. Since your name uses wildcard resolution (not a real ENS subname),
it won't have an ENS profile page. Forward resolution in wallets works perfectly; the ENS app
UI is a separate concern.
- Subnames —
sub.name.globalregistrar.eth is not supported. The
resolver handles one level below the parent only.
- Plain capitalized names in wallets — typing
Gold.globalregistrar.eth
into most wallets won't work because ENS normalizes to lowercase. Use the hex escape form
shown in Lookup, or stick to lowercase names for the smoothest wallet experience.
How to set up forward resolution (the most useful feature):
- Go to Manage, load your name.
- In Resolution address (setAddress), enter the address you want the name to resolve
to — usually your own wallet address. Check primary if you want this to be your
reverse name on the 2015 registrar (separate from ENS primary, but used by this app).
- Click Set and confirm in your wallet.
- Once the ENS resolver is active on
globalregistrar.eth, anyone can send ETH
to yourname.globalregistrar.eth and it arrives at that address.
Text Records — ENS-standard metadata
The Text Records panel in the Manage tab lets you attach structured metadata to any
name you own. Records are stored in a standalone on-chain contract (DeedTextRecords)
and resolve through the ENS Resolver v2 — the same keys ENS wallets and dApps already understand.
- Standard keys and what they do:
| Key | What it sets |
avatar | Profile image URI (ipfs://, https://, or data:) — shown in wallets and dApps |
url | Website for this name |
description | Human-readable description (overrides the built-in default) |
com.twitter | Twitter/X handle (without @) |
com.github | GitHub username |
email | Contact email |
notice | Message to anyone looking up this name |
location | City / country |
keywords | Comma-separated tags |
_btc | Bitcoin address (resolves via ENS coinType 0) |
_sol | Solana address (resolves via ENS coinType 501) |
- Custom keys — select "custom key…" from the dropdown and type any key name.
Convention for app-specific keys:
app.yourapp.keyname.
- Records survive unwrap/rewrap — they're keyed to the name's bytes32, not the
token ID. Unwrapping and re-wrapping preserves all your text records.
- Clearing a record — set the value to empty and click Set, or use the ✕ button
next to any existing record.
- Auth — only the deed holder (or raw registrar owner if unwrapped, or a
permissions delegate with Address role) can set records. The contract enforces this — loading
a name you don't own shows the records read-only with no Set button active.
Wrap (ERC-721)
- Requires a deployed wrapper address in Settings. Three steps, each verified on-chain before the
next unlocks: Create (stake your claim) → Transfer (hand the name to the wrapper) →
Wrap (mint the token).
- Check status anytime — the stepper reads live chain state, so you can resume a half-done wrap
or abort it and reclaim the name.
- Unwrap burns the token and transfers the registrar record back to you.
- While wrapped, the token holder controls the name's address/content/sub-registrar through the
wrapper's proxy functions; trading happens on the Marketplace tab.
Why wrap (and why you might not). Wrapping is optional — a raw name works forever as-is.
Wrap when you want the name to be a standard ERC-721 deed: tradeable on any NFT marketplace,
displayable in NFT wallets, carrying on-chain art + traits, ERC-2981 royalties on
resale, and historic rank on its metadata. In return you accept that the wrapper holds the
raw name in custody while wrapped (you hold the deed; unwrap any time to get the exact name back),
a flat wrap fee + gas, and that records are set through the deed while wrapped. If you only want
to hold or resolve a name, you don't need to wrap.
Fees, in full. Registering costs gas only (the 2015 contract charges nothing).
Wrapping charges a flat wrap fee — shown live in the Wrap panel before you confirm, and it may
be zero — which goes to the project treasury. Unwrapping is gas only. On the Market, a sale pays
feeBps (capped at 5%) to the fee recipient, plus any ERC-2981 royalty the
deployment set (≤ 10%) on marketplaces that honor it. Listing, offers, and cancels are gas only. The
app never takes a cut, and no fee is hidden — the wrap fee is read straight from the contract.
Marketplace
- Storefront grid. Every wrapped deed with an active listing or offer appears as a card:
its mini deed-plate at rest, the live animated seal on hover, era/trait badges, and a
cyan LISTED or amber OFFER price badge. Gold/silver names get an era-tinted border.
- Stats header — listed count, names with offers, floor price, and total wrapped.
- Sort (price low→high / high→low, newest, era/rank) and filter (for-sale, has-offers,
gold, silver) plus a name search. Sort and filter span the whole set; results page 24 at a time
(prev / next appears once there are more than 24).
- Context-aware action on each card: Buy if it's for sale and you don't hold it;
Accept offer if you hold it and someone has made one; Manage if you hold it; otherwise
Details. The button loads the deed into the trade panel and, for Buy/Accept, kicks off the
transaction.
- Trade panel (right) — load any wrapped name to list (with optional expiry / private buyer),
cancel, buy, make/withdraw an escrowed ETH offer, or accept a standing offer. Buttons enable
only for the actions you're entitled to (e.g. Accept only when you hold the deed and an offer exists).
- Recent activity — real on-chain market events (listed, sold, offer, accepted, cancelled)
with name, price, and block. Populates once there's market history.
- Accepting an offer: find the name's card (it shows an OFFER badge) and click
Accept offer — or load it in the trade panel and use Accept standing offer. Either sells
the deed to the offerer at their escrowed price.
Collection
- Club filters (999, 10k, single-char, words, …) scan well-known name sets and show
availability at a glance.
- ✦ Historic — the ranked Gold 2015 / Silver 2016 registry (built from
Changed()
events in blocks 0–2,912,406), each name ranked by its first-registration moment.
- ◈ All Wrapped — every wrapped deed across all eras (including names registered after
2016), enumerated live from the ERC-721. This is the living, tradeable collection. It's fast and
needs no archive node (it reads current token supply, not historical logs). Wrapping a name
adds it here — that's how a name joins the Collection.
- After a bulk reserve, use Build / refresh index (or the "Refresh Collection" link the
reserve offers) to re-scan so new names appear.
Portfolio
- Auto-discovers what you own. The Portfolio shows your reverse primary name, every wrapped
deed you hold (via ERC-721 enumeration), and — with auto-scan on (default) — every
unwrapped name you own, discovered live from chain. No watchlist required; it reflects your
true on-chain holdings. Toggle auto-scan off to fall back to a lighter view (faster on mainnet).
- Each row offers the right next step: wrap → for an unwrapped name you own (it joins the
Collection), list / trade → for a wrapped deed.
- Watchlist (top-right) — paste any names you want to track even if you don't own them
(e.g. watching availability); they're verified by batch owner-scan and saved into the URL hash with
the rest of your config.
Collection & Marketplace
- Clubs are trait categories computed from the raw bytes: 999 Club (000–999), 10k Club,
Single Char, Two/Three Letter, ~300 Words, ~90 Emoji — or paste a custom set. Club generators are
lowercase by design; check capitalized variants via custom paste or Lookup.
- Each page of 120 scans current ownership in one Multicall3 round:
available · registered · wrapped — and
invalid for entries that don't fit in 32 bytes (shown dimmed with the
reason, never silently dropped). Custom pastes are deduped byte-exactly; case variants are kept and
flagged, because they're genuinely different names.
- Trading (needs wrapper + market addresses): load a wrapped name, then
Approve & list at a price, Buy a listing, or use escrowed offers — your
ETH is held by the market contract, auto-refunded if outbid, and the owner can
Accept with a minimum-amount front-run guard.
✦ Historic Registry — Gold 2015 / Silver 2016
- Build historic index scans every
Changed() event from block 0 through the
end of the most recent active era (currently 2,912,406, the last block of 2016). The earliest
event per name is its registration; names are ranked #1, #2, #3… by that moment. Expect a
few minutes on a public RPC — the scanner adaptively shrinks its block ranges when the RPC pushes
back. The index lives in memory for the session.
- Gold frame = first registered in 2015 (block ≤ 778,482);
silver = 2016 (≤ 2,912,406). Eras are driven by a single data-driven
table; a future Bronze 2017 tier is scaffolded but dormant, and would light up additively
(a new attestor + module in a fresh write-once slot) without altering existing Gold/Silver — the
closed historic sets are never diluted. Every card shows its rank badge and the exact
first-registration timestamp (resolved per page, cached).
- Case twins: a tag like "2 case twins" means other capitalizations of that name were also
registered historically — each is its own record with its own rank.
- Ranks and timestamps key off the first event ever — later transfers, updates, disowns,
and re-registrations never move them.
- Export attestation tree downloads
historic-attestations.json: the merkle
root plus a proof per name (self-verified before download). Deploy
HistoricAttestor with that root and the HistoricTraitModule over it, then
the governor links the module once via the wrapper's write-once setTraitModule(0, …)
slot. After that anyone can attest() a name — its token metadata instantly gains the
Historic / Rank / First Registered traits and the gold or silver SVG frame.
Generative Seal — live on-chain art
Every deed carries an optional generative seal: a self-contained, fully on-chain
animated artwork seeded by the name's own bytes. It appears below the deed plate in the
Lookup view, and renders identically on marketplaces that support animated media.
- What it is. A Clifford-attractor "medallion" drawn by a tiny HTML
<canvas> program stored on-chain. The program is a loop — so its byte size is
fixed no matter how dense the art, which is how it stays fully on-chain without the size blow-up
that dense SVG art would cause. The same name on the same collection always produces the same art.
- Unique to this collection. The art is seeded by the name and this collection's
contract address — so your "money" here renders a different attractor than "money" on any other
GlobalRegistrar deployment. The piece is genuinely yours, not something anyone can reproduce just
by knowing the name.
- What's engraved on it.
GLOBAL REGISTRAR across the top; the name in its
true case (so satoshi, Satoshi and SATOSHI each get a
distinct seal); the attractor signature (a · b · c · d — the actual Clifford
parameters that generated the image, a self-referential fingerprint); and a charset/case
line (e.g. 7 CHARS · LETTERS · LOWERCASE). Historic names also get a gold
◆ GOLD 2015 · RANK #N or silver ◆ SILVER 2016 · RANK #N line, with the
rank pulled from the real HistoricAttestor — never fabricated.
- Where it shows. The live animated seal renders on the marketplace item page
(via the token's
animation_url) and here in the app. In collection grid /
thumbnail views — which use the token's static image field — you'll instead see
an engraved certificate plate (branding, true-case name, charset/case, and rank), since the dense
animated art can't be represented in a small static image. Both faces are fully on-chain.
- How it reaches deeds. The art comes from a separate
RegistrarGenerativeModule,
linked to the wrapper through a write-once setAnimationModule slot — the same
immutability guarantee as the trait slots. The wrapper stays renderer-agnostic: if no module is
ever linked, deeds simply show the plate, unchanged. The link is defensive — a misbehaving module
can never break a deed's metadata.
Troubleshooting
- "History unavailable / log queries refused" — the RPC limits
eth_getLogs.
Lookup falls back to a chunked 2015–2016 scan automatically; for the full index or stubborn
endpoints, set an archive-grade RPC in ⚙ Settings.
- Historic scan is slow — normal on free public RPCs (it's 2.9M blocks of logs). A paid or
self-hosted node finishes much faster. Progress shows in the scan bar.
- Wallet buttons do nothing — no injected provider in this context (e.g. a sandboxed
preview). Open the file in a normal browser tab with MetaMask on mainnet.
- "Front-run ✗" after registering — someone else's reserve landed first; the 2015 contract
doesn't revert, it just ignores you. Pick another name. This is the contract's behavior, not a bug.
- A name shows as a hex string — its bytes aren't valid UTF-8; the app shows the raw bytes
rather than guessing.
Safety, transparency & DYOR
These are permanent, byte-exact records on an
eleven-year-old immutable contract. Here is exactly what this app can prevent, what it can only
detect and report, and what it fundamentally cannot protect against. Transparency is the protection
— the contracts are immutable and this app is open and self-contained so you can verify everything
yourself.
- Fully prevented (hard validation): empty names and names over 32 bytes are rejected
before any transaction. Note a "character" can be 2–4 bytes (emoji, many non-Latin scripts), so a
short-looking name can still exceed 32 bytes.
- Detected & reported, but not preventable: duplicate
registration / front-running. The contract is first-come and silently no-ops on a taken
name, so the app checks availability and re-reads owner() after every reserve to tell you
the truth (Reserved ✓ / Front-run ✗). It cannot stop a front-run on a public mempool — the
mitigation is commit–reveal (Secure register), and even then the reveal tx can be sniped
(it reverts honestly rather than losing silently). Reveal through a private RPC (Flashbots Protect)
for maximum protection.
- Cannot be prevented — be mindful: the 2015 contract does no normalization, so
every distinct byte sequence is a distinct name. Case variants (
Gold ≠
gold), whitespace variants (a trailing space), invisible characters
(zero-width / control), and lookalike homoglyphs (Cyrillic о vs Latin
o) are all separate, valid names that can look identical to a human. This is a real
phishing surface. The Register tab warns (without blocking) when a name contains
risky characters, but you must treat a name's bytes, not its appearance, as its identity.
- Irreversible & unrecoverable: registration is permanent. There is no expiry to
forget, but also no undo, no support desk, and no way to recover a name sent to a wrong address.
disown deletes the record permanently; transfer is final. Read every
confirmation.
- Do your own research (DYOR). This app is a faithful, non-custodial interface — it does
not vet names, owners, listings, or counterparties, and cannot. Before you register, buy, or trust
a name: verify its exact bytes (Lookup shows them), verify ownership and history
on-chain rather than trusting appearance or a listing's claims, forward-verify any reverse
name (see Lookup — anyone can plant a reverse record on any address), and on the secondary
market confirm you're buying the precise byte-form you intend. Transactions are irreversible.
- Operational: never trust a transaction receipt alone on this contract — trust the
re-read (the app does this everywhere; keep it in mind via other tools). Test wrap and trading
flows on a fork (Anvil / Tenderly) before pointing real ETH at freshly deployed wrapper/market
addresses.
Deployed Contracts — Ethereum Mainnet
All contracts are verified on Etherscan and immutable. Click any address to inspect
the source code and transaction history.
| Contract | Address | What it does |
| GlobalRegistrar (2015) | 0x3399…a70C | The original 2015 Frontier name contract — all names live here |
| Wrapper (ERC-721) | 0x49F3…0761 | Mints deeds, holds custody, generates on-chain art |
| Marketplace | 0xD420…F016 | Buy, sell, and make offers on wrapped deeds (1% fee) |
| Treasury | 0x52A3…2ca2 | Receives all fees — timelocked routing, no admin backdoor |
| Commit-Reveal Router | 0xf7d9…9840 | Front-run-resistant two-step registration |
| Historic Attestor | 0x39de…34Ff | Proves Gold 2015 / Silver 2016 provenance on-chain (merkle) |
| Text Records | 0x9d8A…97c0 | Set avatar, url, twitter, and other ENS metadata on your name |
| ENS Resolver v2 | 0xbac2…03C5 | Makes name.globalregistrar.eth resolve in ENS wallets |
| Governor (Safe 3-of-5) | 0x4F80…185b | Multisig that controls module slots — requires 3 of 5 signers |
All contracts immutable · verified on Etherscan · deployed 2026-06-18 · no admin backdoors · no upgrade proxies