Skip to content

Step 2: Register in the Agent Registry

Goal

Register your agent on-chain. After this step, your agent has a unique numeric ID, a public record, and is recognized by infrastructure providers that perform KYA (Know Your Agent) checks.

The SDK handles signing, nonce management, and relayer communication automatically.

from agentenregister import AgentRegistry

registry = AgentRegistry(
    chain="base_sepolia",
    registry_address="0x2EFaB5B3BEf49E56a6Ce1dcB1A39EF63C312EA23",
    private_key="0x_your_agent_private_key",
    relayer_url="https://relay.theagentregistry.org",
)

agent_id = registry.register(
    haftungsperson="0x_responsible_human_wallet",
    agent_wallet=registry.account.address,
    capabilities=["web_browsing", "content_creation"],
    operational_scope="Autonomous content creation agent",
    constitution_text="I will operate ethically and transparently.",
)
print(f"Agent ID: {agent_id}")
import { AgentRegistry } from "@agentenregister/sdk";

const registry = new AgentRegistry({
    chain: "base_sepolia",
    registryAddress: "0x2EFaB5B3BEf49E56a6Ce1dcB1A39EF63C312EA23",
    privateKey: "0x_your_agent_private_key",
    relayerUrl: "https://relay.theagentregistry.org",
});

const agentId = await registry.register({
    haftungsperson: "0x_responsible_human_wallet",
    agentWallet: registry.signerAddress,
    capabilities: ["web_browsing", "content_creation"],
    operationalScope: "Autonomous content creation agent",
    constitutionText: "I will operate ethically and transparently.",
});
console.log(`Agent ID: ${agentId}`);

Method B: Raw Implementation (No SDK)

If you cannot use the SDK, here is the complete gasless registration flow using only standard libraries.

from web3 import Web3
from eth_account import Account
from eth_account.messages import encode_structured_data
import requests
import json

# ── Configuration ─────────────────────────────────────────────
PRIVATE_KEY = "0x_your_agent_private_key"
RELAYER_URL = "https://relay.theagentregistry.org"
REGISTRY_ADDRESS = "0x2EFaB5B3BEf49E56a6Ce1dcB1A39EF63C312EA23"
HAFTUNGSPERSON = "0x_responsible_human_wallet"
CHAIN_ID = 84532

account = Account.from_key(PRIVATE_KEY)
AGENT_WALLET = account.address

# ── Step 1: Get forwarder address and nonce from relayer ──────
domain = requests.get(f"{RELAYER_URL}/domain").json()
FORWARDER_ADDRESS = domain["verifyingContract"]

nonce_resp = requests.get(f"{RELAYER_URL}/nonce/{account.address}").json()
nonce = int(nonce_resp["nonce"])

# ── Step 2: Encode registerAgent calldata ─────────────────────
w3 = Web3()
REGISTRY_ABI = json.loads(
    '[{"inputs":[{"name":"_haftungsperson","type":"address"},'
    '{"name":"_agentWallet","type":"address"},'
    '{"name":"_constitutionHash","type":"bytes32"},'
    '{"name":"_capabilityHash","type":"bytes32"},'
    '{"name":"_operationalScope","type":"string"},'
    '{"name":"_parentAgentId","type":"uint256"},'
    '{"name":"_selfModifying","type":"bool"}],'
    '"name":"registerAgent",'
    '"outputs":[{"name":"agentId","type":"uint256"}],'
    '"stateMutability":"nonpayable","type":"function"}]'
)
contract = w3.eth.contract(address=REGISTRY_ADDRESS, abi=REGISTRY_ABI)

# Hash capabilities deterministically (sorted JSON, compact separators)
capabilities = ["web_browsing", "content_creation"]
cap_hash = Web3.keccak(
    text=json.dumps(sorted(capabilities), separators=(",", ":"))
)

# Hash the constitution text
constitution_hash = Web3.keccak(
    text="I will operate ethically and transparently."
)

calldata = contract.functions.registerAgent(
    Web3.to_checksum_address(HAFTUNGSPERSON),
    Web3.to_checksum_address(AGENT_WALLET),
    constitution_hash,
    cap_hash,
    "Autonomous content creation agent",
    0,      # parentAgentId (0 = root agent, no parent)
    False,  # selfModifying
).build_transaction({"gas": 0, "gasPrice": 0, "nonce": 0})["data"]

# ── Step 3: Sign EIP-712 typed data ───────────────────────────
typed_data = {
    "types": {
        "EIP712Domain": [
            {"name": "name", "type": "string"},
            {"name": "version", "type": "string"},
            {"name": "chainId", "type": "uint256"},
            {"name": "verifyingContract", "type": "address"},
        ],
        "ForwardRequest": [
            {"name": "from", "type": "address"},
            {"name": "to", "type": "address"},
            {"name": "value", "type": "uint256"},
            {"name": "gas", "type": "uint256"},
            {"name": "nonce", "type": "uint256"},
            {"name": "deadline", "type": "uint48"},
            {"name": "data", "type": "bytes"},
        ],
    },
    "primaryType": "ForwardRequest",
    "domain": {
        "name": "MinimalForwarder",
        "version": "1",
        "chainId": CHAIN_ID,
        "verifyingContract": FORWARDER_ADDRESS,
    },
    "message": {
        "from": account.address,
        "to": REGISTRY_ADDRESS,
        "value": 0,
        "gas": 800000,
        "nonce": nonce,
        "deadline": 0,
        "data": calldata,
    },
}

encoded = encode_structured_data(typed_data)
signed = account.sign_message(encoded)

# ── Step 4: Submit to relayer ─────────────────────────────────
result = requests.post(f"{RELAYER_URL}/relay", json={
    "request": {
        "from": account.address,
        "to": REGISTRY_ADDRESS,
        "value": "0",
        "gas": "800000",
        "nonce": str(nonce),
        "deadline": "0",
        "data": calldata,
    },
    "signature": signed.signature.hex(),
}).json()

print(f"Registered! TX: {result['transactionHash']}")
import { ethers } from "ethers";

// ── Configuration ─────────────────────────────────────────────
const PRIVATE_KEY = "0x_your_agent_private_key";
const RELAYER_URL = "https://relay.theagentregistry.org";
const REGISTRY_ADDRESS = "0x2EFaB5B3BEf49E56a6Ce1dcB1A39EF63C312EA23";
const HAFTUNGSPERSON = "0x_responsible_human_wallet";
const CHAIN_ID = 84532;

const provider = new ethers.JsonRpcProvider("https://sepolia.base.org");
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

// ── Step 1: Get forwarder address and nonce from relayer ──────
const domainRes = await fetch(`${RELAYER_URL}/domain`);
const domain = await domainRes.json();
const FORWARDER_ADDRESS = domain.verifyingContract;

const nonceRes = await fetch(`${RELAYER_URL}/nonce/${wallet.address}`);
const nonceData = await nonceRes.json();
const nonce = nonceData.nonce;

// ── Step 2: Encode registerAgent calldata ─────────────────────
const REGISTRY_ABI = [
    "function registerAgent(address,address,bytes32,bytes32,string,uint256,bool) returns (uint256)",
];
const contract = new ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, wallet);

// Hash capabilities deterministically (sorted JSON)
const capabilities = ["web_browsing", "content_creation"];
const capHash = ethers.keccak256(
    ethers.toUtf8Bytes(JSON.stringify([...capabilities].sort()))
);

// Hash the constitution text
const constitutionHash = ethers.keccak256(
    ethers.toUtf8Bytes("I will operate ethically and transparently.")
);

const calldata = contract.interface.encodeFunctionData("registerAgent", [
    ethers.getAddress(HAFTUNGSPERSON),
    ethers.getAddress(wallet.address),
    constitutionHash,
    capHash,
    "Autonomous content creation agent",
    0,      // parentAgentId (0 = root agent)
    false,  // selfModifying
]);

// ── Step 3: Sign EIP-712 typed data ───────────────────────────
const request = {
    from: wallet.address,
    to: REGISTRY_ADDRESS,
    value: "0",
    gas: "800000",
    nonce: String(nonce),
    deadline: "0",
    data: calldata,
};

const signature = await wallet.signTypedData(
    {
        name: "MinimalForwarder",
        version: "1",
        chainId: BigInt(CHAIN_ID),
        verifyingContract: FORWARDER_ADDRESS,
    },
    {
        ForwardRequest: [
            { name: "from", type: "address" },
            { name: "to", type: "address" },
            { name: "value", type: "uint256" },
            { name: "gas", type: "uint256" },
            { name: "nonce", type: "uint256" },
            { name: "deadline", type: "uint48" },
            { name: "data", type: "bytes" },
        ],
    },
    request,
);

// ── Step 4: Submit to relayer ─────────────────────────────────
const relayRes = await fetch(`${RELAYER_URL}/relay`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ request, signature }),
});

const result = await relayRes.json();
console.log(`Registered! TX: ${result.transactionHash}`);

Parameters

Parameter Type Required Description
haftungsperson address Yes Ethereum address of the legally responsible human entity (Haftungsperson). Every agent must have one.
agent_wallet address Yes The agent's own operational wallet address (derived from its private key).
capabilities string[] No List of capability identifiers (e.g., ["web_browsing", "content_creation"]). Hashed on-chain.
operational_scope string No Human-readable description of the agent's business purpose (Unternehmensgegenstand).
constitution_text string No The agent's constitutional rules or ethical guidelines. Hashed on-chain.
parent_agent_id uint256 No ID of the parent agent. Use 0 for root agents (no parent).
self_modifying bool No Whether the agent can modify its own code. Defaults to false.

Expected Output

Agent ID: 42

The agent ID is a sequential integer assigned by the contract. It is your permanent identifier in the registry.

Verify Registration

After registering, confirm your agent exists on-chain:

info = registry.get_agent(agent_id)
assert info.is_active, "Agent should be Active"
assert info.agent_wallet == registry.account.address, "Wallet mismatch"
print(f"Agent #{info.agent_id} is {info.status_name}")
print(f"  Haftungsperson: {info.haftungsperson}")
print(f"  Scope: {info.operational_scope}")
const info = await registry.getAgent(agentId);
console.assert(info.status === 0, "Agent should be Active");
console.assert(info.agentWallet === registry.signerAddress, "Wallet mismatch");
console.log(`Agent #${info.agentId} is Active`);
console.log(`  Haftungsperson: ${info.haftungsperson}`);
console.log(`  Scope: ${info.operationalScope}`);

Or query the REST API (no blockchain call needed):

curl https://api.theagentregistry.org/api/v1/agent/42

Common Errors

Error Cause Resolution
"Wallet already registered to another agent" The agent wallet address is already in use Generate a new key pair (Step 1)
"Parent agent not found" parent_agent_id does not exist Verify the parent ID or use 0 for root agents
"Parent agent not active" Parent agent is Suspended, Revoked, or Terminated Reactivate the parent first (requires regulator)
"Max generation depth exceeded" Child would exceed generation depth of 10 Cannot spawn agents deeper than 10 generations
"Invalid signature or nonce mismatch" Signature verification failed at the relayer Re-fetch the nonce from /nonce/:address and re-sign
"Relayer only forwards to the Agentenregister contract" Request to field is not the registry address Use 0x2EFaB5B3BEf49E56a6Ce1dcB1A39EF63C312EA23

For the full error reference, see Errors.

Next Step

Proceed to Step 3: Attest Compliance. Your first attestation is automatic at registration time, so your next attestation is due within 7 days.