Tenzro Testnet is live —request testnet TNZO
← Back to Tutorials

Register an Agent on ERC-8004

AI AgentsIntermediate25 min

ERC-8004 defines three on-chain registries for autonomous AI agents: Identity, Reputation, and Validation. Tenzro uses a deterministic mapping agentId = keccak256(DID) so the same agent has the same on-chain identifier across Ethereum, Base, Arbitrum, and any EVM chain with a deployed registry. This tutorial shows how to derive an agentId, encode calldata for all three registries, and submit it.

What You'll Build

  • Derive an agentId from a Tenzro machine DID
  • Encode calldata for IdentityRegistry.register and getAgent
  • Encode calldata for ReputationRegistry.submitFeedback
  • Encode calldata for ValidationRegistry.requestValidation
  • Submit the calldata via viem on Base

Prerequisites

Step 1: Derive the agentId

The agentId is canonical: keccak256(DID_utf8_bytes) truncated to 256 bits. Same DID ⇒ same agentId everywhere.

tenzro_erc8004DeriveAgentId({
  "did": "did:tenzro:machine:abc123..."
})
  -> {
    did: "did:tenzro:machine:abc123...",
    agent_id: "0x9f3a8b2c..."
  }

Step 2: Encode Register Calldata

register(string did, address operator, string agentCardUri)— the operator EOA is who may later update the agent record, and the card URI typically points to a Google A2A-compatible descriptor.

tenzro_erc8004EncodeRegister({
  "did":            "did:tenzro:machine:abc123...",
  "operator":       "0xOperatorEoa...",
  "agent_card_uri": "https://agents.example.com/.well-known/agent.json"
})
  -> {
    selector: "0x...",     // register(string,address,string) selector
    calldata: "0x..."      // ABI-encoded calldata for IdentityRegistry.register
  }

Step 3: Encode getAgent Calldata

Read-only query for an existing agent record:

tenzro_erc8004EncodeGetAgent({
  "did": "did:tenzro:machine:abc123..."
})
  -> { selector: "0x...", calldata: "0x..." }

Step 4: Encode Feedback (Reputation)

submitFeedback(uint256 targetAgentId, int8 score, string reasonUri) — scores are in [-128, 127]; aggregation is off-chain but the raw signed feedback is verifiable on-chain.

tenzro_erc8004EncodeFeedback({
  "target_did": "did:tenzro:machine:abc123...",
  "score":      100,                 // int8 in [-128, 127]
  "uri":        "ipfs://bafkreig.../review.json"
})
  -> { selector: "0x...", calldata: "0x..." }

Step 5: Encode Validation Request

requestValidation(uint256 subjectAgentId, bytes32 dataHash, string uri) — asks validators to attest that the agent produced output whose hash matches dataHash.

tenzro_erc8004EncodeRequestValidation({
  "subject_did": "did:tenzro:machine:abc123...",
  "data_hash":   "0x5f3d2a1b...",   // 32-byte hex
  "uri":         "ipfs://bafkreih.../claim.json"
})
  -> { selector: "0x...", calldata: "0x..." }

Step 6: CLI

# Derive the agentId for a DID
tenzro erc8004 derive-id \
  --did did:tenzro:machine:abc123...

# Encode calldata for IdentityRegistry.register
tenzro erc8004 encode-register \
  --did did:tenzro:machine:abc123... \
  --operator 0xOperatorEoa... \
  --uri https://agents.example.com/.well-known/agent.json

# Encode calldata for ReputationRegistry.submitFeedback
tenzro erc8004 encode-feedback \
  --target-did did:tenzro:machine:abc123... \
  --score 100 \
  --uri ipfs://bafkreig.../review.json

# Encode calldata for ValidationRegistry.requestValidation
tenzro erc8004 encode-validation \
  --subject-did did:tenzro:machine:abc123... \
  --data-hash 0x5f3d2a1b... \
  --uri ipfs://bafkreih.../claim.json

Step 7: Submit via viem

// The encoder returns raw calldata. Submit it through an EVM wallet pointing
// at the IdentityRegistry deployed on your target chain (Ethereum, Base, etc.).
import { createWalletClient, http } from "viem";
import { base } from "viem/chains";

const wallet = createWalletClient({ chain: base, transport: http() });

const encoded = await client.erc8004.encodeRegister({
  did:            "did:tenzro:machine:abc123...",
  operator:       "0xOperatorEoa...",
  agent_card_uri: "https://agents.example.com/.well-known/agent.json",
});

const txHash = await wallet.sendTransaction({
  to:   "0xIdentityRegistryOnBase...",
  data: encoded.calldata as `0x${string}`,
});

TypeScript SDK

import { TenzroClient } from "@tenzro/sdk";

const client = new TenzroClient();

// 1. Derive the canonical agentId (works off-chain, no fees)
const { agent_id } = await client.erc8004.deriveAgentId({
  did: "did:tenzro:machine:abc123...",
});
console.log("agentId (stable across chains):", agent_id);

// 2. Build register calldata
const { calldata } = await client.erc8004.encodeRegister({
  did:            "did:tenzro:machine:abc123...",
  operator:       "0xOperatorEoa...",
  agent_card_uri: "https://agents.example.com/.well-known/agent.json",
});

// 3. Submit via your preferred EVM signer (viem/ethers)

Relationship to TDIP. TDIP is the native identity layer on the Tenzro ledger (DIDs, delegation scopes, W3C VCs). ERC-8004 is the projection of that identity onto EVM chains so Ethereum-ecosystem contracts and agents can reference the same principal. The mapping is deterministic — you don't need a bridge or oracle to correlate them.

What You Learned

Next Steps