Skip to content

Build with ANS

Accept a name where your app asks for an address. Start with a read-only lookup, then add purchases or record editing if your app needs them.

Connect to Arc

Install viem and create a public client with the settings below. On Arc mainnet, send transaction value and pay gas in native USDC. Convert amounts using 18 decimals.

Install viem
pnpm add viem
arc.ts
import { createPublicClient, defineChain, http } from 'viem'

export const arc = defineChain({
  id: 5042,
  name: 'Arc mainnet',
  nativeCurrency: { name: 'USD Coin', symbol: 'USDC', decimals: 18 },
  rpcUrls: { default: { http: ['https://ans.family/rpc/arc'] } },
  blockExplorers: {
    default: { name: 'Arc Explorer', url: 'https://arc.exploreme.pro' }
  }
})

export const client = createPublicClient({
  chain: arc,
  transport: http()
})
Chain ID
5042
Native currency
USDC, 18 decimals
RPC
https://ans.family/rpc/arc

Address lookup

For input such as alice.arc, lowercase it and strip the suffix before validating the label. Put the suffix back when computing the namehash. Ask the controller for its resolver address rather than assuming that address will stay fixed.

resolve.ts
import { namehash, parseAbi, zeroAddress } from 'viem'
import { client } from './arc'

const controller = '0x3Afe007AD24Ce90F17F24C8dC1E712E6bd6000AC'
const controllerAbi = parseAbi([
  'function resolver() view returns (address)'
])
const resolverAbi = parseAbi([
  'function addr(bytes32 node) view returns (address)'
])

const resolver = await client.readContract({
  address: controller,
  abi: controllerAbi,
  functionName: 'resolver'
})

const address = await client.readContract({
  address: resolver,
  abi: resolverAbi,
  functionName: 'addr',
  args: [namehash('alice.arc')]
})

if (address === zeroAddress) throw new Error('Name does not resolve')

Treat a zero-address response as a failed lookup: the registration may have expired or lack an address record. Block the send action in that case. Query ANS explicitly; an Ethereum ENS lookup will not find these records.

Registration flow

A purchase has two onchain steps. First publish a commitment that conceals the requested name, then reveal it and pay. This prevents observers from taking the name by copying a pending registration.

  1. Commit the request

    Create a random 32-byte secret and retain it locally for the reveal. Pass the label, owner, duration, and secret to makeCommitment(name, owner, duration, secret). Send the resulting hash to commit(bytes32).

  2. Observe the reveal window

    Reveal no earlier than 60 seconds after the commitment. The window closes after 24 hours; after that, begin again with a fresh secret.

  3. Reveal and pay

    Submit register using the original commitment inputs. Attach the amount returned by rentPrice as transaction value. Confirmation mints the registration NFT to the owner and sets that wallet as the name’s destination.

Quote a one-year registration
import { parseAbi } from 'viem'
import { client } from './arc'

const controller = '0x3Afe007AD24Ce90F17F24C8dC1E712E6bd6000AC'
const abi = parseAbi([
  'function available(string name) view returns (bool)',
  'function rentPrice(string name, uint256 duration) view returns (uint256)'
])
const oneYear = 365n * 24n * 60n * 60n

const [available, price] = await Promise.all([
  client.readContract({
    address: controller, abi, functionName: 'available', args: ['alice']
  }),
  client.readContract({
    address: controller, abi, functionName: 'rentPrice', args: ['alice', oneYear]
  })
])
Annual registration prices by name length
Label lengthAnnual priceMinimum duration
3 characters100 USDC1 year
4 characters25 USDC1 year
5 to 63 characters5 USDC1 year

Change a destination

Address edits are authorized by the owner of the ERC-721 registration. Write the new destination to the resolver. After an NFT transfer, use reclaim when the registry ownership also needs to be updated.

Write the destination record
import { namehash, parseAbi } from 'viem'

const resolverAbi = parseAbi([
  'function setArcAddress(bytes32 node, address target)'
])

await walletClient.writeContract({
  address: '0xCFaaBe54713131E988Eb76948aa6184210EB29dF',
  abi: resolverAbi,
  functionName: 'setArcAddress',
  args: [namehash('alice.arc'), newAddress],
  account
})

Contract addresses

Begin with the controller address below. It reports the current registry, registrar, and resolver. These protocol contracts use transparent upgradeable proxies.

ANS contracts on Arc mainnet
ContractAddressRole
Controller0x3Afe007AD24Ce90F17F24C8dC1E712E6bd6000ACEntry point for quotes and name purchases
Registry0x1CB7B317BeCA5FE135A6f761AC9C14a6e9b6B1cdMaps names to owners and resolvers
Registrar0xe2dc805E9D6C99CF7E8382eEFC48F71ECF5cAF16Holds registration NFTs and expiry dates
Resolver0xCFaaBe54713131E988Eb76948aa6184210EB29dFReads and writes each name’s records

Rules and limitations

Accepted names
Labels must be 3 to 63 characters long and use lowercase ASCII letters, digits, or internal hyphens. Leading and trailing hyphens are rejected.
After expiry
Resolution returns zero as soon as registration expires. A renewal grace period follows before another wallet can register the label.
Admin controls
The configured ProxyAdmin can upgrade the controller, registry, registrar, and resolver. Fetch their active addresses through the controller when it offers a getter.
Review status
No independent contract audit has been published. Automated tests are available, but do not replace your own review.