Agent Precompile
Discovery + indexed reverse lookup — resolves the ERC-8004 IdentityRegistry / ReputationRegistry addresses and answers getAgentIds(wallet); failures surface through the shared IPrecompile error set.
The Agent precompile at 0x100000000000000000000000000000000000000A is a thin view-only surface. It exposes getParams() (returning the ERC-8004 identityRegistry and reputationRegistry addresses this chain resolves) and getAgentIds(wallet, pageRequest) (a paginated reverse lookup from a wallet to its agent IDs, read from a chain-side index). All other agent operations — register, attest, revoke, metadata — go directly to the ERC-8004 IdentityRegistry preinstall at 0x8004000000000000000000000000000000000001.
The Solidity interface
Straight from
IAgent.sol. Note that IAgent inherits IPrecompile, so it does not declare its own error types — every failure uses the shared surface described below.import {PageRequest, PageResponse} from "cosmos-evm-contracts/precompiles/common/Types.sol";
import {IPrecompile} from "../common/interfaces/IPrecompile.sol";
interface IAgent is IPrecompile {
struct Params {
address identityRegistry;
address reputationRegistry;
}
function getParams() external view returns (Params memory);
function getAgentIds(address wallet, PageRequest calldata pageRequest)
external
view
returns (uint256[] memory agentIds, PageResponse calldata pageResponse);
} Why the precompile exists
Two problems it solves. First, dApps should not hard-code per-network addresses for the ERC-8004 registries —
getParams() returns them at runtime. Second, iterating IdentityRegistry storage to find every agent belonging to a wallet is prohibitively expensive on-chain; the precompile reads from a chain-side reverse index that is maintained on every agent registration and transfer. That is the sole reason getAgentIds lives on a precompile at all — every other agent operation calls the standard registry directly.Error surface — shared with all Maroo precompiles
IAgent inherits
IPrecompile. Argument-shape failures surface as typed errors from the shared set: InvalidNumberOfArgs(uint256 expected, uint256 got) for wrong arity, InvalidAddress(string bad) for a non-address in the wallet slot, InvalidPageRequest(string method, uint256 index, string value) for a malformed pagination tuple, and UnknownMethod(string methodName) for an unrecognized selector. Underlying chain-index failures surface as QueryFailed(string queryMethod, string reason), normalized through the shared SDK error registry when the reason maps to a known SDK code. See precompile-shared-errors for the full list — a single ABI covers every revert shape.import { decodeErrorResult } from "viem";
const iPrecompileErrorsAbi = [
{ type: "error", name: "InvalidNumberOfArgs",
inputs: [{ name: "expected", type: "uint256" }, { name: "got", type: "uint256" }] },
{ type: "error", name: "InvalidAddress", inputs: [{ name: "bad", type: "string" }] },
{ type: "error", name: "InvalidPageRequest",
inputs: [
{ name: "method", type: "string" },
{ name: "index", type: "uint256" },
{ name: "value", type: "string" },
] },
{ type: "error", name: "QueryFailed",
inputs: [{ name: "queryMethod", type: "string" }, { name: "reason", type: "string" }] },
{ type: "error", name: "UnknownMethod", inputs: [{ name: "methodName", type: "string" }] },
] as const;
try {
// await publicClient.readContract({ ... })
} catch (err: any) {
const decoded = decodeErrorResult({ abi: iPrecompileErrorsAbi, data: err.data });
console.error(decoded.errorName, decoded.args);
} Reading IdentityRegistry after resolving addresses
Once
getParams() gives you the IdentityRegistry address, all other operations use it directly — the precompile never re-exposes registration, transfer, or metadata calls. Combine the two calls to page through a wallet's agent IDs and then hydrate each one from the registry.import { createPublicClient, http } from "viem";
const AGENT_PRECOMPILE = "0x100000000000000000000000000000000000000A" as const;
const agentPrecompileAbi = [
{ type: "function", name: "getParams", stateMutability: "view",
inputs: [],
outputs: [{ type: "tuple", components: [
{ name: "identityRegistry", type: "address" },
{ name: "reputationRegistry", type: "address" },
]}] },
] as const;
const publicClient = createPublicClient({ transport: http("https://rpc-testnet.maroo.io") });
const { identityRegistry } = await publicClient.readContract({
address: AGENT_PRECOMPILE,
abi: agentPrecompileAbi,
functionName: "getParams",
});
// Now call `identityRegistry` directly for register / transfer / metadata.
console.log("IdentityRegistry:", identityRegistry);