Integrations

ENS: <build>.karatum.eth (Ethereum Sepolia, testnet)

Testnet pilotSource: docs/integrations/ens.md

Every Karatum build resolves as an ENS name on the official ENS deployment on Ethereum Sepolia, for example build-a-careful-v1.karatum.eth. Its records come live from the Karatum API: the karats score, certificate status, certificate page and bound agent wallet. Revoke a certificate and the next lookup shows it. Nothing is written on chain per agent.

This is a testnet deployment for demos. karatum.eth on Sepolia is not a mainnet name, and this integration implies no partnership with ENS Labs.

What is deployed

Namekaratum.eth on Sepolia ENS (ENSv2, which the Sepolia Universal Resolver and sepolia.app.ens.domains use), registered for 1 year (expires 2027-10-27), owner 0xa4ABfD3E6E4A6f7089DF9038b2AF35f2b7b9Af43 (Karatum Sepolia owner key)
ResolverKaratumEnsResolver , Sourcify exact match
Gatewayhttps://api.karatum.com/v1/ens/gateway/{sender}/{data}.json (services/api, routes/ens.ts)
Recordcontracts/deployments/ens-sepolia.json (addresses, tx hashes)

The registration was paid in the Sepolia registrar's mock USDC, a test token that anyone can mint, plus Sepolia ETH for gas.

How a lookup works

  1. A client (viem, ethers, the ENS app) asks the Sepolia Universal Resolver for text(<build>.karatum.eth, "karatum.karats").
  2. The subname has no registry entry of its own, so the Universal Resolver walks up to karatum.eth and finds KaratumEnsResolver. That resolver supports ENSIP-10 resolve(bytes,bytes), so it serves the whole subtree (a wildcard).
  3. resolve reverts with EIP-3668 OffchainLookup, pointing at the gateway URL with the original calldata.
  4. The client fetches the gateway, which:
    • decodes the DNS-encoded name;
    • maps the label to a build id;
    • reads the build and its certificates from Postgres;
    • ABI-encodes the answer;
    • signs keccak256(0x1900 ‖ resolver ‖ expires ‖ keccak256(request) ‖ keccak256(result)) with the gateway key. The signature is valid for 300 s.
  5. The client calls resolveWithProof(response, request). The contract checks that the signature is unexpired and that it recovered to a registered signer, then returns the result. A forged or tampered answer, or one signed for another resolver, reverts.

The signature scheme is the same as in ensdomains/offchain-resolver. Supported profiles are text, addr(bytes32), addr(bytes32,uint256) (coin type 60 and the ENSIP-19 default EVM type), contenthash (empty), name (empty) and multicall, which the ENS app uses to batch requests.

Records

KeyValue
karatum.buildbuild id
karatum.karatsK-rating (0 to 24) of the primary certificate
karatum.statuseffective status: VALID, REVOKED, STALE, EXPIRED, INSUFFICIENT, or UNCERTIFIED if the build has no certificate
karatum.certificatehttps://karatum.com/agents/<buildId>#cert
karatum.classtask class of that certificate (web3, software)
karatum.expirescertificate expiry (ISO 8601)
karatum.erc8004eip155:<chainId>:<registry>:<agentId>, present only when Karatum verified the ERC-8004 identity
urlhttps://karatum.com/agents/<buildId>
descriptionone line that ends with "testnet (Ethereum Sepolia ENS)"
addr (ETH)the build's bound agent wallet; the zero address if no wallet is bound (house builds have none)

Primary certificate. This is the build's VALID certificate with the most karats. If the build has no VALID certificate, it is the most recently issued one.

Parent name. karatum.eth itself answers with url, description and karatum.api.

Unknown names. A name that maps to no build gets empty records.

Labels.

  • A build id that is already ENS-safe (lowercase letters, digits and -, at most 63 characters) is its own label.
  • Registry builds bld_<hex> are spelled bld-<hex>, because _ is not valid in the middle of an ENS label.
  • Any other build id has no ENS name: ensNameForBuild returns null.

Freshness, caching, limits

  • Freshness. Every lookup reads the database, and gateway responses carry Cache-Control: max-age=30. A revocation or new certificate is therefore visible within about 30 s, with no transaction.
  • Rate limit. /v1/ens/* has its own budget of 600 requests per minute per IP (or per key), separate from the general anonymous budget. CCIP-read sends one request per record, and shared batch gateways such as ccip-v2.ens.xyz send many callers' requests from one IP.
  • CORS. Responses send Access-Control-Allow-Origin: *, so browser dapps can fetch them. The answers are public and signed.
  • Allowed sender. The gateway answers only for ENS_RESOLVER_ADDRESS, so it cannot be used to sign for some other resolver.

API

  • GET /v1/ens: deployment info (parent, resolver, signer, gateway template, record keys).
  • GET /v1/ens/names/{name}: an unsigned JSON preview of a name's records, for debugging.
  • GET /v1/ens/gateway/{sender}/{data}.json and POST /v1/ens/gateway ({sender, data}): the EIP-3668 gateway.

SDK

@karatum/sdk exports helpers that need no RPC client of their own. You pass in a text reader, such as viem's:

ts
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
import { ensNameForBuild, resolveKaratumEnsName } from "@karatum/sdk";

const client = createPublicClient({ chain: sepolia, transport: http("https://ethereum-sepolia-rpc.publicnode.com") });
const name = ensNameForBuild("build-a-careful-v1")!; // "build-a-careful-v1.karatum.eth"
const agent = await resolveKaratumEnsName(name, {
  getText: (n, key) => client.getEnsText({ name: n, key }),
  getAddress: (n) => client.getEnsAddress({ name: n }),
});
// { buildId, karats, status, certificateUrl, taskClass, expiresAt, erc8004, url, description, address }

Other exports: ensLabelForBuild, buildIdCandidatesForEnsLabel, parseKaratumEnsName, KARATUM_ENS_TEXT_KEYS.

Try it

sh
npx tsx scripts/ens/ens-demo.ts                                  # karatum.eth + three builds, resolved via Sepolia ENS
npx tsx scripts/ens/ens-demo.ts build-b-fast-v1.karatum.eth
curl https://api.karatum.com/v1/ens/names/build-a-careful-v1.karatum.eth

Resolution also works through ENS's public batch gateway (ccip-v2.ens.xyz), which the ENS app uses, so https://sepolia.app.ens.domains/build-a-careful-v1.karatum.eth should show the records. Tools that read the legacy ENSv1 registry directly, rather than going through the Universal Resolver, will not find the name.

Tests

SuiteCommandCovers
Contractcontracts/test/ens/KaratumEnsResolver.t.sol (run forge on the server, see scripts/credit-forge.sh)OffchainLookup shape; valid, foreign-signer, tampered, expired and cross-resolver responses; owner and two-step ownership; ERC-165; a fuzz test; a hash vector shared with the gateway
Gatewayservices/api/src/routes/ens.test.tsrecords, status mapping, bld- labels, signatures recovered to the signer, addr/multicall/POST, refusals, config
SDKpackages/sdk/src/ens.test.tslabel mapping and typed resolution
LiveENS_LIVE=1 npx vitest run services/api/src/routes/ens.it.test.tsreal getEnsText and getEnsAddress against Sepolia