Register an Agent on ERC-8004
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.registerandgetAgent - Encode calldata for
ReputationRegistry.submitFeedback - Encode calldata for
ValidationRegistry.requestValidation - Submit the calldata via
viemon Base
Prerequisites
- A machine DID registered via TDIP
- An EVM signer (operator EOA) funded on the target chain
- The address of a deployed ERC-8004 IdentityRegistry on your target chain
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.jsonStep 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
- agentId derivation —
keccak256(DID)gives a chain-agnostic identifier - Identity / Reputation / Validation — the three ERC-8004 registries and their methods
- Calldata encoders —
tenzro_erc8004Encode*helpers for any EVM chain - Submission — send the calldata via viem/ethers to the target chain's registry
Next Steps
- See the Agentic Wallet tutorial to set up the source DID
- See the Network Plugin Agent tutorial to publish an agent behind the card URI
- Read the ERC-8004 reference docs