npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ensmetadata/cli

v0.5.1

Published

CLI for managing AI agent metadata on ENS using ERC-8004

Readme

@ensmetadata/cli

A command-line tool for managing AI agent identity using ENS and ERC-8004 v2.0. Aimed at developers setting up an agent end-to-end: read and write ENS text records, verify on-chain attestations, publish registration files to IPFS, and register agents on the canonical IdentityRegistry.

Status: pre-1.0. The CLI surface and output shapes may change between minor versions.

Installation

pnpm add -g @ensmetadata/cli
# or: npm i -g @ensmetadata/cli
# or: pnpm dlx @ensmetadata/cli <command>

The installed binary is ens-metadata. Requires Node.js 22 or newer.

ens-metadata --help
ens-metadata <command> --help

Quickstart

These read-only commands work with no credentials:

# Inspect any ENS name's metadata text records
ens-metadata view myagent.eth

# Look up an agent by token ID on the canonical registry
ens-metadata agent registry query 42 --chain mainnet

# Verify an on-chain social media attestation by specifying ENS name and which social media provider
ens-metadata attestation verify handle myagent.eth com.x

For write commands, see Configuration and Conventions below.

Configuration

RPC URLs

Every chain-touching command resolves an RPC URL using this precedence:

  1. --rpc <url> flag
  2. RPC_URL_<chainId> environment variable (for example RPC_URL_1, RPC_URL_11155111, RPC_URL_8453)
  3. MAINNET_RPC_URL (mainnet only)
  4. ETH_RPC_URL (any chain)
  5. A curated set of public fallback RPCs, then viem's built-in defaults

Public fallbacks are best-effort. For production use, supply your own RPC using one of the above methods.

Supported chains

When working with ERC-8004 registries, pass --chain <name> to select the network. The CLI auto-detects the canonical IdentityRegistry address for that chain (mainnet vs. testnet), so you never need to supply the contract address yourself. If --chain is omitted, mainnet is used.

Mainnets: mainnet, base, arbitrum, optimism, polygon, avalanche, bsc, linea, scroll, mantle, gnosis, celo, taiko, abstract.

Testnets: sepolia, base-sepolia, arbitrum-sepolia, optimism-sepolia, polygon-amoy, avalanche-fuji, bsc-testnet, linea-sepolia, scroll-sepolia, mantle-sepolia, celo-alfajores, monad-testnet, abstract-testnet.

The authoritative list is defined in src/lib/registry.ts.

Basenames (*.base.eth)

Subnames of base.eth (for example alice.base.eth) live on Base (chain 8453) and are read from the L2 resolver directly. The CLI auto-detects them via isBasename(name) from the SDK and routes reads — and, for set, writes — through Base. No flag changes are required for the user.

When operating on a Basename:

  • The command builds a single Base public client. The Base RPC follows RPC_URL_8453ETH_RPC_URL → public defaults.
  • --rpc <url> is bound to the chain the subject name lives on. For view alice.base.eth and attestation verify * alice.base.eth, --rpc overrides the Base client.
  • For set alice.base.eth ..., the wallet client's chain is auto-selected as Base; no manual chain flag is needed.

The 2LD base.eth itself is treated as mainnet (consistent with the SDK).

attestation verify handle/uid is single-chain: when supplied, --attester must be an ENS name on the same chain as the subject. Omit the flag to auto-resolve from the subject's chain — atst.base.eth for *.base.eth, atst.lighthousegov.eth for everything else.

Pinata (IPFS publishing)

The agent registration-file publish command uploads to IPFS via Pinata. Provide either:

  • PINATA_JWT (preferred), or
  • PINATA_API_KEY and PINATA_API_SECRET (legacy key pair)

Conventions

Structured JSON output. Every command writes a JSON object to stdout. Pipe into jq for further processing.

Private keys. Write commands take --private-key 0x<hex>. The key is used in-process for signing and is never logged. Prefer environment-injected keys over shell history (for example --private-key "$DEPLOYER_KEY").

Dry-run by default. Commands that send transactions (set, agent registry register, agent registry set-uri, agent registry set-wallet, agent registry unset-wallet) accept a --broadcast flag. Without it, the command returns a transaction preview containing the signer, target contract, encoded calldata, estimated cost, and the signer's balance. This allows you to run the command without --broadcast first, confirm the output, then re-run with --broadcast to submit.

Exit codes. Validation commands set a non-zero exit code on failure so they can gate CI pipelines. Successful runs exit 0.

Command reference

Top-level metadata commands

These operate on ENS text records for any ENS name.

view <name>

Read resolved metadata for an ENS name and, if the name declares a schema text record, fetch that schema and validate the on-chain properties against it.

ens-metadata view myagent.eth
ens-metadata view myagent.eth --ipfs-gateway https://my-gateway.example

When the name's schema record is set, the CLI fetches the schema document (preferring the bundled cache before falling back to the IPFS gateway), validates the recorded properties against it, and reports the result in matchedSchema.

Valid against the declared schema:

{
  "name": "myagent.eth",
  "resolver": "0x231b...",
  "address": "0xAbC0...",
  "class": "Agent",
  "schema": "ipfs://bafy...",
  "matchedSchema": {
    "title": "Agent",
    "version": "1.0.0",
    "uri": "ipfs://bafy...",
    "valid": true
  },
  "properties": {
    "name": "My Agent",
    "description": "Helps with on-chain research.",
    "image": "https://example.com/avatar.png"
  }
}

Invalid against the declared schema (the URI was fetched but the properties don't conform):

{
  "matchedSchema": {
    "title": "Agent",
    "version": "1.0.0",
    "uri": "ipfs://bafy...",
    "valid": false,
    "errors": [
      { "key": "image", "message": "Required field \"image\" is missing" }
    ]
  }
}

When the schema URI can't be fetched (gateway unreachable, bad CID, malformed JSON), the rest of the metadata is still returned and matchedSchema carries a soft-failure shape:

{
  "matchedSchema": {
    "uri": "ipfs://bafy...",
    "valid": false,
    "error": "Failed to fetch schema from IPFS gateway (...): HTTP 504 Gateway Timeout"
  }
}

When no schema record is declared on the name, matchedSchema is null and no fetch is performed. resolver, address, class, and schema are null when their corresponding records are unset.

Options:

  • --ipfs-gateway <origin> (env: IPFS_GATEWAY): override the gateway used to fetch the schema document. Defaults to https://ipfs.io. Has no effect when the schema CID is already in the bundled @ensmetadata/schemas registry.

*.base.eth is auto-detected and read directly from the Base L2 resolver; the resolver field carries the L2 resolver address. See Basenames for the RPC envs.

set <name> <payload>

Write text records to an ENS name from a payload file. The CLI computes a delta against the existing on-chain values and submits only the changed and deleted keys in a single multicall transaction. If the payload includes a schema URI, or one is already set on the name, the payload is validated against that schema before any write.

Schema resolution cascade:

  1. If payload.schema is set, fetch and validate against it.
  2. Otherwise read the schema text record from ENS; if set, fetch and validate.
  3. Otherwise skip schema validation.

By default empty-string entries in the payload are dropped so blank template fields don't overwrite existing records. Pass --include-empty to send empty strings (this is how you delete records via the payload).

--private-key is optional in dry-run; when omitted, the CLI reads the ENS manager directly on-chain (ensjs getOwner for mainnet, the Base L2 registry for *.base.eth) and uses that as the from-address for gas estimation. --private-key is required for --broadcast.

For *.base.eth subjects, the wallet client's chain is auto-selected as Base; no flag is needed. The dry-run output includes chain: 'base' and the broadcast explorerUrl points to https://basescan.org/tx/.... See Basenames for RPC env precedence and the --rpc semantics.

ens-metadata set myagent.eth ./payload.json
ens-metadata set myagent.eth ./payload.json --private-key "$KEY" --broadcast
ens-metadata set myagent.eth ./payload.json --private-key "$KEY" --include-empty --broadcast

Other options:

  • --ipfs-gateway <origin> (env: IPFS_GATEWAY): override the gateway used to fetch schema documents (defaults to https://ipfs.io).

Dry-run output:

{
  "dryRun": true,
  "name": "myagent.eth",
  "chain": "mainnet",
  "schema": {
    "source": "ens",
    "uri": "ipfs://bafy...",
    "validated": true
  },
  "signer": {
    "address": "0xAbC0...",
    "source": "ensManager"
  },
  "records": [
    { "key": "name", "value": "My Agent" },
    { "key": "description", "value": "Helps with on-chain research." }
  ],
  "diff": {
    "added": [{ "key": "name", "value": "My Agent" }],
    "updated": [],
    "deleted": [],
    "unchanged": []
  },
  "estimatedCost": "0.00214 ETH ($7.38)",
  "balance": "0.123456 ETH",
  "hint": "Run with --private-key 0x<KEY> --broadcast to submit on-chain."
}

estimatedCost and balance are best-effort and omitted if gas estimation fails.

The schema.source field is one of payload, ens, or none. The signer.source field is privateKey when --private-key is supplied and ensManager when it isn't. If the payload contains no changes against current on-chain values, set returns noOp: true instead of records and exits without simulating a transaction.

Broadcast output:

{
  "broadcast": true,
  "name": "myagent.eth",
  "chain": "mainnet",
  "schema": {
    "source": "ens",
    "uri": "ipfs://bafy...",
    "validated": true
  },
  "txHash": "0x9f3c...",
  "explorerUrl": "https://etherscan.io/tx/0x9f3c...",
  "diff": { "added": [], "updated": [], "deleted": [], "unchanged": [] }
}

validate <file>

Validate an ENS metadata payload against the agent schema. Exits non-zero on failure.

ens-metadata validate ./payload.json
{ "valid": true, "recordCount": 7 }
{
  "valid": false,
  "errors": [
    { "key": "name", "message": "Required" },
    { "key": "image", "message": "Invalid url" }
  ]
}

template <type>

Print an empty payload skeleton for the given metadata schema type. The output has every property keyed to an empty string (apart from class, set to its declared default, and schema, set to the IPFS URI of the published schema version). Edit the result and pass it to set.

<type> accepts either a registry id (e.g. agent, org) or a schema title (e.g. Agent, Organization), case-insensitive. By default the latest published version is used; pass --version <semver> to pin to a specific one.

ens-metadata template agent > payload.json
ens-metadata template org --version 1.0.0 > payload.json

skill

Print the bundled SKILL.md walkthrough to stdout. Used by AI assistants and for quick reference.

ens-metadata skill

agent registration-file group

Build, validate, and publish ERC-8004 v2.0 registration files. These run off-chain; the resulting IPFS URI is what you register on-chain in the next group.

agent registration-file template

Print a starter ERC-8004 v2.0 registration file. Edit before publishing.

The agentRegistry field is auto-filled with the canonical IdentityRegistry address for the chain you pass via --chain (defaulting to mainnet), so it stays in sync with what agent registry register --chain <name> will actually use. See Supported chains for the full list.

ens-metadata agent registration-file template > registration.json
ens-metadata agent registration-file template --chain base > registration.json

Example output (mainnet):

{
  "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
  "name": "My Agent",
  "description": "A short description of what this agent does and its capabilities.",
  "image": "https://example.com/agent-avatar.png",
  "services": [
    { "name": "MCP", "endpoint": "https://api.example.com/mcp", "version": "2025-11-25", "mcpTools": [], "capabilities": [] },
    { "name": "A2A", "endpoint": "https://example.com/.well-known/agent-card.json", "version": "0.3.0" },
    { "name": "agentWallet", "endpoint": "eip155:1:0x0000000000000000000000000000000000000000" }
  ],
  "registrations": [
    { "agentId": 0, "agentRegistry": "eip155:1:0x..." }
  ],
  "supportedTrust": ["reputation"],
  "active": false,
  "x402Support": false,
  "updatedAt": 1730000000
}

The agentWallet endpoint also uses the same chain id; replace the zero address with the wallet you want to associate. agentId: 0 is a placeholder; the real value is assigned when you call agent registry register.

agent registration-file validate <file>

Validate a registration file against the ERC-8004 v2.0 schema. Exits non-zero on failure.

ens-metadata agent registration-file validate ./registration.json
{ "valid": true }
{
  "valid": false,
  "errors": [
    { "path": "services.0.endpoint", "message": "Invalid url" }
  ]
}

agent registration-file publish <file>

Validate, then upload the file to IPFS via Pinata. Requires PINATA_JWT or PINATA_API_KEY plus PINATA_API_SECRET in the environment. The returned uri is the value you pass to agent registry register.

ens-metadata agent registration-file publish ./registration.json
{
  "cid": "bafkreigh2akiscaildcq6vzgxw4n2jszk2nyer3pukpekutw4lwomoiwie",
  "uri": "ipfs://bafkreigh2akiscaildcq6vzgxw4n2jszk2nyer3pukpekutw4lwomoiwie"
}

agent registry group

Interact with the canonical ERC-8004 IdentityRegistry contracts. The chain is selected with --chain; see Supported chains. All write commands follow the dry-run pattern documented in Conventions.

agent registry register <agent-uri>

Register a new agent identity on the IdentityRegistry. Mints a new token whose URI points at your published registration file.

ens-metadata agent registry register ipfs://bafk... \
  --chain mainnet --private-key "$KEY" --broadcast

Dry-run output:

The registry and to fields below are auto-detected from --chain.

{
  "dryRun": true,
  "chain": "mainnet",
  "registry": "0x...",
  "function": "register",
  "signer": "0xAbC0...",
  "to": "0x...",
  "data": "0x...",
  "value": "0",
  "estimatedCost": "0.00184 ETH ($6.34)",
  "balance": "0.123456 ETH",
  "agentUri": "ipfs://bafk...",
  "hint": "Run with --broadcast to submit on-chain."
}

Broadcast output:

{
  "broadcast": true,
  "chain": "mainnet",
  "registry": "0x...",
  "function": "register",
  "txHash": "0x9f3c...",
  "explorerUrl": "https://etherscan.io/tx/0x9f3c...",
  "agentUri": "ipfs://bafk..."
}

agent registry query <agent-id>

Read-only lookup of an agent token by ID. Returns the owner address and current agent URI.

ens-metadata agent registry query 42 --chain mainnet
{
  "chain": "mainnet",
  "registry": "0x...",
  "tokenId": "42",
  "owner": "0xAbC0...",
  "agentUri": "ipfs://bafk..."
}

agent registry set-uri <agent-id> <new-uri>

Update an existing agent's URI. Run after re-publishing a registration file with new content.

ens-metadata agent registry set-uri 42 ipfs://bafkNew... \
  --chain mainnet --private-key "$KEY" --broadcast

Output shape matches register, with function: "setAgentURI" and additional agentId and newUri fields.

agent registry set-wallet <agent-id> <wallet-address>

Link a verified wallet to an agent via EIP-712 signature. Two modes:

# Self-sign: signer key controls the wallet being linked
ens-metadata agent registry set-wallet 42 0xWallet... \
  --chain mainnet --private-key "$KEY" --broadcast

# Pre-signed: wallet is controlled by a different key; submitter passes signature
ens-metadata agent registry set-wallet 42 0xWallet... \
  --chain mainnet --private-key "$SUBMITTER_KEY" \
  --deadline 1730000000 --signature 0x... --broadcast

When --signature is omitted, the CLI auto-signs with the provided key and sets the deadline to 240 seconds after the latest block's timestamp. When --signature is provided, the CLI verifies it against the wallet address before submission.

The dry-run output includes the encoded EIP-712 domain and message (only when auto-signing) so you can produce the signature out-of-band when self-signing isn't possible:

{
  "dryRun": true,
  "chain": "mainnet",
  "function": "setAgentWallet",
  "agentId": "42",
  "wallet": "0xWallet...",
  "deadline": "1730000240",
  "signer": "0xAbC0...",
  "signature": "auto-signed",
  "estimatedCost": "0.00197 ETH ($6.79)",
  "balance": "0.123456 ETH",
  "eip712": {
    "domain": {
      "name": "ERC8004IdentityRegistry",
      "version": "1",
      "chainId": 1,
      "verifyingContract": "0x..."
    },
    "primaryType": "AgentWalletSet",
    "message": {
      "agentId": "42",
      "newWallet": "<wallet-address>",
      "owner": "0xAbC0...",
      "deadline": "<unix-timestamp>"
    }
  },
  "hint": "Run with --broadcast to submit. To use a different signer, pass --signature <0x...> --deadline <timestamp>."
}

The eip712 block is omitted when --signature is supplied (the verified signature is shown as signature: "provided (verified)" instead).

agent registry unset-wallet <agent-id>

Clear the verified wallet link from an agent.

ens-metadata agent registry unset-wallet 42 \
  --chain mainnet --private-key "$KEY" --broadcast

Output shape matches the other registry write commands, with function: "unsetAgentWallet".

attestation verify group

Verify EIP-712 attestation envelopes that have been written into ENS text records by an attester. Read-only; no signing key needed.

Single-chain: the subject and the attester must live on the same chain. The CLI reads the attestation record, owner, and attester ENS all from the subject's chain. --attester is optional — when omitted, it auto-resolves to atst.base.eth for *.base.eth subjects and atst.lighthousegov.eth for everything else. When supplied, it must end in the same chain's suffix. See Basenames.

attestation verify handle <name> <platform>

Verify a social-handle attestation written to social-proofs[<platform>][<attester-ens>]. The attester auto-resolves from the subject's chain; pass --attester to verify against a non-default attester.

ens-metadata attestation verify handle myagent.eth com.x
ens-metadata attestation verify handle myagent.eth com.x \
  --attester other-attester.eth --max-age 86400

Successful output:

{
  "valid": true,
  "handle": "@myagent",
  "issuedAt": 1730000000,
  "attester": "atst.lighthousegov.eth",
  "attesterAddress": "0xAbC0..."
}

Failure output: valid: false with a reason field. Possible reasons fall into two groups.

Pre-claim failures (the attestation couldn't be located or decoded):

{ "valid": false, "reason": "missing" }
{ "valid": false, "reason": "attester-not-resolved", "attester": "atst.lighthousegov.eth" }

Other pre-claim reasons: owner-not-resolved, decode-error.

Claim-level failures (the envelope was decoded but rejected):

{
  "valid": false,
  "reason": "signature-mismatch",
  "handle": "@myagent",
  "issuedAt": 1730000000,
  "attester": "atst.lighthousegov.eth",
  "attesterAddress": "0xAbC0..."
}

Other claim-level reasons (bad-signature, decode-error, unsupported-version) come from the SDK; the exact list lives in @ensmetadata/sdk. The CLI also produces stale when --max-age is supplied and now - issuedAt exceeds it — this check is enforced in the CLI, not the SDK.

attestation verify uid <name> <platform> <uid>

Verify a uid attestation written to uid[<platform>][<attester-ens>] against a caller-supplied raw uid. Same defaults and output shape as verify handle, with uid in the response instead of handle.

ens-metadata attestation verify uid myagent.eth com.x 1234567890

Workflows

A minimal end-to-end agent registration looks like this:

# 1. Generate a starter registration file and edit it
ens-metadata agent registration-file template > registration.json
$EDITOR registration.json

# 2. Validate before paying for IPFS
ens-metadata agent registration-file validate registration.json

# 3. Publish to IPFS (requires PINATA_JWT)
ens-metadata agent registration-file publish registration.json
# → { "cid": "...", "uri": "ipfs://..." }

# 4. Dry-run the on-chain registration to preview cost
ens-metadata agent registry register ipfs://<cid> \
  --chain mainnet --private-key "$KEY"

# 5. Submit for real
ens-metadata agent registry register ipfs://<cid> \
  --chain mainnet --private-key "$KEY" --broadcast
# → { "txHash": "0x...", ... }

# 6. (Optional) Link a verified wallet to the new agent token
ens-metadata agent registry set-wallet <agent-id> 0xWallet... \
  --chain mainnet --private-key "$KEY" --broadcast

# 7. Verify on-chain attestations on the agent's ENS name
ens-metadata attestation verify handle myagent.eth com.x

For the full walkthrough (suggested artifact directory layout, dry-run/broadcast guardrails, attester onboarding), see SKILL.md. Print it locally with ens-metadata skill.

Troubleshooting

Missing Pinata credentials. Set PINATA_JWT (preferred) or both PINATA_API_KEY and PINATA_API_SECRET in the environment before running agent registration-file publish.

No resolver found for <name> or No resolver set for <name>. The ENS name has no resolver configured, so there is nowhere to write text records or read class/schema. Set a resolver via the ENS app or another tool, then re-run.

RPC timeouts or 429/Too Many Requests. The public fallback RPCs are best-effort and rate-limited. Supply your own with --rpc <url>, or via RPC_URL_<chainId> (for example RPC_URL_1 for mainnet, RPC_URL_8453 for Base), MAINNET_RPC_URL, or ETH_RPC_URL.

Failed to read resolver from Base registry for <name>: ... over rate limit. The default Base RPC (https://mainnet.base.org) rate-limits the back-to-back resolver lookups in view/set. The CLI falls back to curated public Base endpoints (PublicNode, 1rpc.io); for production usage set RPC_URL_8453 to a private endpoint (Alchemy, QuickNode, etc.).

Signature does not recover to wallet <address> from set-wallet. The --signature you supplied was not produced by the wallet you're trying to link, or the --deadline doesn't match the deadline that was signed over. Re-sign against the EIP-712 domain shown in the dry-run output, including the same agentId, newWallet, owner, and deadline.

Could not determine the manager of <name> from set. set was invoked without --private-key and the on-chain owner read returned no result (zero-address registrant or non-EOA owner). Pass --private-key to skip the manager lookup.

Validation failure on set with Invalid payload (validated against schema from ens: ipfs://...). The schema text record on the name points to a schema that the payload doesn't conform to. Either edit the payload to match, or override with payload.schema to validate against a different schema.

Related packages

  • @ensmetadata/sdk: programmatic read/write SDK underlying this CLI
  • @ensmetadata/schemas: JSON schemas for ENS metadata classes (workspace package)