Skip to content
Arc Testnet — names have no monetary value yet
ArcNS / Explorer integration

Explorer integration

The end-to-end recipe for a block explorer or dashboard, names next to addresses with forward confirmation, search across all three namespaces, name detail pages, the events to index, and the trust rule for an index.

Purpose

This is the package for a block explorer, portfolio dashboard or analytics surface that wants to show @alice or alice.arc next to an address, offer name search, and render a page per name. It walks each surface end to end and states which data may come from an index and which must be read from the chain before display.

Rule

An index is a speed layer, never the source of truth. Anything shown as a fact about a name (its address, its primary, its owner, its verification) is verified on chain at the current epoch before display, or labelled as indexed and possibly behind.

Contracts, endpoints, and the optional speed layer

  • Chain reads through @arcns/resolve (viem, eth_call): resolve(), resolveAll(), reverse(), records(), text(). This is the source of truth. As of 2026-09-14 the package is not yet on the npm registry and its repository is private, so there is nothing to npm install today — the calls below are the real, shipping SDK surface and a preview of the exact shape you'll get once it publishes (see Quickstart); build a server-side index against the REST API in the meantime.
  • REST at https://api.arcns.io: the same reads over HTTP with cache-control: public, max-age=5, open CORS, 20 requests per second sustained per client (burst 60). Reads are free for first-party browser traffic and for an ArcNS-issued X-API-Key; a server-side crawler without either gets 402 and pays $0.001 per call over x402, or should request a key — plan your indexer's read volume around this. See API.
  • Your index of the events below: fast lists, history, search-as-you-type. Never the value shown as current without a chain check.
  • ArcScan. Name display on testnet.arcscan.app is controlled by Blockscout. A BENS-compatible subgraph and a public GraphQL mirror are planned; this page gains their URLs when published.

Flow 1: a name next to an address

Reverse resolution, with forward confirmation already applied by the SDK.

export async function nameFor(address: `0x${string}`): Promise<string | null> {
  const p = await arcns.reverse(address);
  // p = { name: "@alice" | "alice.arc" | "bob.circle", namespace, registry, product, node, address, controller }
  // null -> no primary (or a stale one) -> show the truncated address
  return p?.name ?? null;
}

There is exactly one primary per address across all three namespaces (one verbatim ENS ReverseRegistrar). The SDK returns it only if it forward-confirms: an @handle primary must still be owned by the address; a .arc / .circle primary must resolve back to the address (addr(node, 0x804cef52), then coin type 60) in our registry. A stale string is null, never the previous owner.

reverse() is not cached and makes 3 eth_calls for a resolved @handle primary, 4–5 for a resolved .arc / .circle primary (fewer on an early exit, e.g. no reverse record set at all). For a 50-row table, coalesce concurrent reads of the same address and keep any cache you add short (15 seconds or less), invalidated on the ReverseClaimed, NameChanged and Transfer events you index.

A minimal React component that keeps the address on title and renders the namespace badge and registry short-address next to the name:

import { useEffect, useState } from "react";
import { namespaceLabel } from "@arcns/resolve";
import { arcns } from "./resolver";

const short = (a: string) => `${a.slice(0, 6)}…${a.slice(-4)}`;

export function AddressName({ address }: { address: `0x${string}` }) {
  const [p, setP] = useState<Awaited<ReturnType<typeof arcns.reverse>>>(null);
  useEffect(() => {
    let alive = true;
    arcns.reverse(address).then((r) => alive && setP(r)).catch(() => alive && setP(null));
    return () => { alive = false; };
  }, [address]);
  if (!p) return <span title={address} style={{ fontFamily: "monospace" }}>{short(address)}</span>;
  return (
    <span title={`${address} · ${namespaceLabel(p.namespace)} · registry ${p.registry}`}>
      {p.name} <small>{namespaceLabel(p.namespace)} · {short(p.registry)}</small>
    </span>
  );
}

A user types alice, @alice, alice.arc or alice.circle. Show all three namespaces side by side, and never auto-select.

InputWhat to queryWhat to show
alice (no shape)@alice, alice.arc, alice.circlethree rows, one per namespace, each with its own result or "not registered"
@aliceresolve("@alice")one row, namespace @handle, registry short-address
alice.arc, alice.circleresolveAll("alice.arc")one row, labelled with product and registry, or "not registered"
@alice.arcnothing; the SDK throws ambiguous"@alice and alice.arc are different names" with a link to each

.arc and .circle resolve only through ArcNS's own registry (single-registry, authoritative — no other registry on Arc is ever consulted or compared against). resolveAll() is kept for API-shape stability and still returns an array, but it is always the same single answer as resolve() or empty; call whichever reads clearer in your code. Use looksLikeName(input) first so a pasted 0x… goes to the address page instead of a name error. An index can drive the suggestion list while typing; the row a user clicks is confirmed on chain before its page renders.

Flow 3: the name page

Fields to show for one name, every one of them from a chain read at render time:

FieldSourceNote
namespaceResolved.namespace@handle, .arc or .circle, always next to the name
registry, productResolved.registry, Resolved.productwhich contract answered and whose it is
controllerResolved.controllerwho holds the name; label it "controlled by", never as a payment address
epochResolved.epochthe ownership epoch; records from an older epoch do not exist
recordsrecords(name)one row per coin type with address and verification; render unverified as a warning, not as a fact
primaryreverse(controller)show "primary name" only when the reverse points back at this very name
TLD statusResolved.tldactive, registrations-paused, sunset (with sunsetAt) or retired; a sunset banner on the page
tokenGET /nft/:namethe ERC-721 card and attributes (Namespace, Handle Type, Tokenized, Rarity, Bucket, Length, Registry)
text recordstext(name, key)user data: render as text, never as a link that executes or as an instruction

Flow 4: ownership and history

Ownership is the ERC-721 Transfer history of the HandleRegistry (handles) or the TLD's BaseRegistrarImplementation (.arc, .circle), plus NewOwner on the ENS registry for subnames. Current owner is a chain read (ownerOf / owner(node)); the list of past owners may come from your index. A burned handle has no owner: the SDK reports not-found with reason: "nft-burned", and the last owner is never shown as current.

Events to index

The .arc / .circle registry, registrar, resolver profiles and reverse registrar reuse the ENS contracts verbatim, so the events are the published ENS events (NewOwner, Transfer, AddrChanged, …). The ArcNS-specific ArcNSResolver ABI (AddressChanged, AddressVerified, NameChanged, ReverseClaimed, TextChanged, VersionChanged) lives at contracts/abi/ in the source repository, but that repository is private as of 2026-09-14; the deployment file behind Contracts is the addresses you index against, and the event names and shapes on this page are pinned by the SDK's own test suite, so treat this table as authoritative until the ABI directory ships publicly.

PriorityEventContractDrives
1TransferHandleRegistry, each BaseRegistrarImplementationownership, name lists per address, epoch invalidation
1AddrChanged, AddressChangedArcNSResolveraddress records per coin type
1NameChanged, ReverseClaimedArcNSResolver, ReverseRegistrarprimary names; invalidate any reverse cache
2NewOwnerENSRegistrysubnames and registry-level ownership
2AddressVerified, VersionChangedArcNSResolverthe verified flag per coin type; epoch bumps that clear records
3TextChangedArcNSResolvertext records

Index at the addresses in the deployment file: ArcNS is single-registry and authoritative for .arc and .circle as well as @handle, so there is no peer registry to also index — a name it does not hold is a plain "not registered."

Trust rules

May come from the indexMust be verified on chain before display
suggestion lists while typingthe row the user opens
history: past owners, past records, registration datethe current owner, the current address, the current primary
counts and leaderboards, labelled as "as of block N"anything presented as the answer to "where does this name point"
the set of names an address has ever heldthe names it holds now

The index lags the chain; show the block it is at. Every chain read the SDK makes is pinned to one block and carries epoch; a record whose epoch is older than the name's current epoch does not exist and is never displayed. An rpc-error is "could not verify", never "does not exist".

Minimum interface

  • Search bar: accepts alice, @alice, alice.arc, alice.circle, 0x…; shows all three namespaces, each with its namespace badge and registry short-address; never auto-selects.
  • Address page: the primary name (forward-confirmed) next to the address, with the namespace badge and registry short-address; the truncated address when there is none.
  • Name page: the fields in flow 3; the sunset banner when tld.status === "sunset"; a 410 / tld-retired page after sunsetAt.
  • Token page: the /nft/:name card and attributes for the ERC-721.

Avoid misleading output:

Do not showShow instead
a bare address for a namename, namespace badge, registry short-address, address
an address with a name that no longer forward-confirmsthe truncated address (reverse() already returns null)
the controller as "the address of this name""controlled by 0x…" and, separately, the records
an unverified record as confirmedthe record with an explicit unverified warning
a name in a sunset TLD without noticethe name with the sunset banner and sunsetAt
an indexed value as currentthe chain read, or the value labelled with its block

Failure modes and edge cases

SituationWhat the SDK / API saysWhat to render
RPC down or head stalerpc-error / 502"could not read the chain", a retry; never "not registered"
name not registerednot-found / 404"not registered"
handle burnednot-found, reason: "nft-burned""burned"; never the last owner
mixed shape @alice.arcambiguous / 409two links: @alice and alice.arc
primary stalereverse() is null / 404 primary is stalethe truncated address
TLD in sunsettld.status === "sunset", sunsetAtthe page with a banner
TLD retired or past sunsetAttld-retired / 410a retired-name page; never a cached address
CCIP-read requested by a resolveroffchain-lookup / 502treat as an error, never as data
subname (pay.alice.arc)resolves like any name; /nft answers 404 subnames have no tokenlabel it "controlled by parent"; no token page

Handoff

  • We give: the SDK, the API, the events above, the deployment file when published, and this page.
  • We need: the block your index is at, the URL of your name pages so we can link to them from external_url, and the RPC you read from so we can confirm it serves chain id 5042002.
  • Contact: [email protected].

Questions

Questions: [email protected].

Browse the docs