Skip to content
ArcNS docs
DocsExplorer 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 and both registries, 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.
  • REST at https://api.arcns.io: the same reads over HTTP with cache-control: public, max-age=5, open CORS, 20 requests per second per client. 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 2 to 4 eth_calls. 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 for a TLD every pinned registry, 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 per registry that holds the name, each labelled with product and registry; nothing pre-selected
@alice.arcnothing; the SDK throws ambiguous"@alice and alice.arc are different names" with a link to each

resolveAll() returns every registry that holds the name and has a record, ours first, without throwing on disagreement. 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 ENS events. The exact ABIs ship with the deployment file (see Contracts); the ArcNS-specific ArcNSResolver ABI is checked into contracts/abi/ in the repository.

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 and at the pinned peer registry's contracts if you want its .arc / .circle names in search; label those rows with their registry, as the SDK does.

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 and both registries side by side; 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
one registry's answer when two disagreeboth answers, labelled
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 with registriesConsulted"not registered in any pinned registry"
handle burnednot-found, reason: "nft-burned""burned"; never the last owner
registries disagreeconflict / 409 with candidatesboth answers, labelled, none chosen
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].