# Explorer integration
URL: https://docs.arcns.io/explorer/

> 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.

<LlmActions />

## 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.

<Callout title="Rule" type="info">
  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.
</Callout>

## 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](/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](https://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.

```ts
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_call`s. 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:

```tsx
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>
  );
}
```

## Flow 2: the search bar

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.

| Input                       | What to query                         | What to show                                                                                                |
| --------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `alice` (no shape)          | `@alice`, `alice.arc`, `alice.circle` | three rows, one per namespace, each with its own result or "not registered"                                 |
| `@alice`                    | `resolve("@alice")`                   | one row, namespace `@handle`, registry short-address                                                        |
| `alice.arc`, `alice.circle` | `resolveAll("alice.arc")`             | one row per registry that holds the name, each labelled with `product` and `registry`; nothing pre-selected |
| `@alice.arc`                | nothing; 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:

| Field             | Source                                  | Note                                                                                                                |
| ----------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| namespace         | `Resolved.namespace`                    | `@handle`, `.arc` or `.circle`, always next to the name                                                             |
| registry, product | `Resolved.registry`, `Resolved.product` | which contract answered and whose it is                                                                             |
| controller        | `Resolved.controller`                   | who holds the name; label it "controlled by", never as a payment address                                            |
| epoch             | `Resolved.epoch`                        | the ownership epoch; records from an older epoch do not exist                                                       |
| records           | `records(name)`                         | one row per coin type with `address` and `verification`; render `unverified` as a warning, not as a fact            |
| primary           | `reverse(controller)`                   | show "primary name" only when the reverse points back at this very name                                             |
| TLD status        | `Resolved.tld`                          | `active`, `registrations-paused`, `sunset` (with `sunsetAt`) or `retired`; a sunset banner on the page              |
| token             | `GET /nft/:name`                        | the ERC-721 card and attributes (`Namespace`, `Handle Type`, `Tokenized`, `Rarity`, `Bucket`, `Length`, `Registry`) |
| text records      | `text(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](/contracts/)); the ArcNS-specific `ArcNSResolver` ABI is checked into `contracts/abi/` in the repository.

| Priority | Event                               | Contract                                             | Drives                                                            |
| -------- | ----------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------- |
| 1        | `Transfer`                          | `HandleRegistry`, each `BaseRegistrarImplementation` | ownership, name lists per address, epoch invalidation             |
| 1        | `AddrChanged`, `AddressChanged`     | `ArcNSResolver`                                      | address records per coin type                                     |
| 1        | `NameChanged`, `ReverseClaimed`     | `ArcNSResolver`, `ReverseRegistrar`                  | primary names; invalidate any reverse cache                       |
| 2        | `NewOwner`                          | `ENSRegistry`                                        | subnames and registry-level ownership                             |
| 2        | `AddressVerified`, `VersionChanged` | `ArcNSResolver`                                      | the `verified` flag per coin type; epoch bumps that clear records |
| 3        | `TextChanged`                       | `ArcNSResolver`                                      | text 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 index                               | Must be verified on chain before display                         |
| ----------------------------------------------------- | ---------------------------------------------------------------- |
| suggestion lists while typing                         | the row the user opens                                           |
| history: past owners, past records, registration date | the 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 held             | the 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 show                                            | Show instead                                               |
| ------------------------------------------------------ | ---------------------------------------------------------- |
| a bare address for a name                              | name, namespace badge, registry short-address, address     |
| an address with a name that no longer forward-confirms | the 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 confirmed                      | the record with an explicit `unverified` warning           |
| one registry's answer when two disagree                | both answers, labelled                                     |
| a name in a sunset TLD without notice                  | the name with the sunset banner and `sunsetAt`             |
| an indexed value as current                            | the chain read, or the value labelled with its block       |

## Failure modes and edge cases

| Situation                         | What the SDK / API says                                             | What to render                                              |
| --------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------- |
| RPC down or head stale            | `rpc-error` / `502`                                                 | "could not read the chain", a retry; never "not registered" |
| name not registered               | `not-found` / `404` with `registriesConsulted`                      | "not registered in any pinned registry"                     |
| handle burned                     | `not-found`, `reason: "nft-burned"`                                 | "burned"; never the last owner                              |
| registries disagree               | `conflict` / `409` with `candidates`                                | both answers, labelled, none chosen                         |
| mixed shape `@alice.arc`          | `ambiguous` / `409`                                                 | two links: `@alice` and `alice.arc`                         |
| primary stale                     | `reverse()` is `null` / `404 primary is stale`                      | the truncated address                                       |
| TLD in sunset                     | `tld.status === "sunset"`, `sunsetAt`                               | the page with a banner                                      |
| TLD retired or past `sunsetAt`    | `tld-retired` / `410`                                               | a retired-name page; never a cached address                 |
| CCIP-read requested by a resolver | `offchain-lookup` / `502`                                           | treat as an error, never as data                            |
| subname (`pay.alice.arc`)         | resolves like any name; `/nft` answers `404 subnames have no token` | label 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: [support@arcns.io](mailto:support@arcns.io).

## Questions

Questions: [support@arcns.io](mailto:support@arcns.io).
