Resolver SDK
@agtnames/resolver is a standalone TypeScript library that resolves a .agt name against the AGT Registry and verifies its manifest. It reads the registry over raw JSON-RPC — no wallet library, no API key — and ships the agt-resolve CLI. Read-only by design.
Install
npm install @agtnames/resolverNode 20+, Bun and Deno, and browsers (one caveat under Runtimes). Only dependencies: @noble/curves and @noble/hashes.
Quick start
import { AgtResolver } from "@agtnames/resolver";
const agt = new AgtResolver({ chain: "polygon" });
// Everything: chain state + the manifest, fetched and verified three ways.
const r = await agt.resolveAgent("launchpad.agt");
if (r.verified) {
r.manifest.endpoints // owner's claims, safe to act on as such
r.manifest.capabilities
} else {
r.reasons // e.g. ["no manifest set"] — say so; do not present manifest content as fact
}import { AgtResolver } from "@agtnames/resolver";
const agt = new AgtResolver({ chain: "polygon" });
// Chain state only: owner, status, records. No manifest fetch, one multicall-free read set.
const rec = await agt.resolve("launchpad.agt");
rec.owner // "0x37007a1c233f00b423bc0d177ac5b50ca9417596"
rec.active // true
rec.perpetual // true (expiry is "perpetual")
rec.records // { addr, manifestUri, endpoints: { mcp?, a2a?, http?, ws? }, wallet, texts }import { isAgent } from "@agtnames/resolver";
// true only if the name is registered AND its manifest verifies against the on-chain owner
const ok = await isAgent("launchpad.agt", { chain: "polygon" });Chains
A named chain fills in the RPC endpoint and the deployed contract addresses. localhost has no defaults — pass rpcUrl and registry.
| chain | id | Registry | Resolver | Migration |
|---|---|---|---|---|
polygon | 137 | 0x5B9386C47395B0551c814cC03b69cbD20eb0C87A | 0x66Ae037d2A6a770B4772b889b6cA1704504399f2 | 0x4276d03AcbcA433D257FBd90c53F090F4B16d38E |
amoy | 80002 | 0xd08E0d9BCB26572Eaa22fe27Df53a5D2721D3BCD | 0xE02f88b9BC0394742bBBe5c590E043B647B83419 | 0xC79A3fb86BcC3637BB58cDcd1f6E12Cf3fFDCBc8 |
Default RPCs: https://polygon-bor-rpc.publicnode.com and https://polygon-amoy-bor-rpc.publicnode.com. The table is exported as CHAINS; contracts are source-verified on Polygonscan.
API
new AgtResolver(options)
new AgtResolver({
chain: "polygon", // polygon | amoy | localhost
rpcUrl: "https://polygon-bor-rpc.publicnode.com", // override the public default
registry: "0x5B9386C47395B0551c814cC03b69cbD20eb0C87A", // required only for localhost / custom
ipfsGateway: "https://dweb.link/ipfs/", // used for ipfs:// manifests
timeoutMs: 15_000, // per RPC request (manifest fetch uses 10 s)
maxManifestBytes: 256 * 1024,
legacyDns: false, // opt-in DNS-over-HTTPS fallback for pre-registry names
});The constructor throws immediately when it cannot determine an RPC URL or a registry address — configuration errors surface before any call.
Methods
| Method | Returns |
|---|---|
resolve(name) | NameRecord — chain state and records, no manifest fetch. |
resolveAgent(name) | AgentResolution — NameRecord plus the fetched manifest, CID check and verification. |
text(name, key) | One text record (e.g. description, url, avatar). |
available(name) | boolean — can the name be registered right now. |
fnsOwner(name) | Owner on the legacy registry, or null when no legacy address is configured. |
One-shot helpers: resolveAgent(name, options) and isAgent(name, options).
NameRecord
| Field | Meaning |
|---|---|
name, label | "launchpad.agt", "launchpad". |
tokenId, node | Decimal token ID and its hex namehash — the same number. |
registered, owner | Whether anyone holds the name, and who. |
active, perpetual, expiry | Status flags; expiry is unix seconds as a string, "perpetual", or null. |
resolver | The resolver contract holding this name's records. |
records | addr, manifestUri, endpoints (protocol → URL for mcp, a2a, http, ws), wallet, texts. |
source | "registry-v2", or a legacy fallback when enabled. |
AgentResolution
Everything in NameRecord, plus:
| Field | Meaning |
|---|---|
manifest | The parsed manifest, or null. |
manifestSource | "onchain" normally; "dns" / "dns-inline-v1" only with the legacy fallback. |
cid | "match", "mismatch", "unsupported" (not a raw sha2-256 CIDv1) or "not-ipfs". |
verified, reasons, signer | Outcome of the three-way check, the reasons when it fails, and the recovered signer. |
Helpers
import {
namehash, tokenIdOf, normalizeName, labelOf, // names
canonicalize, canonicalUnsigned, signManifest, // manifests
verifyManifest, fetchManifest, verifyCid, CHAINS, // verification, chains
} from "@agtnames/resolver";
normalizeName("LaunchPad") // "launchpad.agt"
namehash("launchpad.agt") // "0xa41373928a1fe80b04d647faf417c8851dc61f4ab52f2347cf3715195ab01d20"
tokenIdOf("launchpad.agt").toString() // "74213674913414793103689332001647165694612910724881941644579943946836492950816"
CHAINS.polygon.registry // "0x5B9386C47395B0551c814cC03b69cbD20eb0C87A"
// Verify a manifest you already hold against the current owner
const { verified, signer, reasons } = verifyManifest(manifest, r.owner);signManifest(unsigned, privateKeyHex) signs with a raw key (servers, CI). In a browser, sign the canonical string with the wallet's personal_sign instead — that is what /manifest does. Canonical form and verification rules: Manifest Spec.
Errors
import { AgtResolver } from "@agtnames/resolver";
try {
const agt = new AgtResolver({ chain: "localhost" }); // throws: registry is required
} catch (e) { /* configuration problem — fix before making calls */ }
const agt = new AgtResolver({ chain: "polygon", timeoutMs: 5_000 });
try {
const r = await agt.resolveAgent("launchpad.agt");
// Manifest problems never throw: they land in r.reasons with verified === false.
} catch (e) {
// Network problems do throw: RPC unreachable, non-JSON body, AbortError on timeout.
}CLI
agt-resolve installs with the package. Output is pretty JSON on stdout; errors go to stderr. Exit codes: 0 ok, 1 the lookup failed, 2 usage error.
npx agt-resolve resolve launchpad.agt # full AgentResolution (manifest + verification)
npx agt-resolve record launchpad.agt # chain state only (NameRecord)
npx agt-resolve available someone-new.agt # { "name": "someone-new.agt", "available": true }
npx agt-resolve text launchpad.agt description # one text record
npx agt-resolve namehash launchpad.agt # node + tokenId — offline
npx agt-resolve fns launchpad.agt # legacy registry owner (needs --legacy / a configured FNS)
# flags (or env): --chain AGT_CHAIN · --rpc AGT_RPC_URL · --registry AGT_REGISTRY
# --gateway AGT_IPFS_GATEWAY · --doh AGT_DOH_URL · --legacy
npx agt-resolve resolve you.agt --chain amoy | jq .verifiedHow resolution works
- Derive the token ID from the name (
namehash) and read ownership, status, the resolver address and the records from the registry over JSON-RPC. - Fetch the manifest from its URI (
ipfs://through the gateway,https://, or inlinedata:), bounded by size and time. - Verify: the bytes hash to the CID when it is one; the signature recovers to
manifest.owner; that address is the current on-chain owner;agtis a3.xversion. Any failure becomes areasonsentry.
Runtimes
Resolution is fetch plus pure-JS cryptography, so it runs in Node 20+, Bun, Deno and browsers. One caveat in 1.0.x: manifests published as inline base64 data: URIs are decoded with Node's Buffer, which browsers and Deno lack; ipfs:// and https:// manifests are fully portable. The CLI is Node-only.
MCP server
The same resolution is available as tools for any MCP-compatible client through @agtnames/mcp — see Use with Claude Code.
Source & license
packages/resolver on GitHub. MIT licensed.