hawk docs

Name your agents.

Hawk is identity for the agent economy on Base — ENS-standard .hawk names for operators, agents, and wallets. It implements the exact ENS interfaces (ERC-137 registry, standard resolver profiles, ENSIP namehash, reverse records, a UniversalResolver), so the ENS tooling you already use works unchanged.

An agent that claims to be yours either resolves under your name — or it doesn’t: bot1.acme.hawk cryptographically hangs off acme.hawk.

The agent pattern.

  1. Register and wrap your operator name. acme.hawk → wrap it (one click in the app, or HawkWrapper.wrapETH2LD). Wrapping turns your name into a parent that can issue subname tokens.
  2. Issue a subname per agent. Subnames are real ERC-1155 tokens — transferable, sellable, revocable like any asset.
    HawkWrapper.setSubnodeOwner(
      namehash("acme.hawk"),
      "bot1",
      agentAccount,  // the agent's smart account
      0,             // fuses — or PARENT_CANNOT_CONTROL to emancipate
      0
    )
    Then point it at the agent: PublicResolver.setAddr(node, agentAccount).
  3. Give the agent a capability card — text records. The machine-readable profile any counterparty can read in one call:
    keyvalue
    urlthe agent's API / A2A endpoint
    descriptionwhat this agent does
    agent.capabilitiescomma-separated verbs: swap,lend,rebalance
    agent.modelmodel / runtime identifier
    agent.operatoracme.hawk — walk up to the human
    avatarimage URI
  4. Set the agent's primary name. The agent's account calls ReverseRegistrar.setName("bot1.acme.hawk") — every explorer and dapp that renders reverse resolution now shows the agent's name.

Verify a counterparty agent.

import { createPublicClient, http } from "viem";
import { base, namehash } from "hawk-names";

const client = createPublicClient({ chain: base, transport: http() });

async function verifyAgent(claimedName, senderAddress) {
  const resolved = await client.getEnsAddress({ name: claimedName });
  if (resolved?.toLowerCase() !== senderAddress.toLowerCase()) return null;
  const operator = claimedName.split(".").slice(1).join(".");
  const endpoint = await client.getEnsText({ name: claimedName, key: "url" });
  const capabilities = await client.getEnsText({ name: claimedName, key: "agent.capabilities" });
  return { operator, endpoint, capabilities };
}
Why this beats a registry contract or an off-chain list. Verifiable delegation — counterparties walk the namehash chain to the operator. Revocable — keep the parent's control fuses and you can replace a compromised agent's subname; emancipate it and the identity is provably the agent's own. Composable — every wallet, explorer, and agent framework that speaks ENS resolves it with zero custom code. Tradeable — a wrapped subname (the agent's name, reputation, endpoint) can change operators atomically.

viem / wagmi — one line.

import { createPublicClient, http } from "viem";
import { base } from "hawk-names"; // ← Base with Hawk wired in as its ENS

const client = createPublicClient({ chain: base, transport: http() });

// reverse: address → name (do this in your UI)
await client.getEnsName({ address: "0x882220CF716aEF2421b6ab283E63427D81497d8c" });
// → "genesis.hawk"

// forward: name → address
await client.getEnsAddress({ name: "genesis.hawk" });

// capability records
await client.getEnsText({ name: "genesis.hawk", key: "url" });

Install: npm i hawk-names viem — source at packages/sdk, package at npmjs.com/package/hawk-names. With wagmi, pass the chain into your config and use the stock hooks.

Without the SDK.

import { defineChain } from "viem";

const baseWithHawk = defineChain({
  id: 8453,
  name: "Base",
  nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
  rpcUrls: { default: { http: ["https://mainnet.base.org"] } },
  contracts: {
    ensRegistry: { address: "0x5DAF4DF48b022Bf0Fb454DBe9CB2592d3A32b0b2" },
    ensUniversalResolver: { address: "0x1Ebbe68AfA011ADBE561De6215B1AA1Fe0e4bc6C" },
    multicall3: { address: "0xcA11bde05977b3631167028862bE2a173976CA11" },
  },
});

ethers v6: new JsonRpcProvider(rpc, { chainId: 8453, name: "base", ensAddress: "0x5DAF…b0b2" }). Payment: register and renew in ETH or USDC (Circle's canonical USDC on Base).

UX rules.

  1. Always forward-check the reverse before displaying a name (viem does this for you).
  2. Normalize before hashing — ENSIP-15 (normalize from hawk-names or viem/ens).
  3. Names expire. A resolved name is valid now; don't cache beyond normal UI windows.

Indexed data.

Names, owners, expiries, subnames, and auctions over GraphQL — CORS open, call it straight from your dapp or agent:

api.dothawk.xyz/graphql

curl -s https://api.dothawk.xyz/graphql \
  -H "content-type: application/json" \
  -d '{"query":"{ names(orderBy: \"expiresAt\", orderDirection: \"desc\", limit: 20) { items { label owner expiresAt wrapped } totalCount } }"}'

Addresses.

Base mainnet, chainId 8453 — live, sources on Blockscout. Owned by a 2-of-3 Safe: 0xd0eC82124401A30d8337FEF77899e883bb12Df0b.

contractaddress
HawkRegistry0x5DAF4DF48b022Bf0Fb454DBe9CB2592d3A32b0b2
UniversalResolver0x1Ebbe68AfA011ADBE561De6215B1AA1Fe0e4bc6C
PublicResolver0xC7cAB8Af20fF52346F4e93c143Cc8e9d6384e26b
ReverseRegistrar0x4221F2953294ec98Ddf54964DB293E281db71daB
DefaultReverseRegistrar0x34acC481E26Ee9566d868F455BecD05353Bae257
HawkRegistrarController0x65Acb254B2EF5af1FDFf7B8C77427Ca051Ff4F71
HawkBaseRegistrar0x83dcABD50E531325C76b9CB07F4C04Aca187722E
HawkWrapper0x0B922e8B56c778667AbDbBBa522880F98362c6C8
HawkPriceOracle0x697b2C75652b6895D507b9C8E2Cb3b3C8EfccA29
HawkReservedList0x351c51AA3079e7f7EbeFb5079A92279b22a0AB6c
HawkMetadata0xA972f32580C2DD8eEf379e27B4B91b205BF3437F
GatewayProvider0x4Bebe0C9073167d9772E89f272100036EC97F2ff

Addresses come from the deploy script's own record (contracts/deployments/hawk-base.json), so published addresses can never drift from what's on chain. USDC: 0x8335…2913 (Circle, canonical on Base).