AgentsScan
API Documentation
On-chain identity for AI agents using ENS subnames and ENSIP-25 verification.
Overview
AgentsScan provides a way to register AI agents on-chain by issuing them ENS subnames, setting ENSIP-25 attestation text records, and making everything cryptographically verifiable. There are two ways to register an agent:
UI Flow
Connect your wallet, fill in the form, and sign transactions directly from the dashboard. Your wallet executes all 5 on-chain operations. No backend needed.
API / Relayer Flow
Sign an EIP-191 message off-chain, send it to the relayer API, and the server executes all on-chain operations on your behalf. Best for programmatic use.
Quick Start
Clone the repository and install dependencies. The project uses Next.js 16, wagmi, viem, and Tailwind CSS.
git clone https://gitlab.com/proskairos/agentsscan.git
cd agentsscan
npm installCreate a .env.local file in the project root with the following variables. You need a funded relayer wallet on Sepolia.
# Relayer private key (funded with Sepolia ETH)
RELAYER_PRIVATE_KEY=0x_your_private_key_here
# WalletConnect Project ID (get from cloud.walletconnect.com)
NEXT_PUBLIC_WC_PROJECT_ID=your_project_idStart the development server. The app runs on localhost:3000 by default.
npm run devArchitecture
The system has three key addresses that you should understand before using the API:
The ENS name owner. This wallet must own the parent ENS name (e.g. myname.eth). In the API flow, you sign an EIP-191 message to prove ownership. The relayer then acts on your behalf.
The ETH address that gets set as the ENS record for the subname. This is the on-chain identity of your AI agent. It can be the same as your connected wallet or a separate hot wallet the agent uses to transact.
A server-side wallet defined by RELAYER_PRIVATE_KEY. This wallet executes all on-chain transactions in the API flow. It must be funded with Sepolia ETH and approved via setApprovalForAll on the ENS Registry by the ENS owner.
Environment Setup
Before using the API flow, there is one on-chain prerequisite you must complete: approve the relayer wallet to create subnodes under your ENS name.
Approve the Relayer (One-time Setup)
The relayer needs permission to call setSubnodeOwner on your parent ENS name. You must call setApprovalForAll(relayerAddress, true) on the ENS Registry contract. This only needs to be done once per ENS name.
// Target: ENS Registry
// Address: 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e
// Function: setApprovalForAll(address operator, bool approved)
//
// operator: <your relayer wallet address>
// approved: trueimport { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider(
"https://ethereum-sepolia.publicnode.com"
);
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
const ensRegistry = new ethers.Contract(
"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
["function setApprovalForAll(address,bool)"],
signer
);
// Approve relayer to create subnodes under your name
const tx = await ensRegistry.setApprovalForAll(
RELAYER_ADDRESS, // the relayer wallet address
true
);
await tx.wait();
console.log("Approved! Relayer can now create subnodes.");API Reference
POST /api/register
Register a new AI agent via the relayer. The relayer verifies your EIP-191 signature against the on-chain ENS owner, then executes all 5 on-chain operations.
Request Body
{
"parentName": "myname.eth",
"sublabel": "trader-bot",
"agentWalletAddress": "0x1234...abcd",
"signature": "0xabcdef..."
}Parameters
parentNamerequiredThe parent ENS name you own. Must end with .eth.
sublabelrequiredThe sublabel for the agent. The full ENS name will be {sublabel}.{parentName}.
agentWalletAddressrequiredThe ETH address to set as the ENS record for this agent. Can be the same as the owner or a separate agent wallet.
signaturerequiredAn EIP-191 signature of the message: I authorize registering {sublabel}.{parentName}. The signer must be the on-chain ENS owner.
Success Response (200)
{
"success": true,
"ensName": "trader-bot.myname.eth",
"agentId": 1,
"relayer": "0xRE...AYER",
"transactions": {
"setSubnodeOwner": "0xabc...",
"setResolver": "0xdef...",
"setAddr": "0x123...",
"registerAgent": "0x456...",
"setEnsip25Text": "0x789..."
}
}Error Response (4xx / 5xx)
{
"success": false,
"error": "Missing required fields: parentName, sublabel, agentWalletAddress, signature"
}Error Codes
Testing the Flow
Here is a complete step-by-step guide to test the API flow end to end. This assumes you already have a funded wallet with a Sepolia ENS name.
Step 1: Get a Sepolia ENS name
Go to app.ens.domains, connect your wallet, switch to Sepolia, and register any available .eth name. This will be your parentName.
Step 2: Fund the relayer wallet
Generate a new wallet (or use an existing one), set it as RELAYER_PRIVATE_KEY in .env.local, and fund it with Sepolia ETH from a faucet.
Step 3: Approve the relayer
Call setApprovalForAll(relayerAddress, true) on the ENS Registry from your ENS owner wallet. See the Environment Setup section for details.
Step 4: Construct the signature message
The EIP-191 message must follow this exact format. The relayer will verify this message against the on-chain ENS owner.
I authorize registering {sublabel}.{parentName}
// Example:
I authorize registering trader-bot.myname.ethStep 5: Sign the message
Sign the message from your ENS owner wallet using any Web3 library. Here are examples for both wagmi and ethers.js:
import { signMessage } from "wagmi/actions";
const parentName = "myname.eth";
const sublabel = "trader-bot";
const message = `I authorize registering ${sublabel}.${parentName}`;
const signature = await signMessage({ message });
// signature => "0xabcdef..."import { ethers } from "ethers";
const signer = new ethers.Wallet(PRIVATE_KEY);
const message = "I authorize registering trader-bot.myname.eth";
const signature = await signer.signMessage(message);
// signature => "0xabcdef..."Step 6: Send the API request
With the signature in hand, make a POST request to the relayer endpoint. The server will verify your signature, check ENS ownership, and execute all 5 on-chain operations.
const response = await fetch("/api/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
parentName: "myname.eth",
sublabel: "trader-bot",
agentWalletAddress: "0xYourAgentWalletAddress",
signature: "0xYourEip191Signature",
}),
});
const data = await response.json();
if (data.success) {
console.log("Agent registered!", data.ensName);
console.log("Agent ID:", data.agentId);
console.log("Transactions:", data.transactions);
} else {
console.error("Failed:", data.error);
}curl -X POST https://your-domain.com/api/register \
-H "Content-Type: application/json" \
-d '{
"parentName": "myname.eth",
"sublabel": "trader-bot",
"agentWalletAddress": "0xYourAgentWallet",
"signature": "0xYourEip191Sig"
}'trader-bot.myname.eth).Deployed Contracts
All contracts are deployed on Sepolia testnet. These addresses are configured in src/lib/wagmi.ts.
0x67D2c322F409B15f5e6e1Bd1d092fc71c1010AC8
Stores agent records with auto-incrementing IDs. Functions: registerAgent(string) returns uint256 ID, agents(uint256) returns (owner, ensName, exists).
ENS Registry (Sepolia)
0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e
The official Sepolia ENS registry. Used for setSubnodeOwner, setResolver, setApprovalForAll, and owner lookups.
Public Resolver (Sepolia)
0xE99638b40E4Fff0129D56f03b55b6bbC4BBE49b5
The official Sepolia public resolver. Used for setAddr, setText, and reading ENS text records for verification.
On-chain Steps
Registering an agent requires exactly 5 on-chain transactions, executed in order. Here is what each step does and why the order matters.
Takes ownership of the subnode (e.g. trader-bot.myname.eth) from the parent ENS owner. The new owner becomes the relayer address (API flow) or agent wallet address (UI flow). This must happen first because you need ownership to set resolver and records.
// ENS Registry: setSubnodeOwner(node, label, owner)
// node = namehash("myname.eth")
// label = labelhash("trader-bot")
// owner = relayerAddress (API) or agentWallet (UI)Points the subnode to the Sepolia Public Resolver. This must be set before you can call setAddr or setText on the subname, because those functions are called on the resolver contract.
// ENS Registry: setResolver(node, resolver)
// node = namehash("trader-bot.myname.eth")
// resolver = 0xE99638b40E4Fff0129D56f03b55b6bbC4BBE49b5Sets the primary ETH address record for the agent subname. When someone resolves trader-bot.myname.eth, they get this address back. This is the agent's on-chain identity.
// Public Resolver: setAddr(node, addr)
// node = namehash("trader-bot.myname.eth")
// addr = 0xYourAgentWalletAddressCalls the MinimalAgentRegistry contract to create a new agent record. The contract auto-assigns an incrementing ID and stores the ENS name and caller address (which is the relayer in the API flow). Returns an AgentRegistered event with the ID.
// Agent Registry: registerAgent(string _ensName)
// _ensName = "trader-bot.myname.eth"
// Returns: uint256 agentId (via event)Sets an ENS text record using the ENSIP-25 agent registration key format. The key encodes the registry address and agent ID so any verifier can cross-check the on-chain Agent struct. This is the cryptographic proof that links the ENS name to the agent registration.
// Public Resolver: setText(node, key, value)
// node = namehash("trader-bot.myname.eth")
// key = "agent-registration[0x67D2...0AC8][1]"
// value = "true"