# ArcNS docs URL: https://docs.arcns.io/ > Own your name on Arc — yours forever. One @handle, .arc or .circle name that replaces long wallet addresses and carries a record per chain. Pay once in USDC, no renewals, no expiry. *Arc name registry* One name — **@you**, **you.arc** or **you.circle** — that resolves to an address with a record per chain. Register it once; it's yours forever. Try the resolver, then jump into the docs. **Health** · `GET /health` ```bash curl -s https://api.arcns.io/health ``` RESPONSE · 200 (free): ```json { "ok": true, "service": "api", "version": "0.1.0" } ``` No RPC call — pure liveness, free for everyone. **Resolve a name** · `GET /resolve/%40circle` ```bash curl -s https://api.arcns.io/resolve/%40circle ``` RESPONSE · 200 (first-party read): ```json { "name": "@circle", "namespace": "handle", "registry": "0x520Ef0D4…43215d2", "chainId": 5042002, "blockNumber": 61199205, "owner": "0x3dDF29e9…cF3A17", "addr": null, "epoch": 1, "tokenized": false, "locked": false, "handleType": "Human", "expires": null } // a reserved genesis name, held by the treasury; // owner is never a payment address — addr is null ``` Captured through the first-party path (a browser on docs.arcns.io). From a terminal the same GET answers 402 — see the x402 tab. **Mixed shape** · `GET /resolve/%40nike.arc` ```bash curl -si https://api.arcns.io/resolve/%40nike.arc ``` RESPONSE · 409: ```json { "error": "ambiguous: both a handle and a domain shape", "input": "@nike.arc" } ``` Decided with NO chain read — @name and name.arc are refused as ambiguous before any RPC call. **Paid read (x402)** · `GET /resolve/%40alice` ```bash curl -si https://api.arcns.io/resolve/%40alice ``` RESPONSE · 402 + PAYMENT-REQUIRED: ```json { "code": "PAYMENT_REQUIRED", "x402Version": 2, "accepts": [{ "scheme": "exact", "network": "eip155:5042002", "asset": "0x36000000…000000", "amount": "1000", "payTo": "0x54f71A9F…E751D9", "extra": { "name": "GatewayWalletBatched" … } }] } // $0.001 in USDC, gas-free via Circle Gateway; // sign it, retry with PAYMENT-SIGNATURE → 200 ``` Agents pay $0.001 per answer; first-party browsers and API-key holders read free. A 4xx or 502 is never charged. **SDK usage** · `@arcns/resolve` ```ts import { arcTestnet, createResolver } from '@arcns/resolve' const arcns = createResolver({ client }) // a viem PublicClient on arcTestnet const r = await arcns.resolve('@circle') ``` RETURNS: ```ts { name: '@circle', namespace: 'handle', product: 'arcns', registry: '0x520Ef0D4…43215d2', controller: '0x3dDF29e9…CF3A17', chainId: 5042002, epoch: 1n, handleType: 'Human', … } // no Arc record set: resolve() throws // ResolveError { code: 'no-record-for-chain' } — never the controller ``` Reads the chain directly — free, no key. Source-accurate to sdk/ (0.1.0-testnet.2) and the Quickstart. > **Note** > ArcNS is live today on [app.arcns.io](https://app.arcns.io). Names run on **Arc testnet** (chain id `5042002`) while we prepare the mainnet launch — nothing on testnet has monetary value; see [Trust & transparency](/trust/) for exactly what that means for you. **Start here** - [Why get a name](/why/): What a permanent, multi-chain name does for you, in plain language. - [Register your name](/get-started/): Search, register in two transactions, set records, prove them, set your primary. - [Fees](/pricing/): Pay once, own forever — priced by name length and namespace, in USDC. **Use your name** - [Receive on any chain](/features/#receive-on-any-chain): A record per chain — Arc, Ethereum, Bitcoin, Solana — each with its own verified flag. - [Get paid by name](/get-paid/): A QR code and a shareable link anyone can pay. - [Subnames](/features/#subnames): pay.you.arc or pay.@you — under the name you already own, no extra fee. - [Recovery & locks](/features/#a-safety-net): A way back in if you lose access; a freeze if your key is at risk. **Identity & trust** - [Verified records](/verified/): What the verified flag means and the proof challenge format. - [Ownership & auditing](/trust/): Permanent ownership, anti-impersonation, and the contracts you can inspect. - [Common questions](/faq/): Permanence, safety, cost, and how the three namespaces fit. - [What is not live yet](/features/#not-live-yet): Gifting and the marketplace, stated plainly. **Build & integrate** - [Resolver API](/api/): Every route, the status contract, x402 for agents, registration-as-a-service. - [SDK quickstart](/quickstart/): Resolve @nike, nike.arc and nike.circle with @arcns/resolve. - [MCP server](/mcp/): Resolve names and prepare registrations from an AI agent. - [Contracts](/contracts/): Every deployed address on Arc testnet, from one file. ## What ArcNS is [#what-arcns-is] A wallet address is 42 unreadable characters, and one wrong character sends funds nowhere. ArcNS replaces it with a name you can read and share: register `@you` once and people pay it instead of copying an address, explorers show you as `@you` instead of raw hex, and one name carries an address record for every chain you use — Arc, Ethereum, Bitcoin, Solana. It's **permanent** — no renewals, no expiry — and it carries text records you own, so a wallet can finally prove who it belongs to. Start with [Why get a name](/why/), then [register yours](/get-started/). > **Note** > **Three shapes, three namespaces.** `@alice` is a handle; `alice.arc` and `alice.circle` are domain-shaped names with subnames. They are three independent registries that can belong to three different people, and every answer names its **namespace** and the **registry** it was read from — a mixed shape like `@alice.arc` is refused, never guessed. See [how they fit together](/faq/#handle-arc-and-circle). Why ArcNS ## Built to be namespace-safe, not just fast Four things that are true on Arc testnet today, verified against the deployed contracts. **Three namespaces, one rule** `@handle`, `.arc` and `.circle` are three independent namespaces with three different registries — never merged, never guessed between. **Never guessed, never mixed** Every result names its namespace and registry address. A mixed shape like `@alice.arc` is refused, not resolved; two registries that disagree are shown side by side. **Permanent registrations** No annual renewal, no expiry, no lapse to a stranger because a payment was missed — a name is registered once, in USDC, and held forever. **[Open source, MIT](https://forgejo.selfhsted.com/fortiblox/fortiblox-arcns)** The `.arc` / `.circle` registry, resolver profiles and reverse registrar reuse ENS contracts verbatim; the SDK, API, MCP server and app are all in one repository. **[Register your name](/get-started/)** — Start here Search a name, register it once (two transactions, a minute apart), point it at your addresses, prove you control them, and set it as your primary — the whole thing takes a few minutes on app.arcns.io. [Get started](/get-started/) ## Status [#status] * **Testnet only.** Everything here targets Arc **testnet** (chain id `5042002`, RPC `https://rpc.testnet.arc.io`, explorer [testnet.arcscan.app](https://testnet.arcscan.app)). Nothing on testnet has monetary value and the deployment may be reset. Mainnet is a separate, gated milestone. * **Contracts.** Deployed on 2026-09-07 at block `60889698`; every address is on the [Contracts](/contracts/) page, generated from the deployment file in the repository. * **App.** [`app.arcns.io`](https://app.arcns.io) is live: search, register, records, verification, primary names, recovery, locks, gifting and the marketplace — see [What you can do](/features/). * **API.** [`https://api.arcns.io`](/api/) holds no keys and no database. Reads are free for browsers on the ArcNS sites and for API-key holders; agents pay **$0.001 per call over x402**. Registration-as-a-service (`POST /raas/register`) is live for agents. * **SDK.** [`@arcns/resolve`](/quickstart/) is `0.2.0-testnet.1` in the repository's `sdk/` directory. It is **not on the npm registry yet** (checked 2026-09-14): build it from source until the first testnet release lands. * **MCP.** [`mcp.arcns.io`](/mcp/) is live for AI agents — read tools need no auth, write tools return unsigned calls for your own wallet. ## Source and licence [#source-and-licence] The whole project is open source under the MIT licence: [fortiblox/fortiblox-arcns](https://forgejo.selfhsted.com/fortiblox/fortiblox-arcns). The `.arc` / `.circle` registry, resolver profiles, reverse registrar and universal resolver reuse [ENS](https://docs.ens.domains/) contracts verbatim, so ENS-aware wallets and libraries resolve ArcNS names with the registry address swapped. ## Questions [#questions] Questions: [support@arcns.io](mailto:support@arcns.io). --- # Why get a name URL: https://docs.arcns.io/why/ > A name is more than a nickname — it's a permanent, multi-chain, verifiable identity you own outright. Here's what that gives you as a holder. A name on ArcNS is a small thing to register and a big thing to have. But the reason it matters most comes first: ## You own it — forever [#you-own-it--forever] Most names you can register somewhere are really **rented**. Miss a renewal and your name lapses, and someone else can grab it. For a name people send money to, that's dangerous: an expired name picked up by a stranger silently redirects payments meant for you. ArcNS doesn't work that way. In every namespace — `@handle`, `.arc`, `.circle` — you **pay once and own your name outright**: no renewals, no expiry, no subscription, no chance of losing it because you forgot to pay or stopped using it. It changes hands only when *you* decide to transfer or sell it. That permanence is the whole point, and it's what sets ArcNS apart from every naming system that expires. ## What else you get [#what-else-you-get] **Yours, forever** Pay once in USDC and own it. No renewals, no expiry dates to miss, no chance of losing your name because you forgot to pay. It can't lapse and it isn't custodial — your wallet is the owner on chain. **One name, every chain** A name carries an address record per chain — Arc, Ethereum, Bitcoin, Solana — each with its own verification flag. Share one name; get paid on whichever chain the sender is using. A chain with no record is an explicit “no record”, never a guessed address. **Fewer costly mistakes** People send to @you, not to a 42-character string. A readable name is far harder to fat-finger than raw hex — and mistakes on chain are permanent. **A real identity** Add a website, a description, an avatar and other ENS-style text records. @you becomes a profile people recognize, not a random address. **Prove it's really you** Prove control of an address for a given chain and its record shows as verified. Wallets can tell a proven record from an unproven claim before they send. **A safety net** Set up on-chain recovery for your @handle so you can get it back if you lose access, and lock it instantly if you think your key is at risk. Completing a recovery or undoing a lock both wait behind a 7-day timelock, so a thief can't undo what you set. ## Who it's for [#who-its-for] * **Anyone who receives crypto.** Creators, freelancers, merchants, friends splitting a bill — hand out `@you` once instead of a fresh address every time. * **People who use more than one chain.** If you hold on Ethereum, Bitcoin or Solana as well as Arc, a single name's records cover all of them. * **Anyone tired of copy-pasting addresses.** Set a name as your primary and explorers and apps that integrate ArcNS show your wallet as `@you` instead of a string of characters. * **People who want to be recognizable on chain.** A name plus a forward-confirmed primary is how others know a wallet is really yours — see [Explorers](/explorer/). * **Agents and the people who run them.** A name is the root a human, a wallet and another agent can all start from; see the agent section of [What you can do](/features/#agents). > **Note** > You don't have to use every feature. Many people register a name just to receive payments to something readable. Records, verification and subnames are there for every namespace; recovery and locks are there for `@handle`. All of it is optional and costs nothing extra to skip. ## Ready? [#ready] **[Register your name](/get-started/)** It takes a few minutes on app.arcns.io. **[See what a name can do](/features/)** The full feature set, explained in plain language — including what is not live yet. ## Questions [#questions] Questions: [support@arcns.io](mailto:support@arcns.io). --- # Register your name URL: https://docs.arcns.io/get-started/ > Search a name, register it in two transactions, point it at your addresses, prove you control them, and set it as your primary — a step-by-step walkthrough on app.arcns.io. Getting a name takes a few minutes. Everything happens at [app.arcns.io](https://app.arcns.io) — you'll need a wallet on Arc testnet and a little **USDC**, which on Arc is both the gas asset and the registration price (see [Fees](/pricing/)). **Step 1** ### Open the app and sign in [#open-the-app-and-sign-in] Go to [app.arcns.io](https://app.arcns.io) and connect the wallet you want your name tied to — an injected wallet such as MetaMask, or sign in with email, Google, X, Discord, GitHub, Telegram or a passkey to get an embedded wallet. This wallet will own the name, so use one you control and intend to keep. The app switches you to Arc testnet (chain id `5042002`) when you act. **Step 3** ### Search for a name you like [#search-for-a-name-you-like] Type the name you want — say `you` — into the search box. The app tells you whether `@you`, `you.arc` and `you.circle` are available and what each costs, read live from the price oracle. Shorter names cost more (see [Fees](/pricing/)). If it's taken, try a variation until you find one that's free. > **Note** > Names are **letters, numbers and hyphens only** (`a-z`, `0-9`, `-`; 1 to 32 characters; no hyphen at the edges or back-to-back, not all digits) — no emoji and no lookalike symbols from other alphabets. That's deliberate: it stops anyone from registering a name that *looks* identical to yours. More on that in [Trust & transparency](/trust/). **Step 5** ### Register it — two transactions, a minute apart [#register-it--two-transactions-a-minute-apart] Pick what you're registering as (`Human`, `Merchant` or `Org` — this is recorded with a `@handle`; `Agent` is defined on chain but not offered in the app yet) and confirm. Registration is **commit-reveal**: the first transaction commits a hash of your name, address and a random secret so nobody can front-run you; after at least **60 seconds** of chain time (and within 24 hours) the second transaction reveals and registers it, paying the one-time price shown. The app keeps your pending commitment in the browser between the two steps and tells you when it's ready. From that moment `@you` is yours — no renewals, ever. **Step 7** ### Point it at your addresses [#point-it-at-your-addresses] Open your name and add the addresses you want it to resolve to — your Arc address, and optionally your Ethereum, Bitcoin and Solana addresses. Now when someone sends to `@you`, a name-aware wallet looks up the record for the chain they're on. A chain you haven't added is an explicit "no record", never a guess. **Step 9** ### Prove you control them [#prove-you-control-them] Verify each address so it shows as **verified**: the app walks you through a control proof for each chain — a signature from that address (or an on-chain call for a smart-contract wallet) for Arc and Ethereum, and a signed challenge plus public key for Bitcoin and Solana. Verified records are the ones wallets can trust — see [Verified records](/verified/). > **Note** > Any later change to a record clears its verified flag, and a change of owner makes every old record unreadable. What is shown as verified was proven by the current owner for the current address — nothing else. **Step 11** ### Add a profile (optional) [#add-a-profile-optional] Add a website, a description, an avatar and other text records. This is what turns `@you` from a routing label into an identity people recognize. **Step 13** ### Set it as your primary name [#set-it-as-your-primary-name] Mark `@you` (or `you.arc`) as your **primary** name. This is the reverse link: explorers and apps that integrate ArcNS will show your wallet as that name instead of a raw address — but only while it forward-confirms, so a stale primary is never shown. ## That's it [#thats-it] You now have a name that works across chains, records you can prove, and an identity apps can display. From here you might want to: **[Get paid to your name](/get-paid/)** Share the QR code and payment link that come with your name so anyone can pay you by scanning or tapping. **[Set up recovery & locks](/features/#a-safety-net)** Give yourself a way back in if you lose access, and a way to freeze a handle in an emergency. **[Add a subname](/features/#subnames)** pay.you.arc for payments, docs.you.arc for a project — under the name you already own. **[Understand the fees](/pricing/)** What you paid, why shorter names cost more, and how the price climbs over time and volume. > **Note** > **Looking someone up?** The search on [app.arcns.io](https://app.arcns.io) works two ways: type a name to check if it's free to register, or look up a name that already exists to see whose it is and what it points to — with the namespace and registry shown alongside. It's how you find a name to register, and how you check one before you trust it. > **Note** > **Registering from a script or an agent?** The same registration is available without a wallet on Arc through [registration-as-a-service](/api/#registration-as-a-service): `POST /raas/register`, paid once over x402, minted to your address. For your own transactions, the [SDK](/quickstart/)'s `buildCommit` / `buildRegister` return the unsigned calls, and the [MCP server](/mcp/)'s `prepare_register` does the same for an agent. ## Questions [#questions] Questions: [support@arcns.io](mailto:support@arcns.io). --- # What you can do URL: https://docs.arcns.io/features/ > Multi-chain address records, a primary name, a profile, subnames, verified records, permanent ownership, recovery and locks, agent records — everything an ArcNS name gives you, and what is not live yet. Your name is the starting point. Here's everything you can do with it — use as much or as little as you like. Two features the app is built for are **not live yet** on Arc testnet and are marked as such below, so nothing on this page is a promise dressed up as a fact. ## Receive on any chain [#receive-on-any-chain] Point `@you` at your addresses on **Arc, Ethereum, Bitcoin and Solana**. Each is a separate address record with its own ENSIP-11 coin type and its own verification flag. When someone sends to `@you`, a name-aware wallet reads the record for the chain they're paying from — no more copy-pasting a raw `0x…` string. You hand out one name; it works everywhere you hold funds. **Arc** Your home chain (coin type `2152525650`). `@you` resolves to your Arc address so anyone can pay you on Arc by name — this is the record the [payment QR and link](/get-paid/) use. **Ethereum** Add your Ethereum address (coin type `60`) so people on Ethereum can send to `@you` instead of a `0x…` string. A wallet can also ask for the Arc record with Ethereum as the fallback. **Bitcoin** Add your Bitcoin address (coin type `0`) and collect BTC under the same name as everything else. You prove control of it with a Bitcoin message signature. **Solana** Add your Solana address (coin type `501`) and receive there too. You prove control of it with an Ed25519 signature from that key. > **Note** > You choose which chains to add — one or all four. Add more later at any time; your name stays the same. A chain with no record answers an explicit `no-record-for-chain`, never the wrong address on the wrong chain. ## Get paid with a QR code and a link [#get-paid-with-a-qr-code-and-a-link] Every name with an Arc record comes with a **QR code** and a **shareable payment link**, so people can pay `@you` by scanning or tapping instead of copying an address. Show the QR in person, or drop the link in a bio, a message, or an invoice. **[Get paid to your name](/get-paid/)** See how the QR code and payment link work, and where they shine — creators, merchants, and splitting bills with friends. ## Be shown as @you everywhere [#be-shown-as-you-everywhere] Set `@you` as your **primary name** and explorers and apps that integrate ArcNS display your wallet as `@you` instead of a long address — in transaction histories, profiles, and anywhere your wallet appears. There is exactly one primary per address across all three namespaces, and it is shown only while it forward-confirms: a `@handle` primary must still be owned by the address, a `.arc` / `.circle` primary must still resolve back to it. A stale primary is never shown. ## Build a profile [#build-a-profile] Behind your name sit text records you control — the same ENSIP-5 records ENS-aware wallets already read: **Avatar & description** A picture and a line of text that make @you recognizable at a glance. **Website & socials** Link your site and social accounts so people can find the real you. **Agent records** agent.manifest, agent.x402, agent.mcp, agent.a2a, agent.erc8004 — the endpoints and identity of an agent, on its name. See below. **All in one place** Everything lives under @you, so your identity travels with your name — and is cleared automatically when the name changes hands. ## Subnames [#subnames] One name can branch into purpose-built names — **`pay.you.arc`**, **`docs.you.arc`**, **`shop.you.arc`** — all under the name you already own, at **no extra registration fee**. A `@handle` gets sub-handles the same way (`pay.@you`). Each one can carry its own records, so you can keep your money and your projects neatly separated under a single identity. **Separate your money** Give out pay.you.arc for payments and keep you.arc for your identity, so a public payment name never exposes your main wallet. **Organize a project** docs.you.arc, app.you.arc, shop.you.arc — one name per purpose, all clearly yours. **Onboard a team** Hand teammates or departments their own subname without giving up control of the parent. **Good to know about subnames** * **They're free to create** — no registration fee beyond the name you already own; you pay only the transaction's gas. * **You're in control.** As the owner of the parent, you create and revoke its subnames whenever you like. * **They live under your name.** A subname has no separate token (`/nft/:name` answers `404 subnames have no token`) and is labelled "controlled by parent" rather than having its own owner — there's nothing separate to manage. * **Where to make them.** Live on chain and in the [SDK](/quickstart/) (`buildCreateSubname` for a handle, `buildSetSubnodeRecord` for `.arc` / `.circle`); the app's subnames panel ships behind a feature flag. ## Prove it's really you [#prove-its-really-you] Anyone can *set* an address on a name. Verification lets you **prove** it: for each record, prove control of that address for that chain and it shows as **verified**. Senders can tell a proven record from an unproven one before they send a single coin. **Verified records** A signature from the address (or an on-chain call for a contract wallet) on a challenge bound to your name's current version. Any later change to the record clears the flag. **Honest by default** An unverified record is shown as unverified so no one mistakes an unproven claim for a confirmed one — the API, the SDK and the app all carry the flag. > **Note** > Verified records are the ones others should trust. The full mechanics — the challenge format and what each proof type checks — are on [Verified records](/verified/). Signing in to third-party apps with a name is not a feature ArcNS offers today. ## Own it permanently [#own-it-permanently] You register a name **once** and it's yours to keep. There's no renewal to remember and no expiry date that could hand your name to a stranger. This is a deliberate choice: a name people send money to must never quietly change owners because a payment was missed. See [Fees](/pricing/) for how one-time pricing works. ## A safety net [#a-safety-net] Losing access to a wallet is the nightmare scenario in crypto. A `@handle` gives you two on-chain tools for it: **Recovery — get back in if you lose access** Set a recovery address in advance, and if you lose access to the wallet that owns your handle, that address can initiate recovery and complete it after a **7-day timelock** (the contract floors the delay at seven days). It's your insurance policy against a lost or inaccessible key — set it up while everything is fine, so it's there when you need it. Recovery is disabled once a handle is tokenized (see [Fees](/pricing/)): an ERC-721 in the wild has only its holder as owner, by design. **Locks — freeze your handle in an emergency** If you think your key might be compromised, lock your handle. A lock freezes it — ownership, records and transfers — so a thief can't move it or redirect your addresses while you sort things out. Unlocking has its own **7-day timelock**, so an attacker can't simply lock and immediately steal. `.arc` and `.circle` names use the verbatim ENS registrar, which has no recovery or lock: protect the wallet that owns them, or hold them from a smart-contract wallet with its own recovery. ## Agents [#agents] An ArcNS name is the naming layer Arc's agent stack lacks: ERC-8004 gives an agent a numeric id, ArcNS gives it `@payday-bot`. A name carries the agent's endpoints and identity as text records (`agent.manifest`, `agent.x402`, `agent.mcp`, `agent.a2a`, `agent.erc8004`), while the payment address stays the on-chain Arc record with its `verified` flag — the one pay-to fact that is proved rather than asserted. **[MCP server](/mcp/)** Resolve names, check availability, read agent records and prepare registrations from Claude Code, Cursor or VS Code — no key, no auth for reads. **[Pay per read with x402](/api/#agents-pay-per-read-with-x402)** Agents read the resolver API for $0.001 a call, settled in USDC from a Circle Gateway balance — no gas, no key. **[Register without a wallet on Arc](/api/#registration-as-a-service)** POST /raas/register: pay once over x402 and receive the name minted to your own address in about two minutes. > **Note** > The `Agent` handle type exists on chain and the API and SDK report it (`handleType`), but the app does not offer it at registration yet. An agent name is recognised by its records (`agent.manifest` / `agent.erc8004`) as well as by its type — see the verification ladder on the [MCP page](/mcp/). ## Not live yet [#not-live-yet] Two features the app is built around are switched off in the app today, even though their contracts are already deployed on Arc testnet. They are listed here so the docs never describe something you cannot do today. **Gift a name — not live yet** The intended feature: **pre-pay a name for someone else** and hand them a claim link, so a friend who holds no USDC yet can claim a name with zero funds of their own. The voucher contract this needs is **deployed on chain** and the app's gift UI is built, but the `/gift` page is behind a feature flag that stays off until a real email provider is wired up for claim notifications and one real gift has been run end-to-end with real funds. This section changes when it ships. **Buy and sell names — not live yet** The intended feature: a **built-in marketplace** (`ArcNSMarket`) with fixed-price listings, escrowed offers and English auctions with anti-snipe protection, a 2% fee on sales, and premium reserved names released through it rather than by direct registration. The contract is **deployed and configured on chain**, with sales fees flowing to the treasury — but the app's `/marketplace` page is still behind a feature flag that has not been switched on yet. Until then a `@handle` moves only by its owner's explicit transfer, recovery or tokenization, and reserved names are released only to their verified rightful holders (see [Fees](/pricing/#reserved-names)). ## Next [#next] **[Register your name](/get-started/)** Put all of this to use — start on app.arcns.io. **[Invite friends](/referrals/)** Share your referral link — and read the honest state of attribution. **[Trust & transparency](/trust/)** How ownership, anti-impersonation and on-chain auditing work. ## Questions [#questions] Questions: [support@arcns.io](mailto:support@arcns.io). --- # Get paid to your name URL: https://docs.arcns.io/get-paid/ > Turn @you into a way to get paid. Every ArcNS name with an Arc record carries a QR code and a shareable payment link, so anyone can pay you by scanning or tapping — no copying a long address. Once your name has a live Arc address record, `@you` becomes something people can pay in a tap. Every name gets a **QR code** and a **shareable payment link** on its page in [app.arcns.io](https://app.arcns.io) — nobody has to copy a wallet address. **A QR code to scan** Your name's pay screen shows a QR of its Arc address as a standard EIP-681 payment URI, so any EVM wallet can scan it and pay — hold it up at a table, put it on a screen, print it on a flyer. **A link to share** Copy the payment link or hit share, and send it in a message, a bio, or an invoice. Opening it opens a payment to that name. ## How to get paid [#how-to-get-paid] **Step 1** ### Link a payment address [#link-a-payment-address] Add your Arc address to your name (see [Register your name](/get-started/)) and, ideally, verify it. That's the address your QR and link point at — the name's **live Arc address record**, never the wallet that merely owns the name. Until it's set, the pay screen shows an honest "no payment address set" rather than sending anyone to the wrong place. **Step 3** ### Open your name's pay screen [#open-your-names-pay-screen] Your name's page shows the QR code, your address with a one-tap copy button, and the buttons to copy or share your payment link. **Step 5** ### Share it any way you like [#share-it-any-way-you-like] Show the QR to be paid in person, or send the link online. Optionally set an **amount** first and it's carried in both, so the payer sees exactly what to send. ## What the QR and the link carry [#what-the-qr-and-the-link-carry] * **The QR is a standard payment URI.** It encodes an EIP-681 URI wallets already know how to parse: `ethereum:
@5042002` (Arc testnet's chain id), with `?value=` added when an amount is set. Nothing proprietary — any EVM wallet that can scan a payment QR can pay it. * **The link carries the name, not just the address.** It is an `arcns:` URI shaped `arcns:?recipient=0x…&chainId=5042002&amount=`. A simple consumer pays the `recipient=` address directly; a name-aware wallet re-resolves the name when the link is opened and warns if the two disagree — the same way a shared link should behave once a name changes hands. * **Verification travels with it.** The address carries its flag from [Verified records](/verified/): `verified` next to the address if you proved control of it, `unverified` as an explicit warning otherwise. Nothing unproven is dressed up as confirmed. > **Note** > Amounts are decimal USDC (Arc's settlement and gas asset) up to six decimal places. The QR's `value=` field carries the equivalent in native wei (`1 USDC = 10^18` on Arc); the link's `amount=` field carries the decimal figure — both come from the one amount you type. > **Note** > Because the link and QR are tied to your **name**, they keep working for as long as `@you` is yours. Change which address receives, and the same link and code follow — no need to reprint or re-share. ## Great for [#great-for] **Creators & freelancers** Put @you and its QR in your bio or on an invoice and get paid without swapping addresses each time. **Merchants & pop-ups** Show the QR at the counter — customers scan and pay in seconds, in Arc's own settlement asset. **Friends & splitting bills** Send your link in the chat; everyone pays @you back without asking for your address. ## Next [#next] **[Building a wallet?](/wallet/)** The recipient-field flow: resolve, show the namespace and registry, warn on unverified, then send. **[What verified means](/verified/)** How an owner proves control of an address, and what a wallet should show for it. ## Questions [#questions] Questions: [support@arcns.io](mailto:support@arcns.io). --- # Fees URL: https://docs.arcns.io/pricing/ > Pay once, own forever — no renewals. How ArcNS pricing works — a one-time registration in USDC priced by name length and namespace, an optional tokenize fee for @handle, and the 2% marketplace fee once the marketplace ships. ArcNS is designed to be simple: **you pay once and own your name forever.** There are no renewals, no subscriptions, and no expiry dates, in any of the three namespaces (`@handle`, `.arc`, `.circle`). Here's the full picture. > **Note** > Every figure on this page is the on-chain pricing config the contracts read (`ArcNSPriceOracle`, mirrored in `docs/pricing-tiers.json` in the repository), and the launch prices were checked against live `/quote` answers on 2026-09-09: `@alice` 10 USDC, `alice.arc` 5 USDC, `@alexander` 5 USDC. The app and the API always show the live figure before you pay. ## One-time registration [#one-time-registration] Registering a name is a **single, one-time payment** in **USDC** — Arc's own gas and settlement asset, so one balance covers both the fee and the price. After that, the name is yours to keep; you never pay again to hold it. > **Note** > Because there's no renewal, your name can't lapse and can't be snatched by someone else because you missed a payment. That permanence is the whole point of a name people send money to. ### Priced by length [#priced-by-length] Shorter names cost more. Here's the current **launch price** at every length for `@handle` — one payment to register, plus an optional one-time fee if you ever want to tokenize the handle: | Length | Tier | Register (launch price, one-time) | Tokenize (optional) | | -------------- | --------- | --------------------------------- | ------------------- | | 1 character | Legendary | 25,000 USDC | +20 USDC | | 2 characters | Uncommon | 2,500 USDC | +15 USDC | | 3 characters | Uncommon | 250 USDC | +10 USDC | | 4 characters | Uncommon | 50 USDC | +3 USDC | | 5 characters | Common | 10 USDC | +1 USDC | | 6–9 characters | Common | 5 USDC | +0.75 USDC | | 10+ characters | Common | 2.5 USDC | +0.5 USDC | > **Warning** > **These are launch prices — 25% of the full ("ceiling") price.** Each length's ceiling is 100,000 / 10,000 / 1,000 / 200 / 40 / 20 / 10 USDC (same row order). The price climbs on **two independent dials that multiply together**: elapsed time since the namespace launched (2026-09-06), and how many `@handle` names have been sold — each dial starts at 50% and steps up 12.5 points every 91 days (time) or every 500 paid registrations (volume), capping at 100%. Because you pay once and own the name forever, **registering earlier locks in a lower price** — a name registered today never costs more later. ### `.arc` and `.circle` pricing [#arc-and-circle-pricing] The two TLD namespaces share the same length-tier shape at half of `@handle`'s ceiling per length: launch prices of 12,500 / 1,250 / 125 / 25 / 5 / 2.5 / 1.25 USDC for lengths 1 / 2 / 3 / 4 / 5 / 6–9 / 10+, with ceilings of 50,000 / 5,000 / 500 / 100 / 20 / 10 / 5. `.circle` is priced identically to `.arc`. A `.arc` or `.circle` name is an ERC-721 token from the moment it's registered (the verbatim ENS registrar), so there is no tokenize fee for a TLD name. > **Note** > Prices are set on chain and read live — the exact figure is always shown in the app and by `GET /quote/:name` before you confirm, so you never pay anything you haven't seen. Gas on Arc is paid in the same USDC; the two transactions of a registration (commit, then register) each cost a little gas on top of the price. ## Reserved names [#reserved-names] Some names aren't available for ordinary registration. At genesis, **20,307 reserved names** were minted to the ArcNS treasury in each of the three namespaces — the same list across `@handle`, `.arc` and `.circle` — and resolve today to the treasury as their controller with no payment address set. Three things can happen to a reserved name: * **Blocked outright.** System words, lookalike variants and a profanity list — never registrable by anyone, including us. This is the impersonation-risk tier. * **Released free to the rightful holder.** Trademarks, tickers and well-known identities are held, not sold: a company proves it controls its domain (the app's [claim page](https://app.arcns.io/claim) verifies a DNS record), a person is verified by hand, and the name is released at no charge. Charging someone for their own name would be exactly the behavior this list exists to prevent. * **Sellable as premium inventory** — short names and sought-after words — through the marketplace at a multiplier over the length price, rather than by ordinary registration. The marketplace is **not live yet** (see [What you can do](/features/#not-live-yet)), so this tier is simply held for now. ## The 2% marketplace fee [#the-2-marketplace-fee] When a name is **sold** through `ArcNSMarket` — a fixed-price listing, an accepted offer, or an auction — a **2% fee** applies to the sale. If you're just registering and holding your own name, this never touches you; it only applies when a name changes hands through a sale. `ArcNSMarket` is already **deployed and configured on Arc testnet**, with sales fees flowing to the treasury — but the app's `/marketplace` page is still behind a feature flag that hasn't been switched on yet (see [What you can do](/features/#not-live-yet)), so no sale can happen through the app today; the fee is recorded here because it is fixed in the same pricing config as everything else. ## Optional: tokenizing a `@handle` [#optional-tokenizing-a-handle] A `@handle` is **soulbound by default**: it moves only through an explicit `transfer` call by its owner, through recovery, or (once the marketplace ships) a sale — not like an ordinary token. Turning it into a fully transferable, standard ERC-721 (so it can move through any NFT tooling) is **entirely optional** and carries its own **separate one-time fee** (the "Tokenize" column above — also priced by length, and much smaller than registration). Most holders never need this. If you never tokenize, you never pay it. > **Warning** > Tokenizing is a **one-way step**: it permanently disables the on-chain **recovery** option for that handle (recovery and "the NFT holder is simply the owner" are mutually exclusive). See [What you can do](/features/#a-safety-net). `.arc` and `.circle` names skip this entirely — they're ERC-721 from registration. ## Registering through the API [#registering-through-the-api] Agents that register through [registration-as-a-service](/api/#registration-as-a-service) pay the same on-chain price **plus the gas of the two transactions and a small margin**, in one x402 payment; the `402` challenge states the exact total before anything is settled. On testnet the gas component is visible in that total, because Arc's gas floor is 20 Gwei in USDC. ## At a glance [#at-a-glance] | What | Cost | When | | ------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------- | | Register a name | One-time, in USDC, priced by length and namespace (tables above) | Once, when you register it | | Hold your name | **Free forever** | No renewals, ever | | Tokenize a `@handle` (optional) | Separate one-time fee (table above) | Only if you choose to; not applicable to `.arc` / `.circle` | | Sell a name on the marketplace | 2% of the sale | Only when it sells — marketplace not live yet | > **Note** > Whatever you're about to pay is always shown before you confirm. Nothing is charged without you approving it in your wallet first. ## Questions [#questions] Questions: [support@arcns.io](mailto:support@arcns.io). --- # Quickstart URL: https://docs.arcns.io/quickstart/ > Install @arcns/resolve, create one resolver, resolve @nike, nike.arc and nike.circle, branch on typed errors, read records with verification, reverse an address, and call the API with curl. Ten minutes from install to a resolved name with its namespace, registry and verification status on screen. Everything below is Arc **testnet** (chain id `5042002`), package `0.2.0-testnet.1`. ## Install [#install] ```bash title="terminal" # @arcns/resolve is not on the npm registry yet, and its source repository # is private to FortiBlox Labs — there is no public install path today. # Read the same data with no install at all, over the public HTTP API: curl -s https://api.arcns.io/version ``` `@arcns/resolve` is `0.2.0-testnet.1` in the (private) `sdk/` directory of this repository. It will ship on the npm dist-tag `testnet` (pre-releases never move `latest`, so name the tag once it's out: `npm install @arcns/resolve@testnet`); as of 2026-09-14 it has not been pushed to the registry, and the repository isn't public, so there is nothing an outside developer can `npm install` or `git clone` yet. Everything below — `resolve()`, `records()`, `reverse()` and the rest — is the real, currently-shipping SDK surface, generated straight from its TypeScript source, so treat it as a preview of the exact shape you'll get once the package publishes; for integrating today, use [the HTTP API](#try-the-api) instead, which needs no package at all. `viem` 2.x is a peer dependency of the SDK. ESM and CommonJS builds are both shipped. Licence: MIT. > **Note** > The Arc testnet contract addresses ship inside the package as `ARCNS_DEPLOYMENTS[5042002]`, mirrored from `contracts/deployments/5042002.json` (see [Contracts](/contracts/)), so `createResolver({ client })` needs no `deployment` on Arc testnet. Pass one explicitly only for a fork or a local chain. ## One resolver, one client [#one-resolver-one-client] Create one resolver per process, not one per component or request. One instance gives you one shared 5-second cache and one TLD table read. ```ts title="resolver.ts" // A module singleton: one viem client, one resolver, one short cache. import { createPublicClient, http } from "viem"; import { arcTestnet, createResolver, type Resolver } from "@arcns/resolve"; export const arcns: Resolver = createResolver({ client: createPublicClient({ chain: arcTestnet, transport: http(), // or http(process.env.ARC_RPC_URL) // v1 has no CCIP-read for payment records; an OffchainLookup is an error, not data. ccipRead: false, // [!code highlight] }), // On a fork or a local chain, point it at your own deployment file: // deployment: deploymentFromJson(JSON.parse(readFileSync("deployments/5042002.json", "utf8"))), }); ``` The full configuration surface, from the SDK's own `ResolverConfig` (`sdk/src/resolve.ts`): **ResolverConfig** | Property | Type | Description | | --- | --- | --- | | `client` | `object` | viem public client. `chain` must be set. Create it with `ccipRead: false` so CCIP-read never runs (SR-24). | | `deployment?` | `object` | The arcns contract set. Defaults to the checked-in `ARCNS_DEPLOYMENTS[chainId]`. Absent: every read throws `not-configured` (SDK-only; there is no fallback registry). | | `tldTable?` | `TldTable` | TLD table. Default: loaded lazily from `deployment.tldDirectory` (cached), or the shipped default table without a deployment. | | `cacheTtlMs?` | `number` | `resolve()` cache TTL in ms. Default 5_000 (SR-25: ≤ 5 s); 0 disables. | | `now?` | `function` | Clock in unix seconds, for TLD lifecycle gates. | ## Resolve a name [#resolve-a-name] ```ts const r = await arcns.resolve("@nike"); // { name: "@nike", namespace: "handle", registry: "0x…", product: "arcns", // address: "0x…", coinType: 2152525650, verification: "verified", epoch: 3n, … } await arcns.resolve("nike.arc"); // read from the single ArcNS registry, one labelled answer await arcns.resolve("nike.circle"); // same, for .circle await arcns.resolve("@nike.arc"); // throws ResolveError { code: "ambiguous" } ``` Dispatch is on **shape** (`@` prefix, or a known TLD suffix), never "try one, fall back to the other". Use `looksLikeName(input)` to decide whether to attempt resolution at all, so a pasted `0x…` address does not surface a validation error. `parseName(input)` is that shape decision on its own, with no chain read — a `ParsedName`: **ParsedName** | Property | Type | Description | | --- | --- | --- | | `namespace` | `Namespace` | Which naming system the shape selects. Exactly one — or `ambiguous` is thrown. | | `canonical` | `string` | Canonical display form: `@alice`, `pay.@alice`, `alice.arc`, `pay.alice.circle`. | | `label` | `string` | The registrable label: `alice` in every example above. | | `labels` | `array` | Every label, leftmost first, TLD included for domains: `["pay","alice","arc"]`; `["pay","alice"]` for `pay.@alice`. | | `tld?` | `string` | The TLD label for domains; absent for handles. | | `node` | ``0x${string}`` | The node the name's records hang off (ENS namehash for domains, the `HANDLE_ROOT`-derived node for handles — onchain-design §2.1). | Every `Resolved` carries `input`, `name`, `namespace`, `chainId`, `registry`, `product`, `resolver`, `node`, `controller`, `epoch`, `coinType`, `chain`, `address`, `addressBytes`, `verification` and, for TLD names, `tld: { status, sunsetAt }`. * `namespace` is `handle`, `arc` or `circle`. `namespaceLabel(namespace)` gives the display form (`@handle`, `.arc`, `.circle`). * `registry` is the contract the answer came from; `product` says whose it is — always `arcns`: resolution is single-registry and authoritative (CEO decision 2026-09-09), never a comparison against another registry. * `controller` is who owns the name. It is **never** the payment address and the SDK never falls back to it. * `epoch` bumps on every ownership change; records from an older epoch are unreachable by construction. * `verification` is `verified` or `unverified`. See [Verified records](/verified/). The whole `Resolved` shape, from `sdk/src/types.ts`: **Resolved** — A successfully resolved name. `namespace` **and** `registry` are not optional and there is no variant of this type without them (SR-04). `@jack`, `jack.arc` and `jack.circle` are different namespaces with different owners, so a bare address is never a complete answer. `registry` is always FortiBlox ArcNS's own registry — the only one this SDK ever reads from. | Property | Type | Description | | --- | --- | --- | | `input` | `string` | Exactly what the user typed, for display. | | `name` | `string` | Canonical display form: `@alice`, `alice.arc`, `pay.alice.circle`. | | `namespace` | `Namespace` | Which naming system matched. Render this before allowing a send. | | `chainId` | `number` | Chain the registry was read on. | | `registry` | ``0x${string}`` | The registry contract the answer came from. Render its short form next to the name (SR-72). | | `product` | `"arcns"` | Which product owns `registry`. | | `resolver` | ``0x${string}`` | Resolver contract the record was read from. | | `node` | ``0x${string}`` | ENS node / handle node the records hang off. | | `controller` | ``0x${string}`` | Who controls the name right now: `HandleRegistry.ownerOf(tokenId)` for a handle, `ENSRegistry.owner(node)` for a TLD name. This is **never** the payment address (SR-21) — it is exposed so a UI can show "controlled by". | | `epoch` | `bigint` | Record epoch (`recordVersions(node)` / handle `version`). Bumps on every ownership change; records from an older epoch are unreachable by construction. | | `handleType?` | `HandleTypeName` | `@handle` only: the declared `HandleType` (`Human` \| `Merchant` \| `Org` \| `Agent`). Absent for `.arc` / `.circle` names. Decides how to *address* a name (`isAgentHandle`), never whether to pay it — that is `verification`. | | `coinType` | `number` | Coin type of the record that answered (may differ from the requested chain when a fallback chain matched). | | `chain` | `union` | Chain `coinType` belongs to, or null for a coin type this SDK does not name. | | `address` | `string` | The address to send to. `0x` hex for EVM chains, base58 for SOL/X1, a scriptPubKey hex for BTC (ENSIP-9 bytes rendered by `valueToAddress`). | | `addressBytes` | ``0x${string}`` | Raw ENSIP-9 record bytes. | | `verification` | `Verification` | Whether ownership of `address` was proved on chain for this coin type. | | `tld?` | `object` | TLD lifecycle, for TLD namespaces. A `sunset` name still resolves until `sunsetAt`; show the banner. | Multi-chain: the default `chain` is `ARC` (ENSIP-11 coin type `0x80000000 | 5042002` = `2152525650`). Pass `{ chain: "ETH" | "BTC" | "SOL" | "X1" }` to read that coin type's record, or `{ chain: "ARC", fallbackChains: ["ETH"] }` to try the coin-type-60 record next; the result names the `coinType` that answered. A name with no record for the requested chain is an explicit `no-record-for-chain`, never a wrong address on the wrong chain. ## Branch on the error code [#branch-on-the-error-code] `resolve()` throws a `ResolveError` with a typed `code`, so a recipient field can tell "still typing" from "does not exist" from "no address for this chain". ```ts title="resolve-recipient.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 }); // 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 "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; } } } ``` | `code` | Meaning | | ----------------------------------------- | -------------------------------------------------------------------------------------------------- | | `unrecognized` | not a handle or a known domain; probably a raw address | | `ambiguous` | both shapes at once, e.g. `@jack.arc` | | `invalid-handle`, `invalid-domain` | right shape, invalid content; `reason` names the rule (`HyphenAtEdge`, `NonAscii`, `AllDigits`, …) | | `not-found` | valid name, not registered (or `reason: "nft-burned"`); only after a successful chain read | | `no-record-for-chain` | registered, but no address record for the requested chain; never the controller | | `tld-retired`, `tld-sunset`, `tld-paused` | TLD lifecycle gates (reads: `tld-retired`; writes: all three) | | `offchain-lookup` | the resolver asked for CCIP-read; v1 treats that as an error, never as data | | `rpc-error` | transport or decoding failure; never reported as `not-found` | | `not-configured` | no deployment for this chain; never a guessed address | ## Records with verification per coin type [#records-with-verification-per-coin-type] `records(input)` returns every non-empty address record of a name in our registry, each with its own verification flag. ```ts const recs = await arcns.records("alice.arc"); for (const rec of recs) { // rec = { coinType, chain, value, address, verification } // coinType 2152525650 · chain "ARC" · verification "verified" // coinType 60 · chain "ETH" · verification "verified" // coinType 0 · chain "BTC" · verification "unverified" -> warn // coinType 501 · chain "SOL" · verification "unverified" -> warn } ``` `verification === "verified"` means the owner proved control of that address for that coin type on chain. Anything else is a claimed-but-unproven record: show a warning and make the user look twice. Coin types: `BTC` 0, `ETH` 60, `SOL` 501, `ARC` 2152525650. `text(input, key)` reads an ENS text record from our resolver, or `null`. Each entry is an `AddressRecord` (`sdk/src/records.ts`): **AddressRecord** — One decoded address record. | Property | Type | Description | | --- | --- | --- | | `coinType` | `number` | | | `chain` | `union` | Chain `coinType` maps to, or null for one this SDK does not name (it still renders as hex). | | `value` | ``0x${string}`` | Raw ENSIP-9 bytes. | | `address` | `string` | Human-readable form — see valueToAddress. | | `verification` | `Verification` | | ## Reverse: the one primary name [#reverse-the-one-primary-name] ```ts const p = await arcns.reverse("0xce42…54AC"); // { name: "alice.arc", namespace: "arc", registry, product, chainId, node, address, controller } | null ``` The answer is a `Primary` (`sdk/src/resolve.ts`): **Primary** — An address's one primary name, forward-confirmed (SR-07, SR-23). | Property | Type | Description | | --- | --- | --- | | `name` | `string` | Canonical display form as stored in the reverse record: `@alice`, `alice.arc`. | | `namespace` | `Namespace` | | | `registry` | ``0x${string}`` | | | `product` | `"arcns"` | | | `chainId` | `number` | | | `node` | ``0x${string}`` | | | `address` | ``0x${string}`` | The queried address (checksummed). | | `controller` | ``0x${string}`` | Current controller of the name (`ownerOf` / `owner(node)`) — equals `address` for handles by construction. | There is exactly one primary per address across all three namespaces. 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_call`s 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); coalesce concurrent reads of the same address in a table. ## Single-registry, authoritative [#single-registry-authoritative] `.arc` / `.circle` resolve **only** through the FortiBlox ArcNS registry (`deployment.ensRegistry`). There is no cross-resolution against, comparison with, or warning about any other registry on Arc — `resolve()` / `resolveAll()` never read a second registry, and a name this registry does not hold is a plain `ResolveError { code: "not-found" }`, exactly like any other unregistered name (CEO decision 2026-09-09: single-registry, winner-take-all). ## Try the API [#try-the-api] `https://api.arcns.io` reads the same chain and returns the same fields for callers that cannot run an EVM client. `@` must be URL-encoded as `%40` in a path. ```bash curl -s https://api.arcns.io/health # {"ok":true,"service":"api","version":"0.1.0"} — no RPC call; liveness only curl -si https://api.arcns.io/resolve/%40circle | head -1 # HTTP/2 402 — from a terminal you are not a first-party browser: read the # PAYMENT-REQUIRED header and pay $0.001 over x402, or present an X-API-Key. curl -s https://api.arcns.io/quote/%40alice # x402 as well — price, availability, commit window curl -s https://api.arcns.io/version # free: build, chain id, RPC host, head block ``` Reads are free for browsers on the ArcNS sites and for API-key holders, and sold to everyone else at $0.001 per answer over x402 v2 — the SDK, which reads the chain directly, is always free. The full status contract, every response field and the payment flow are on the [API](/api/) page. ## Next [#next] * Building a send flow: [Wallets](/wallet/). * Showing names next to addresses: [Explorers](/explorer/). * What `verified` means and how proofs are made: [Verified records](/verified/). ## Questions [#questions] Questions: [support@arcns.io](mailto:support@arcns.io). --- # 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. ## Purpose [#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 (built from source in the repository — not yet on the npm registry, see [Integration approaches](#integration-approaches)), the read-only API at `api.arcns.io`, and the contracts, already deployed on Arc testnet (see [Contracts](/contracts/)). What the wallet consumes is one function, `resolve()`, and the fields it returns. > **Rule** > A name is never just an address. Show the **namespace**, the **registry** and the **verification** status before enabling "Send". Refuse a mixed shape; never fall back from the controller to a payment address. Everything below is that rule, applied. ## Required capabilities [#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` against the single authoritative ArcNS registry for the namespace. | | 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 safely | The typed `ResolveError.code` switch below: `not-found` and `no-record-for-chain` are explicit; every other failure gets its own message, never a silent guess. | ## Required contracts, endpoints and ABI [#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 Arc testnet addresses are already deployed and bundled in the package as `ARCNS_DEPLOYMENTS[5042002]` (the full list is on [Contracts](/contracts/)); pass an explicit `deployment` only to point at a different chain or environment. 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); it is free for first-party browser origins and `X-API-Key` holders and priced at $0.001/call over x402 for everyone else (see [Integration approaches](#integration-approaches) below); its full contract is on the [API](/api/) page. ## The flow, end to end [#the-flow-end-to-end] What `resolve()` does with one input, in the order the SDK does it (`sdk/src/parse.ts`, `sdk/src/resolve.ts`): the shape decision first with no chain read, then the TLD gate, then the registry, then the record and its verified flag. Every RPC failure is `rpc-error` (or `offchain-lookup`), never `not-found`. ```mermaid flowchart TD A["User types a name"] --> B("looksLikeName()?") B -- no --> B1["pass the raw
address through"] B -- yes --> C("parseName()
shape only, no RPC") C -- "@alice.arc" --> C1["ambiguous"] C -- "bad label" --> C2["invalid-handle
invalid-domain"] C -- "one shape" --> T("TLD name, and the TLD
retired or past sunset?") T -- yes --> T1["tld-retired"] T -- no --> R("registry holds it?
ownerOf · owner(node)") R -- no --> N["not-found
only after a
successful read"] R -- yes --> D("addr(node, coinType)
set?") D -- "no, no fallback" --> E["no-record-for-chain
never the controller"] D -- yes --> V["verified(node, coinType)"] V --> S["Resolved
name · namespace · registry
address · verification"] ``` **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 "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 (`@handle`, `.arc`, `.circle`) side by side and never auto-select between them. 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 [#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 | | `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 | | `tld-paused` (write builders only) | "registrations for .circle are paused"; the name still resolves for sends | n/a for reads | | `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 [#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. **Not yet on the npm registry** — build it from the `sdk/` directory of the repository and `npm link` it until the first testnet release ships. | | 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. CORS is open. Free for first-party browser origins on the ArcNS sites and for callers with an ArcNS-issued `X-API-Key`; every other caller (a bot, a backend, a script) gets `402 Payment Required` and pays $0.001 per call over x402 v2. Treat `502` as "resolve later", never as "free". See [Agents: pay per read with x402](/api/#agents-pay-per-read-with-x402). | | 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 [#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. ## What ArcNS provides, what the wallet builds [#what-arcns-provides-what-the-wallet-builds] | ArcNS provides | The wallet builds | | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | `@arcns/resolve` (MIT, ESM and CJS, viem peer) — built from the repository's `sdk/` directory today; not yet on the npm registry | the recipient field, the picker for `unverified`, in-wallet warnings | | 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, a public cache and free first-party/`X-API-Key` reads | fallback or thin-client integration; paying over x402 for non-browser, keyless traffic | | transaction builders (`buildSetAddr`, `buildVerifyAddrSelf`, `buildSetPrimary`, …) returning a `PreparedCall` | signing and sending; the SDK never holds keys | | the contract addresses, already deployed on Arc testnet and bundled in the SDK | pinning them in the build | ## Handoff [#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] Questions: [support@arcns.io](mailto:support@arcns.io). --- # 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, name detail pages, the events to index, and the trust rule for an index. ## Purpose [#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 [#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](/quickstart/#install)); 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](/api/#agents-pay-per-read-with-x402). * **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 [#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 { 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_call`s 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: ```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>>(null); useEffect(() => { let alive = true; arcns.reverse(address).then((r) => alive && setP(r)).catch(() => alive && setP(null)); return () => { alive = false; }; }, [address]); if (!p) return {short(address)}; return ( {p.name} {namespaceLabel(p.namespace)} · {short(p.registry)} ); } ``` ## Flow 2: the search bar [#flow-2-the-search-bar] A user types `alice`, `@alice`, `alice.arc` or `alice.circle`. Show **all three namespaces side by side**, 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, labelled with `product` and `registry`, or "not registered" | | `@alice.arc` | nothing; 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 [#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 [#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 [#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](/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. | 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: 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 [#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 [#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 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 | | 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 [#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` | "not registered" | | handle burned | `not-found`, `reason: "nft-burned"` | "burned"; never the last owner | | 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 [#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] Questions: [support@arcns.io](mailto:support@arcns.io). --- # Resolver widget URL: https://docs.arcns.io/widget/ > The drop-in web component from @arcns/resolve-widget, with the script-tag and framework snippets, the attribute table, the events, the render states, theming, and accessibility. Testnet `` is a web component that does the whole resolve-and-render step for a recipient field, a search box or an explorer row: the user types `@alice`, `alice.arc` or `alice.circle` (or pastes a `0x…` address) and the element shows the address, the **namespace**, the **registry** it was read from and the **verified** flag, then tells your code through an event. It wraps [`@arcns/resolve`](/quickstart/) and follows the [wallet](/wallet/) recipe exactly; use it when you would rather not write that UI yourself. Package: `@arcns/resolve-widget`, MIT, `0.1.0-testnet.N` on the `testnet` dist-tag. > **Rule** > The widget never shows a bare address. Every answer carries its namespace badge, the registry short-address and the verification badge, and a mixed shape such as `@alice.arc` is refused before any read. Resolution is single-registry and authoritative: `.arc` and `.circle` names resolve only against the FortiBlox ArcNS registry, so there is never a second registry's answer to weigh against it. ## Drop it in [#drop-it-in] > **Not on npm yet** > As of 2026-09-14, the `@arcns` npm scope has not been created — neither `@arcns/resolve-widget` nor `@arcns/resolve` is on the npm registry, so the CDN URL and `npm install` command below are the shape the install takes once they publish, not something that resolves today. Both packages are fully built in the `sdk-widget/` and `sdk/` directories of the (private) [repository](https://forgejo.selfhsted.com/fortiblox/fortiblox-arcns); until the first testnet release lands, that source is the only way to get the widget. To integrate today with no package at all, call [the HTTP API](/api/) directly. One script tag, one element, once published. The IIFE build bundles the SDK and the parts of `viem` it needs; nothing else is loaded. ```html ``` With a bundler, once published: **npm** ```bash npm install @arcns/resolve-widget@testnet @arcns/resolve@testnet viem ``` **pnpm** ```bash pnpm add @arcns/resolve-widget@testnet @arcns/resolve@testnet viem ``` **yarn** ```bash yarn add @arcns/resolve-widget@testnet @arcns/resolve@testnet viem ``` **bun** ```bash bun add @arcns/resolve-widget@testnet @arcns/resolve@testnet viem ``` ```ts import { defineArcnsResolve } from "@arcns/resolve-widget"; defineArcnsResolve(); // idempotent; a no-op during server-side rendering ``` `@arcns/resolve` and `viem` are peer dependencies of the ESM build. ## What it shows [#what-it-shows] | State | On screen | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | idle | a hint naming the accepted shapes | | invalid | the rule the input breaks (`A hyphen cannot start or end a name.`), or that `@name.arc` is two different names. Nothing is looked up while the user is still typing. | | loading | `Resolving alice.arc…`; the region is `aria-busy` | | resolved | namespace badge (`@handle`, `.arc`, `.circle`), the canonical name, **Verified** or **Unverified**, the checksummed address with a copy button, and `registry 0x0a1D…2FE4 · ArcNS`; `· ETH record` when the coin-type-60 fallback answered | | not-found | `alice.arc is not registered.`, or `@arcns is registered but has no ARC address record. Nothing to send to.`, or `0x0b94…209D has no primary name (…)` | | error | `Couldn't read the chain. Try again.` with **Retry**; a retired TLD; a missing configuration | The state is mirrored to a `state` attribute on the element, so a page can style `arcns-resolve[state="resolved"]`. The status area reserves its height, so moving between states never shifts the content below it. ### `verified` on the badge [#verified-on-the-badge] Forward (name to address): **Verified** means the owner proved control of that address for that coin type on chain, `verified(node, coinType)` on the ArcNS resolver. **Unverified** means a record exists but nobody proved it: the badge is a warning and its tooltip says so. The name's controller is never shown as a payment address. Reverse (address to name): a primary is returned only after it forward-confirms, so the badge is always **Verified** and the tooltip reads "forward-confirmed". An address whose primary no longer confirms is not-found, never the stale name. This is the same meaning the [API](/api/) gives `addr.verified` and the reverse route's `verified: true`; the full definition is on [Verified records](/verified/). ### Single registry, one answer [#single-registry-one-answer] `.arc` and `.circle` lookups resolve only against the FortiBlox ArcNS registry — single-registry, winner-take-all (CEO decision 2026-09-09), the same rule the [SDK](/quickstart/#single-registry-authoritative) and the [API](/api/) apply. There is no `resolveAll()`, no second registry to compare against, and no per-row **Use** button: a name this registry does not hold is a plain `not-found`, exactly like any other unregistered name. An earlier build of the widget rendered a multi-registry `ambiguous`/`conflict` state with `candidates` and `picked` fields on the events; all of that has been removed. `ambiguous` now names only the mixed-shape refusal below. ## Attributes [#attributes] Every attribute has a camelCase property (`rpc-url` is `rpcUrl`). | Name | Type | Description | Default | | --- | --- | --- | --- | | `value` | `string` | initial input; the property reads and writes the field, and writing resolves at once | | | `mode` | `"auto" \| "resolve" \| "reverse"` | `resolve` (names only), `reverse` (addresses only), `auto` (a `0x` + 40 hex address is reversed, anything else is a name) | auto | | `rpc-url` | `string` | JSON-RPC endpoint for the SDK | the chain's default | | `chain-id` | `number` | chain to read; one the SDK ships a deployment for, or give an RPC | 5042002 | | `api-url` | `string` | when set, lookups use `GET {api-url}/resolve/:name` and `/reverse/:address` instead of the RPC; the public instance, `https://api.arcns.io`, is free only for first-party ArcNS origins and `X-API-Key` holders — see below | | | `chain` | `"ARC" \| "ETH" \| "BTC" \| "SOL" \| "X1"` | which record to show; `ARC` falls back to the coin-type-60 record the way the API does | ARC | | `namespaces` | `string` | which namespaces the field accepts; a name outside the list is `invalid` with the namespace named | handle,arc,circle | | `placeholder` | `string` | input placeholder | per mode | | `label` | `string` | accessible label, visually hidden | Name or address | | `readonly` | `boolean` | the field cannot be typed into; set `value` from code | | | `theme` | `"auto" \| "light" \| "dark"` | `light`, `dark`, or follow `prefers-color-scheme` | auto | | `debounce` | `number` | quiet milliseconds before a lookup; `0` looks up on every keystroke; Enter looks up at once | 300 | Properties only: `resolver` (a shared `@arcns/resolve` `Resolver`; overrides `rpc-url` and `chain-id`), `state`, `result`, `focus()`, `refresh()`. Reads through `api-url` are free for the ArcNS sites (`arcns.io`, `app.arcns.io`, `docs.arcns.io`) and for callers presenting an ArcNS-issued `X-API-Key`; every other origin gets `402 Payment Required` and is charged per call over x402 — see [Agents: pay per read with x402](/api/#agents-pay-per-read-with-x402). The widget has no x402 client built in, so a `402` from a non-first-party `api-url` renders as a plain `error` state, not a retry or a payment prompt. If you're embedding on a site other than the ones above, use `rpc-url` instead (the SDK reads the chain directly and is always free) or front `api-url` with your own paying proxy. ## Events [#events] Both bubble and are composed, so one listener on `document` covers every instance. | Event | `detail` | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `arcns-resolve` | `{ input, mode, name, namespace, registry, product, address, verified, chainId, coinType? }` | | `arcns-error` | `{ input, code, message, reason?, status? }`; `code` is one of `not-found`, `no-record-for-chain`, `no-primary`, `ambiguous`, `tld-retired`, `rpc-error`, `not-configured`, `http-error` | Invalid input while typing emits nothing; the mixed shape `@name.arc` does emit `ambiguous`, because it is a decision rather than a typo. A lookup the user has typed past emits nothing at all: `fetch` requests are aborted and RPC answers are discarded. ## Frameworks [#frameworks] The element is plain DOM. Register it once on the client, then use the tag. **React** ```tsx import { useEffect, useRef } from "react"; import { defineArcnsResolve, type ArcnsResolveDetail, type ArcnsResolveElement } from "@arcns/resolve-widget"; export function Recipient({ onAddress }: { onAddress: (d: ArcnsResolveDetail) => void }) { const ref = useRef(null); useEffect(() => { defineArcnsResolve(); const el = ref.current!; const handler = (e: CustomEvent) => onAddress(e.detail); el.addEventListener("arcns-resolve", handler); return () => el.removeEventListener("arcns-resolve", handler); }, [onAddress]); return ; } ``` No React dependency in the package. React 19 passes custom-element attributes through as written; on React 18 declare the tag in `JSX.IntrinsicElements` and keep the `addEventListener` above. **Vue** ```vue ``` Set `compilerOptions.isCustomElement = (tag) => tag.startsWith("arcns-")` so Vue leaves the tag alone. **Svelte** ```svelte console.log(e.detail)} /> ``` **No markup** ```ts import { mount } from "@arcns/resolve-widget"; const field = mount("#recipient", { mode: "resolve", resolver: arcns, // your app's shared @arcns/resolve instance, optional onResolve: (d) => enableSend(d), onError: (d) => disableSend(d.code), }); field.setValue("alice.arc"); field.destroy(); ``` ## Theming [#theming] Styles live in the shadow root and read custom properties from the page. Each defaults to the ArcNS brand token for the active theme, and every text and background pair in both themes meets WCAG AA. ```css arcns-resolve { --arcns-bg: #fffefc; --arcns-fg: #1b1816; --arcns-fg-muted: #6b635c; --arcns-border: rgba(27, 24, 22, 0.18); --arcns-accent: #b93d0a; --arcns-ok: #1f7a3a; --arcns-warn: #a12d5e; --arcns-radius: 12px; --arcns-font: "Geist", system-ui, sans-serif; --arcns-font-mono: "Geist Mono", ui-monospace, monospace; --arcns-status-min-height: 6.25rem; } ``` Also `--arcns-surface`, `--arcns-fg-secondary`, `--arcns-accent-soft`, `--arcns-accent-strong` (accent text on the tinted namespace badge; `#35c5ff` in dark, because the base accent falls below AA on the tint), `--arcns-accent-contrast`, `--arcns-ok-soft`, `--arcns-warn-soft`. CSS parts for deeper changes: `root`, `field`, `input`, `status`, `result`, `namespace`, `verified`, `address`, `copy`, `meta`. ## Accessibility [#accessibility] A real `