# Wallet integration
URL: https://docs.arcns.io/wallet/

> The end-to-end recipe for resolving an ArcNS name in a wallet's recipient field, from "user types alice.arc" to "send enabled", with every failure mode, its user-facing message, and whether it blocks the send.

<LlmActions />

## Purpose

This is the package for a wallet, payment app or agent that wants to accept `@alice`, `alice.arc` or `alice.circle` where it accepts a `0x…` address today. It walks the recipient field end to end: what to recognise, what to resolve, what to show, and exactly when "Send" may be enabled.

Wallet-native support means a user pastes a name instead of an address and the wallet does the resolution itself, on chain, with nothing to trust but the RPC. What exists today: the `@arcns/resolve` SDK, the read-only API at `api.arcns.io`, and the contracts (addresses pending, see [Contracts](/contracts/)). What the wallet consumes is one function, `resolve()`, and the fields it returns.

<Callout title="Rule" type="info">
  A name is never just an address. Show the **namespace**, the **registry** and the **verification** status before enabling "Send". Refuse a mixed shape; refuse to pick between registries; never fall back from the controller to a payment address. Everything below is that rule, applied.
</Callout>

## Required capabilities

| Capability                                    | How                                                                                                                                                                                           |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Recognise input                               | `looksLikeName(input)`: a `@` prefix or a known TLD suffix. Anything else is passed through as a raw address, so a pasted `0x…` never shows a name error.                                     |
| Normalise safely                              | Nothing to do beyond what the SDK does: labels are ASCII `a-z0-9-`, 1 to 32 bytes, no edge or double hyphen, not all digits, lower-cased. No Unicode, no emoji, so no homograph can be typed. |
| Resolve name to address                       | `arcns.resolve(input, { chain })`, which reads the chain over `eth_call` and consults every pinned registry for a TLD.                                                                        |
| Verify address to primary (forward-confirmed) | `arcns.reverse(address)` for the in-wallet "you are sending to alice.arc" display; the SDK returns a primary only when it forward-confirms.                                                   |
| Handle no-result and mismatch safely          | The typed `ResolveError.code` switch below: `not-found` and `no-record-for-chain` are explicit; `conflict` shows every candidate and never auto-selects.                                      |

## Required contracts, endpoints and ABI

Direct RPC through the SDK needs no ABI work on your side: `@arcns/resolve` carries the ABIs and reads `HandleRegistry`, the verbatim ENS registry, the `ArcNSResolver` (`addr(node, coinType)`, `verified(node, coinType)`, `recordVersions`), the `UniversalResolver` and the `ReverseRegistrar`. The addresses arrive in the package with the testnet deployment file; until then pass a `deployment`. The REST route is `GET https://api.arcns.io/resolve/:name` with the same fields under slightly different names (`owner` for the controller, `addr.verified` for verification); its full contract is on the [API](/api/) page.

## The flow, end to end

**1. The user types `alice.arc` in the recipient field.** `looksLikeName("alice.arc")` is true, so the wallet attempts resolution. While the label is still invalid (`invalid-domain`, `reason: "HyphenAtEdge"` for `alice-`), the field shows nothing: the user is still typing.

**2. Resolve, with the chain the send is on.**

```ts
import { ResolveError, namespaceLabel } from "@arcns/resolve";

export async function resolveRecipient(input: string, chain: "ARC" | "ETH" | "BTC" | "SOL" = "ARC") {
  try {
    const r = await arcns.resolve(input, { chain });
    // r = { name, namespace, registry, product, chainId, epoch, controller,
    //       coinType, address, verification, tld? }
    // SHOW ALL OF THESE before enabling "Send":
    //   name + namespaceLabel(r.namespace)      -> "alice.arc  ·  .arc"
    //   short(r.registry) + r.product            -> "registry 0xc3…3a01 (arcns)"
    //   r.verification                           -> "verified" | "unverified" (warn)
    //   r.tld?.status === "sunset"               -> banner: this TLD sunsets on r.tld.sunsetAt
    return r;
  } catch (e) {
    if (!(e instanceof ResolveError)) throw e;
    switch (e.code) {
      case "unrecognized":        return null;                                   // probably a raw address — pass it through
      case "invalid-handle":
      case "invalid-domain":      return null;                                   // still typing; e.reason names the rule
      case "ambiguous":           throw new Error("@name and name.arc are different names — type one of them");
      case "not-found":           throw new Error(`${input} is not registered`);
      case "no-record-for-chain": throw new Error(`${input} has no ${chain} address`);
      case "conflict":            return chooseRegistry(e.candidates!);          // never auto-pick
      case "tld-retired":         throw new Error(`${input}: this TLD has been retired`);
      case "offchain-lookup":
      case "rpc-error":           throw new Error("Couldn't read the chain — try again");
      default:                    throw e;
    }
  }
}
```

**3. Render the answer.** The display rule for every resolution and for the send confirmation: the namespace badge (`@`, `.arc` or `.circle`), the full canonical name, and the registry short-address, with `verified` shown only for a proof-backed record. For a search or a picker, show all three namespaces (and both `.arc` / `.circle` registries) side by side and never auto-select. The address itself stays visible (for example on hover or in the confirmation), because the name is a label for it, not a replacement.

**4. Verification.** `verification === "verified"` means the owner proved control of that address for that coin type on chain. `unverified` means a record exists but nobody proved it: show a warning the user has to pass through. Details on [Verified records](/verified/).

**5. Sunset banner.** A TLD can be put into sunset. Names in a sunset TLD keep resolving until `sunsetAt`; after that every read is `tld-retired` (`410` on the API), never a stale address and never `not-found`. When `r.tld?.status === "sunset"`, show a banner ("this TLD sunsets on …") on the resolution and on the confirmation; the send itself may proceed.

**6. Send enabled.** Only when: a single labelled answer exists, its `chainId` and `coinType` match the send, the namespace, registry and verification are on screen, and the user has passed any warning. The controller is never a fallback: a name whose owner has not set a record for this chain is `no-record-for-chain`, and "Send" stays disabled.

**7. Confirmation.** Repeat name, namespace badge and registry short-address on the confirmation sheet, next to the address the transaction will carry.

## Failure modes

| `code`                             | User-facing message                                                                        | Blocks send?                         |
| ---------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------ |
| `unrecognized`                     | none: pass the input through as a raw address                                              | no (it is not a name)                |
| `invalid-handle`, `invalid-domain` | none while typing; on submit, the `reason` (for example "no hyphen at the edge of a name") | yes                                  |
| `ambiguous`                        | "@name and name.arc are different names — type one of them"                                | yes                                  |
| `not-found`                        | "alice.arc is not registered"                                                              | yes                                  |
| `no-record-for-chain`              | "alice.arc has no ETH address"                                                             | yes                                  |
| `conflict`                         | a picker listing every candidate with its product and registry; nothing selected           | yes, until the user chooses          |
| `tld-retired`                      | "alice.circle: this TLD has been retired"                                                  | yes                                  |
| `tld-sunset` (write builders only) | "registrations for .circle are closed"; the name still resolves for sends until `sunsetAt` | n/a for reads                        |
| `unknown-registry`                 | none for users; a configuration error: an unpinned registry was passed without opt-in      | yes                                  |
| `offchain-lookup`, `rpc-error`     | "Couldn't read the chain — try again"                                                      | yes; never shown as "not registered" |
| `not-configured`                   | none for users; a configuration error: no deployment for this chain                        | yes                                  |
| `verification === "unverified"`    | "This address has not been verified by the name's owner"                                   | no, after an explicit warning        |
| `tld.status === "sunset"`          | "The .circle namespace is being retired; names resolve until …"                            | no                                   |

The two lines that matter most: an RPC failure is a retry, never "free"; and a controller is never a payment address.

## Integration approaches

| Approach                            | Use when                                                                           | Trade-offs                                                                                                                                                       |
| ----------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Direct RPC through `@arcns/resolve` | You can run viem in-process (web, Node, React Native)                              | No third party to trust: the wallet reads Arc itself. One shared 5-second cache per resolver. `reverse()` costs 2 to 4 `eth_call`s.                              |
| REST through `api.arcns.io`         | You cannot run an EVM client (a thin client, a bot, a backend in another language) | Same fields, `cache-control: public, max-age=5`, 20 requests per second per client with burst 60. Treat `502` as "resolve later", never as "free". CORS is open. |
| Your own API instance               | Bulk consumers, or a compliance boundary                                           | Same code, your RPC, your addresses from the deployment file. See [API](/api/).                                                                                  |

Whichever you choose: the chain is the source of truth, and an outage of `api.arcns.io` is not a resolution answer.

## Verification semantics and the controller

* `verified` is per coin type: `alice.arc` can have a verified Arc address and an unverified Bitcoin address.
* Records are keyed by the name's `epoch`, which bumps on every ownership change, so a record set by a previous owner can never be returned.
* `controller` (SDK) / `owner` (API) is who holds the name. It is exposed so a wallet can show "controlled by"; it is never the payment address and no resolver falls back to it.
* Answers from the pinned peer registry are always `unverified` and carry only a coin-type-60 record.

## What ArcNS provides, what the wallet builds

| ArcNS provides                                                                                                | The wallet builds                                                            |
| ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `@arcns/resolve` on npm (`testnet` tag), MIT, ESM and CJS, viem peer                                          | the recipient field, the picker for `conflict`, the warning for `unverified` |
| typed `ResolveError.code` for every failure                                                                   | the messages in the table above, in the wallet's own voice                   |
| `namespaceLabel()` and every result field needed for the display rule                                         | the badge, the registry short-address, the confirmation sheet                |
| `reverse()` with forward confirmation built in                                                                | the "sending to alice.arc" display and any short cache around it             |
| `records()` with per-coin-type verification                                                                   | multi-chain address selection                                                |
| `api.arcns.io` with the same fields, CORS and a public cache                                                  | fallback or thin-client integration                                          |
| transaction builders (`buildSetAddr`, `buildVerifyAddrSelf`, `buildSetPrimary`, …) returning a `PreparedCall` | signing and sending; the SDK never holds keys                                |
| the contract addresses in the deployment file, when published                                                 | pinning them in the build                                                    |

## Handoff

* We give: the SDK, the API, this page, the [Quickstart](/quickstart/) and the [Verified records](/verified/) page.
* We need: the chains you send on (so we can confirm the coin types you read), and the URL of your integration once it ships so we can list it.
* Contact: [support@arcns.io](mailto:support@arcns.io).

## Questions

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