documentation

the .usdc resolver api.

turn a .usdc name into the Arc address behind it with a single HTTP request. every read endpoint is public, CORS-open, cacheable and needs no authentication.

quickstart

$ curl https://usdcdomains.com/api/resolve/alice.usdc

a registered name returns 200 with the resolution payload. an unregistered one returns 404. that is the whole integration surface for most applications.

read endpoints

GET/api/resolve/{domain}

resolve a domain

returns the address a name points at, plus its owner and records. with or without the TLD: alice and alice.usdc are equivalent. addresses are lowercase 0x hex. cached 60s at the edge. 404 when unregistered.

{
  "name": "alice",
  "fqdn": "alice.usdc",
  "tld": "usdc",
  "owner": "0x8f2a…c41d",
  "target": "0x93b7…7e02",
  "records": { "x": "alice", "url": "https://alice.example" },
  "registeredAt": "2026-09-17T09:12:44.180Z",
  "txHash": "0x7d3e…",
  "forSale": { "priceWei": "50000000000000000000", "priceUsdc": "50", "listedAt": "…" }
}
GET/api/check?name={name}

check availability

validates the name and reports availability and price. always 200; read the available flag. a free result is a snapshot, not a hold: the database decides the winner at reservation time.

{
  "available": true, "valid": true, "normalized": "alice", "fqdn": "alice.usdc",
  "registered": false, "held": false,
  "priceUsdc": "5", "priceWei": "5000000000000000000", "configured": true
}
GET/api/domains?limit=50&offset=0&q=

list the registry

every confirmed name, newest first. limit caps at 100; q is a case-insensitive substring filter.

{ "tld": "usdc", "total": 128, "limit": 50, "offset": 0, "domains": [ { "name": "alice", "fqdn": "alice.usdc", … } ] }
GET/api/wallet/{address}

domains by wallet

reverse lookup: everything a 0x address owns, newest first. case-insensitive. lists by owner, not target. served no-store.

{ "owner": "0x8f2a…c41d", "tld": "usdc", "count": 2, "names": [ … ] }
GET/api/stats

registry stats

total registered names, price tiers, treasury address, chain id and explorer. configured is false while the treasury is unset and registrations are closed.

registration

reserve → pay → confirm. ownership is recorded only after the payment settles on Arc mainnet (chain id 5042).

POST/api/register/reserve

reserve a name

body: { name, wallet }. inserts a pending reservation with a fresh reference and a ten-minute expiry. a unique index decides races: the loser gets 409 NAME_UNAVAILABLE. send memo as the transaction calldata (UTF-8 → hex) and wei as its value, to treasury. reserving the same name again from the same wallet returns the live reservation.

{
  "reservationId": "…",
  "memo": "usdc:reg:alice:1a2b3c4d",
  "treasury": "0x072d…c14d",
  "wei": "5000000000000000000",
  "priceUsdc": "5",
  "expiresAt": "…",
  "chainId": 5042
}
POST/api/register/confirm

confirm payment

body: { reservationId, txHash }. the server fetches the transaction itself and verifies: the receipt succeeded, the chain id matches, the recipient is the treasury, the value covers the price, the calldata is exactly the reservation reference, and the sender is the reserving wallet. 425 TX_NOT_FOUND is retryable while the transaction is pending. a transaction is never credited twice.

{ "ok": true, "name": "alice", "fqdn": "alice.usdc", "owner": "0x8f2a…c41d", "target": "0x8f2a…c41d", "txHash": "0x7d3e…", "registeredAt": "…" }

updates

POST/api/update

repoint, set records, or transfer

body: { name, action, value, ts, signature }. action is set-target, set-records or transfer. for the address actions value is the new lowercase address; for records it is a JSON object string with sorted keys. ts is a millisecond timestamp within ten minutes of now. signature is a personal_sign by the current owner over exactly this text:

usdc registry
action: set-target
name: alice.usdc
value: 0x93b7…7e02
ts: 1789470000000

no gas: the change is authenticated by signature and written to the registry immediately. a transfer also repoints the target to the new owner and removes any listing. record keys: avatar, url, description, x, github, telegram, discord, email, btc, sol, location, each at most 256 characters.

marketplace

peer-to-peer resale. the owner lists a price; a buyer pays the seller directly on Arc; the server verifies the transaction and moves ownership. no fee is taken and no funds pass through the registry.

GET/api/market/listings?limit=50&offset=0&q=&sort=newest

names for sale

sort is newest, price-asc or price-desc; q is a substring filter. held is true while a buyer is completing a purchase. cached 15s.

{ "tld": "usdc", "total": 3, "listings": [ { "name": "alice", "fqdn": "alice.usdc", "seller": "0x8f2a…c41d", "priceWei": "50000000000000000000", "priceUsdc": "50", "listedAt": "…", "held": false } ] }
GET/api/market/{domain}

one listing

the listing for a name, or 404 NOT_LISTED. registry and wallet payloads also carry a forSale field when a name is listed.

GET/api/market/sales?limit=50&offset=0

recent sales

completed purchases, newest first, each with the on-chain txHash of the payment. a sold name also exposes lastSale in its resolve payload.

POST/api/market/list

list a name, or change its price

body: { name, priceWei, ts, signature }. priceWei is an integer string between 1 and 1,000,000 USDC (18 decimals). the owner signs this text with personal_sign; ts must be within ten minutes.

usdc market
action: list
name: alice.usdc
price: 50000000000000000000 wei
ts: 1789470000000
POST/api/market/unlist

remove a listing

body: { name, ts, signature }. same message with action: unlist and price: 0 wei. refused with 409 BUY_IN_PROGRESS while a buyer holds the name; so are price changes and transfers.

POST/api/market/reserve

reserve a listed name

body: { name, wallet }. ten-minute hold. returns the seller address to pay, the wei to send and the memo to put in the calldata. the seller cannot reserve their own name (400 SELF_PURCHASE).

{ "reservationId": "…", "memo": "usdc:buy:alice:1a2b3c4d", "seller": "0x8f2a…c41d", "wei": "50000000000000000000", "priceUsdc": "50", "expiresAt": "…", "chainId": 5042 }
POST/api/market/confirm

confirm the purchase

body: { reservationId, txHash }. the server reads the transaction from Arc and checks: mined and not reverted, to is the seller, value ≥ price, calldata equals the memo, from is the buyer, the listing is unchanged, the tx was never credited before. then owner and target move to the buyer, the listing is removed and the sale is recorded. 425 TX_NOT_FOUND while pending: retry.

name rules

  • character set — a-z, 0-9 and hyphens. the ASCII check runs on the raw input before lowercasing, so Unicode look-alikes are refused outright.
  • length — 3 to 63 characters, excluding the TLD.
  • hyphens — no leading, trailing or consecutive hyphens.
  • reserved — admin, www, api, support, help, mail, root, system, official, circle, arc, usdcdomains, registry, market, docs, domains, name, treasury and usdc itself.

amounts & chain

  • USDC is the gas token on Arc. the native balance and the USDC ERC-20 at 0x3600…0000 are the same funds shown two ways.
  • 18 decimals on the wire. msg.value and every wei field use the 18-decimal native view: 5 USDC = 5000000000000000000. the ERC-20 view uses 6 decimals; never mix them.
  • plain transfer. the payment is a native value transfer to an externally owned address with the memo as calldata — no contract call, no approval.
  • fees. EIP-1559 with a 20 gwei floor; a registration costs about a cent in gas on top of the price. finality is deterministic and sub-second, so confirmation is usually instant.
  • network. Arc mainnet, chain id 5042, rpc https://rpc.mainnet.arc.io, explorer https://explorer.arc.io. bring USDC to Arc with Circle's CCTP bridge from Ethereum, Base, Solana and 20+ chains.

error codes

non-2xx responses carry a stable error.code plus a human-readable error.message. branch on the code.

INVALID_NAME400the domain failed validation.
INVALID_WALLET400not a valid 0x address.
INVALID_TX400not a valid transaction hash.
INVALID_BODY400the request body is not a JSON object.
INVALID_ACTION400unknown update action.
INVALID_RECORDS400a record key or value is not allowed.
NOT_FOUND404the domain is not registered.
NAME_UNAVAILABLE409taken, or held by a live reservation.
NO_RESERVATION404the reservation id does not exist.
RESERVATION_EXPIRED409the ten-minute window closed and another reservation holds the name.
TX_NOT_FOUND425not mined yet. retryable.
TX_FAILED400the transaction landed but reverted.
WRONG_CHAIN400the transaction is on a different chain.
WRONG_RECIPIENT400the payment did not go to the expected recipient.
UNDERPAID400the value sent was below the price.
MEMO_MISMATCH400calldata is not this reservation's reference.
PAYER_MISMATCH400a different wallet sent the payment.
TX_ALREADY_USED409transaction already credited to a domain.
BAD_SIGNATURE401the update signature is not from the owner wallet.
STALE_SIGNATURE400the timestamp is outside the ten-minute window.
INVALID_PRICE400listing price is not an integer wei amount within bounds.
NOT_LISTED404the name is not for sale.
SELF_PURCHASE400the buyer already owns the name.
BUY_IN_PROGRESS409a buyer holds the name; listing changes, transfers and other buyers must wait.
LISTING_STALE409the listing or the owner changed since the reservation.
RATE_LIMITED429too many requests from this IP; honor Retry-After.
RPC_ERROR502the Arc RPC could not be reached.
NOT_CONFIGURED503deployment is missing its treasury or database.
DB_ERROR500the registry database could not be reached.