This guide covers the calls the aDNS dapp makes. The signatures below match
ADNSRegistrar.sol in the contracts repository. The registrar address lands with the
Sepolia deployment; until then the widget runs against a mock with these exact shapes.
v1 registrar constraints: control is proven for ERC-721 same-chain NFTs only (via
ownerOf/approval). ERC-1155 and ERC-6909 control proofs, and cross-chain bindings, are
on the roadmap (see ADNSIP-25 security considerations).
1. Check that the wallet controls the NFT
The connected wallet must control the NFT it binds. v1 proves this for ERC-721:
// ERC-721: the registrar accepts the owner or an approved operator.
const owner = await publicClient.readContract({
address: nftContract,
abi: erc721Abi,
functionName: 'ownerOf',
args: [tokenId],
});
const controls = owner.toLowerCase() === account.toLowerCase();
2. Check that the name is available
isAvailable(slug, label) mirrors the register guards that do not depend on the caller
(label shape, collection exists, name free). slug is the collection (parent), label is
the name.
const available = await publicClient.readContract({
address: registrar,
abi: registrarAbi,
functionName: 'isAvailable',
args: [slug, label], // e.g. ('normies', 'charlie')
});
if (!available) {
// render: "name already taken, pick another"
}
3. Price the name (native ETH)
Registration is paid in native ETH via msg.value, with length-based pricing
(shorter names cost more). The price is a gas-free view returning wei — no token, no
approval step.
// Live price for the typed label (wei).
const price = await publicClient.readContract({
address: registrar,
abi: registrarAbi,
functionName: 'priceForLabel',
args: [label],
});
// Pre-flight: make sure the wallet can cover the price plus a little gas.
const balance = await publicClient.getBalance({ address: account });
const GAS_BUFFER = 2_000_000_000_000_000n; // 0.002 ETH
if (balance < price + GAS_BUFFER) {
// render: "You need X ETH to register this name (plus a little for gas)"
}
4. Register in one transaction
aDNS names are dual-mode ERC-6909 tokens: a free name is owned by a wallet and is
transferable; a bound name is owned by an NFT and is frozen (BYONFT). The registrar
exposes both entry points, both payable, same price:
register(slug, label)— mints a free wallet-owned name to the caller.registerWithBinding(slug, label, nftOwner)— atomically mints and binds the name to an NFT the caller controls (this is the BYONFT flow the widget uses).
const hash = await walletClient.writeContract({
address: registrar,
abi: registrarAbi,
functionName: 'registerWithBinding',
value: price, // native ETH; contract refunds any excess
args: [
slug, // collection, e.g. "normies"
label, // the name, e.g. "charlie"
{
chainId, // must equal the registrar's chain in v1 (same-chain only)
tokenContract, // the ERC-721 collection
tokenId, // the specific token
},
],
});
A free name can be bound later with bind(node, nftOwner), moved between NFTs with
rebind, or released back to free mode with unbind. See ADNSIP-25.
On success the name is bound to the NFT, and charlie.normies (public form;
charlie.normies.adns.eth under the hood) resolves to that token. Whoever controls the
token controls the name.
Collection slugs (the parent, e.g. normies) are admin-allocated in v1 via
registerCollection. A user registers a label under an existing slug. If the slug does
not exist, isAvailable returns false.
Reading bindings (ADNSIP-25)
To resolve a name to its NFT, or verify a binding, use the registry's forward record:
// Forward record: name -> NFT tuple
const nft = await publicClient.readContract({
address: registry,
abi: registryAbi,
functionName: 'ownerNft',
args: [node], // namehash of label.slug.adns.eth
});
A binding is verified only when the forward record and the NFT's adns reverse record
agree — see ADNSIP-25 for the reconciliation rule.