Verified records
What the verified flag on an ArcNS address record means, how an owner proves control of an address for a coin type, and the arcns:v1 challenge format.
Every address record ArcNS returns carries a verification flag. This page says exactly what it means, so a wallet or explorer can decide what to show and what to warn about.
What verified means
| Value | Meaning | What to do |
|---|---|---|
verified | The owner proved control of this address for this coin type on chain (verified(node, coinType) on the resolver). | Show it plainly. |
unverified | A record exists but ownership was never proved for it. It may still be correct; nobody has shown that it is. | Show a warning and make the user look twice before sending. |
The flag is per coin type. alice.arc can have a verified Arc record and an unverified Bitcoin record at the same time. In the SDK it is Resolved.verification and AddressRecord.verification; in the API it is the boolean addr.verified.
Two things the flag never does:
- It never turns a controller into a payment address.
controller(the SDK) orowner(the API) is who holds the name; the resolvers never fall back to it, verified or not. - It never survives an ownership change. Records are keyed by the name's
epoch, which bumps on every transfer, so a proof made by a previous owner is unreachable by construction.
ArcNS resolves .arc and .circle names from its own single, authoritative registry only — there is no other pinned registry it falls back to or compares against. verified is an extension of ArcNS's own resolver (verified(node, coinType)); a node whose resolver doesn't implement that extension — a plain legacy resolver exposing only addr(bytes32) — always reads as unverified, never as confirmed.
How a record becomes verified
Not yet on npm
@arcns/resolve is the real, currently-shipping SDK surface below, but it has not been published to the npm registry yet — see the Quickstart for why and what to use today for read-only lookups. The transaction builders on this page (writes) have no HTTP API equivalent; treat the code below as a preview of the shape you'll get once the package publishes.
For the Arc address itself, the simplest proof is a transaction from that address: buildVerifyAddrSelf proves msg.sender.
const call = buildVerifyAddrSelf({ deployment, name: "alice.arc" });
await wallet.sendTransaction({ to: call.to, data: call.data, value: call.value }); // proves the Arc address (msg.sender)For another chain's address the owner signs a challenge with the wallet that holds that address, then submits the signature on Arc. resolver.challenge() reads the name's current on-chain versionOf(node) and hands back both the raw challenge string and the EIP-712 typed data to sign:
import { buildVerifyAddr } from "@arcns/resolve";
const { challenge, typedData, nonce } = await resolver.challenge("alice.arc", "ETH");
// arcns:v1:rec:0x9a7a…7bae:60:0x0000…0003:5042002:0x2841…6287a:9f3c1a2b…
// EVM coin types (ETH, ARC/ERC-1271) sign the EIP-712 `RecordProof` digest, not the raw string:
const signature = await wallet.signTypedData(typedData);
const call = buildVerifyAddr({ deployment, name: "alice.arc", chain: "ETH", nonce, proof: { signature } });
await wallet.sendTransaction({ to: call.to, data: call.data, value: call.value });The challenge format
arcns:v1:rec:<node>:<coinType>:<version>:<chainId>:<resolver>:<nonce>| Field | Example | Meaning |
|---|---|---|
arcns:v1 | scheme and version | |
| purpose | rec | a record proof, verified on chain (ctl is a separate control proof, verified off chain) |
| node | 0x9a7a…7bae | the ENS / handle node the record hangs off (parseName(...).node); encodes the namespace and label, so no separate namespace/label field exists |
| coin type | 60 | ENSIP-9 / ENSIP-11 coin type being proved (BTC 0, ETH 60, SOL 501, ARC 2152525650) |
| version | 0x0000…0003 | ArcNSResolver.versionOf(node); changes on every ownership change, so the proof dies with it |
| chain id | 5042002 | Arc testnet |
| resolver | 0x2841…6287a | the resolver that verifies the proof (ArcNSResolver) |
| nonce | 9f3c1a2b… | from generateNonce(); one use |
Binding the node, coin type, version, chain id and resolver into the signed string is what stops a signature from being replayed for a different name, a different chain, or the same name after it changes hands.
Signature forms by chain:
- ETH (and other EVM coin types): the EIP-712
RecordProofdigest (recordProofTypedData), not the raw challenge string — verified on chain byverifyAddrSecp256k1. - BTC: a BIP-137 signature over the raw challenge string, plus the 64-byte uncompressed public key.
- SOL / X1 (ed25519): a signature over the raw challenge string, followed by the public key.
Local pre-checks (verifyEthTypedProofLocal, verifyControlProofLocal) let a wallet refuse a doomed transaction before it is sent. The resolver verifies only the EIP-712 typed-data form for EVM record proofs; an earlier EIP-191 form (verifyEthProofLocal) was removed before the SDK's first published version.
Showing it
- Wallets:
verifiednext to the address, nothing more;unverifiedas a warning that the user must pass through before "Send" is enabled. See Wallets. - Explorers: show the flag per record on the name page and never render an unverified record as if it were confirmed. See Explorers.
- Agents and machine consumers: treat
verification !== "verified"as "do not pay without a human".
Questions
Questions: [email protected].