Agent Precompile

component identity

Discovery + agent-wallet index — 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 lookup of the agent IDs whose ERC-8004 agent wallet is wallet, read from a chain-side index). Everything else — registration, the agent URI, metadata, the agent wallet, ERC-721 ownership — goes directly to the ERC-8004 IdentityRegistry preinstall at 0x8004000000000000000000000000000000000001, which has no attest or revoke function.

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 from a contract is prohibitively expensive, so the precompile reads a chain-side index keyed by agent wallet: it is written when an agent registers (the registrant is the initial wallet) or its wallet changes, and the entry is deleted when the agent NFT is transferred. It answers "which agents use this wallet", not "which agents does this address own" — for ownership, use the registry's ERC-721 balanceOf and Transfer events. Every other agent operation calls the registry directly.

Error surface — shared with all Maroo precompiles

IAgent inherits IPrecompile. Calldata that does not match a method reverts with ABISetupFailed(string reason), not UnknownMethod: an unknown selector gives the reason no method with id: 0x…, and arguments that are too short, or a pageRequest tuple that does not decode, give the ABI decoder's message. Bytes after the last argument are ignored, and a wrong argument count does not produce InvalidNumberOfArgs. When the chain-side index rejects a lookup, for example a pageRequest with both key and offset set, getAgentIds reverts with QueryFailed(string queryMethod, string reason). See precompile-shared-errors for the full list — a single ABI covers every revert shape.
import { BaseError, ContractFunctionRevertedError, 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: "ABISetupFailed", inputs: [{ name: "reason", type: "string" }] },
] as const;

try {
  // await publicClient.readContract({ ... })
} catch (err) {
  // viem keeps the revert data on the ContractFunctionRevertedError in the cause chain, not on err.
  const revert = err instanceof BaseError ? err.walk((e) => e instanceof ContractFunctionRevertedError) : null;
  if (!(revert instanceof ContractFunctionRevertedError) || !revert.raw) throw err;
  const decoded = decodeErrorResult({ abi: iPrecompileErrorsAbi, data: revert.raw });
  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);
ESC
Type to search