lucidAGENTS
Packages

@lucid-agents/identity

Publish ERC-8004 identity metadata and operate identity and reputation registries.

@lucid-agents/identity connects a Lucid runtime to ERC-8004 identity and reputation registries. It can resolve a known token ID or register an on-chain agent identity, add trust metadata to the Agent Card, generate a registration document, and publish an OASF capability record.

ERC-8004 is still an Ethereum standards-track Draft, not a finalized ERC. Identity ownership and reputation events are useful evidence, but they do not prove that an agent is safe, correct, or trustworthy. Read the protocol support boundary before making a conformance claim.

Install

bun add @lucid-agents/identity @lucid-agents/wallet viem

Choose a mode

ModeNetwork callsWallet requiredUse when
Advertise existing trustNone during buildNoYou already have registry entries and only need Agent Card metadata
Read registryRegistry readsNoYou want to inspect a known agent ID through public RPC
Auto-registerRegistry writeYes, funded for gasYou intentionally want the SDK to create a new identity
Direct registry clientsReads and optional writesFor writesYou need lifecycle or reputation operations outside runtime build

Registration is an on-chain side effect. Keep autoRegister: false until the chain, domain, registry address, signer, and transaction policy have been reviewed.

Add identity to a runtime

import { createAgent } from '@lucid-agents/core';
import { http } from '@lucid-agents/http';
import { identity, identityFromEnv } from '@lucid-agents/identity';
import { wallets, walletsFromEnv } from '@lucid-agents/wallet';

const runtime = await createAgent({
  name: 'research-agent',
  version: '1.0.0',
  description: 'Finds and synthesizes primary sources',
})
  .use(wallets({ config: walletsFromEnv() }))
  .use(identity({ config: identityFromEnv() }))
  .use(http())
  .build();

The identity extension orders itself after wallets when that extension is present. A wallet is optional for read-only clients. Auto-registration prefers a developer wallet and retains the agent wallet as a backward-compatible fallback.

Configure from environment

AGENT_DOMAIN=research.example.com
RPC_URL=https://your-base-sepolia-rpc.example
CHAIN_ID=84532
IDENTITY_AGENT_ID=42
IDENTITY_AUTO_REGISTER=false

# Optional override. Otherwise the package uses its known deployment for CHAIN_ID.
IDENTITY_REGISTRY_ADDRESS=0x...

identityFromEnv(overrides?) reads:

VariableMeaning
AGENT_DOMAINDomain placed in the identity proof and registration URI
IDENTITY_AGENT_IDOptional known token ID; skips domain-document discovery
RPC_URLEVM JSON-RPC endpoint
CHAIN_IDNumeric EVM chain ID
REGISTER_IDENTITY or IDENTITY_AUTO_REGISTERWhether to write a missing registration
IDENTITY_A2A_ENDPOINTExplicit A2A service URL in the registration document
IDENTITY_A2A_VERSIONA2A service version label
IDENTITY_WEBSITE, IDENTITY_TWITTER, IDENTITY_EMAILOptional service endpoints
IDENTITY_INCLUDE_A2A, IDENTITY_INCLUDE_WEB, IDENTITY_INCLUDE_TWITTER, IDENTITY_INCLUDE_EMAILSelect generated services
IDENTITY_INCLUDE_OASFEnable structured OASF output

OASF environment configuration additionally requires all five JSON-array values: IDENTITY_OASF_AUTHORS_JSON, IDENTITY_OASF_SKILLS_JSON, IDENTITY_OASF_DOMAINS_JSON, IDENTITY_OASF_MODULES_JSON, and IDENTITY_OASF_LOCATORS_JSON. IDENTITY_OASF_ENDPOINT and IDENTITY_OASF_VERSION are optional.

The IdentityConfig accepted by the extension is:

type IdentityConfig = {
  trust?: TrustConfig;
  agentId?: bigint | number | string;
  registrationDiscovery?: {
    fetch?: IdentityRegistrationFetch;
    timeoutMs?: number;
    maxBytes?: number;
  };
  domain?: string;
  autoRegister?: boolean;
  rpcUrl?: string;
  chainId?: number;
  registration?: AgentRegistrationOptions;
};

IDENTITY_REGISTRY_ADDRESS is consumed by the bootstrap helper rather than stored on IdentityConfig.

Supply a complete trust configuration when registration has already happened:

import { identity } from '@lucid-agents/identity';

const identityExtension = identity({
  config: {
    trust: {
      registrations: [
        {
          agentId: '42',
          agentRegistry:
            'eip155:84532:0x8004A818BFB912233c491871b3d84c89A494BD9e',
        },
      ],
      trustModels: ['feedback'],
    },
  },
});

This mode enriches the Agent Card but does not verify the supplied values at startup. Validate them in your release process.

Read or register identity directly

createAgentIdentity() is the lower-level bootstrap API:

import { createAgentIdentity } from '@lucid-agents/identity';

const result = await createAgentIdentity({
  agentId: 42n,
  domain: 'research.example.com',
  rpcUrl: process.env.RPC_URL,
  chainId: 84532,
  autoRegister: false,
  registration: {
    name: 'Research Agent',
    description: 'Finds and synthesizes primary sources',
    x402Support: true,
  },
});

console.log(result.record?.agentId, result.trust, result.status);

The result can include:

  • record, trust, signature, transactionHash, and didRegister from identity bootstrap
  • status, domain, and isNewRegistration
  • clients.identity and clients.reputation when clients could be created
  • registration and oasfRecord when registration options were supplied

Read-only bootstrap is designed to let the agent continue when registry client initialization fails. It may return no record or clients; treat the required fields as the success condition when identity is mandatory. Explicit auto-registration failures propagate instead of degrading to an identity-free result. A domain is not an on-chain ERC-721 lookup key. Instead, domain-only bootstrap fetches /.well-known/agent-registration.json, requires a registration matching the configured namespace, chain, and registry address, then passes its agentId to ownerOf and tokenURI. Supply agentId or IDENTITY_AGENT_ID to skip document discovery and verify a known ID directly. Both paths populate record and unsigned trust without a signer. Agent IDs must fit the ERC-721 uint256 range; use a decimal string or bigint above JavaScript's safe-integer range.

Domain discovery rejects redirects and hard-caps requests at 1500 ms and 64 KiB. registrationDiscovery can inject fetch and tighten those bounds. Missing, malformed, oversized, ambiguous, or registry-mismatched documents do not produce trust.

For an HTTP(S) on-chain agentURI, bootstrap fails closed when its origin does not match the configured domain. Non-HTTP or malformed URIs cannot establish a domain relationship and fail closed unless agentURI pins the exact expected value. If a configured agentId is missing, bootstrap will not auto-register a replacement token with a different ID. Remove the ID for an intentional new registration.

createAgentIdentity() and identityFromEnv() default autoRegister to false. Registration occurs only when autoRegister: true, REGISTER_IDENTITY=true, or IDENTITY_AUTO_REGISTER=true is explicitly configured, and a signing wallet is available.

Runtime surface

The extension contributes two related slices:

type IdentityExtensionRuntime = {
  trust?: TrustConfig;
  identity?: {
    registration?: AgentRegistration;
    buildOASFRecord?: (requestUrl: string) => OASFRecord | undefined;
    result?: AgentIdentity;
  };
};

There are no runtime.identity.signDomainProof(), identity.createDomainChallenge(), or identity.verifyDomainProof() instance methods.

The HTTP extension uses registration and buildOASFRecord to serve discovery metadata. trust is merged into the generated Agent Card.

Registration and OASF documents

Generate a registration document explicitly:

import { generateAgentRegistration } from '@lucid-agents/identity';

const registration = generateAgentRegistration(result, {
  name: 'Research Agent',
  description: 'Finds and synthesizes primary sources',
  a2aEndpoint: 'https://research.example.com/.well-known/agent-card.json',
  website: 'https://research.example.com/',
  x402Support: true,
  active: true,
});

Host the returned JSON at the agentURI registered on-chain—conventionally:

https://research.example.com/.well-known/agent-registration.json

A registration document can describe services and registrations, but it is only trustworthy when a consumer verifies its origin and the corresponding on-chain record.

To publish OASF metadata, use structured configuration:

const config = {
  domain: 'research.example.com',
  registration: {
    selectedServices: ['A2A', 'web', 'OASF'] as const,
    oasf: {
      version: '0.8.0',
      authors: ['ops@research.example.com'],
      skills: ['source-discovery', 'synthesis'],
      domains: ['research'],
      modules: ['https://research.example.com/modules/core'],
      locators: ['https://research.example.com/.well-known/oasf-record.json'],
    },
  },
};

The generated record also includes current Lucid entrypoints. Free-form OASF strings are rejected; the five array fields are required in strict mode.

Domain-proof helpers

Domain proof is exposed as standalone message-building and signing helpers:

import {
  buildDomainProofMessage,
  signDomainProof,
} from '@lucid-agents/identity';
import type { SignerWalletClient } from '@lucid-agents/wallet';

const params = {
  domain: 'research.example.com',
  address: '0x1234000000000000000000000000000000000000' as const,
  chainId: 84532,
  nonce: crypto.randomUUID(),
};

const message = buildDomainProofMessage(params);
const signature = await signDomainProof(
  walletClient as SignerWalletClient,
  params
);

The package does not provide a domain-challenge server or a general verifier. The relying party must issue and persist a nonce, enforce expiry and audience, recover the signer, compare the expected domain/address/chain, and mark the nonce consumed.

Registry clients

Identity registry

import { createIdentityRegistryClient } from '@lucid-agents/identity';

const identityRegistry = createIdentityRegistryClient({
  address: '0x8004A818BFB912233c491871b3d84c89A494BD9e',
  chainId: 84532,
  publicClient,
  walletClient, // omit for read-only use
});

const record = await identityRegistry.get(42n);
const metadata = await identityRegistry.getMetadata(42n, 'version');

const registered = await identityRegistry.register({
  agentURI: 'https://research.example.com/.well-known/agent-registration.json',
});

The client also supports wallet assignment, metadata writes, transfers, approvals, and version lookup. Write methods require an account-bearing wallet client and wait for transaction confirmation.

Reputation registry

import { createReputationRegistryClient } from '@lucid-agents/identity';

const reputation = createReputationRegistryClient({
  address: '0x8004B663056A597Dffe9eCcC1965A193B7388713',
  identityRegistryAddress: '0x8004A818BFB912233c491871b3d84c89A494BD9e',
  chainId: 84532,
  publicClient,
  walletClient,
});

const summary = await reputation.getSummary(42n);
const txHash = await reputation.giveFeedback({
  toAgentId: 42n,
  value: 95,
  valueDecimals: 0,
  tag1: 'quality',
  tag2: 'research',
  endpoint: 'https://research.example.com',
});

Interpret feedback in context. On-chain provenance does not make an evaluation objective, Sybil-resistant, or relevant to your use case.

Validation registry status

createValidationRegistryClient() remains exported for backward compatibility, but validation is under active development and is not created inside result.clients. Do not describe validation as generally available or build a production dependency on its current shape without pinning and auditing the upstream draft and deployed contract.

Known registry deployments

The package currently contains identity and reputation addresses for:

NetworkChain IDIdentity and reputationValidation
Ethereum mainnet1ConfiguredZero address / not deployed
Ethereum Sepolia11155111ConfiguredDeprecated compatibility address
Base Sepolia84532ConfiguredDeprecated compatibility address

SUPPORTED_CHAINS contains additional chain constants, but getRegistryAddresses() rejects chains that do not yet have entries in the deployment map. Check the official ERC-8004 contracts repository before every production deployment.

Type ownership and exports

Import IdentityConfig and identity-specific helper/client types from @lucid-agents/identity. Canonical cross-package contracts—including IdentityRuntime, AgentRegistration, RegistrationEntry, TrustConfig, and TrustModel—live in @lucid-agents/types/identity.

The package exports:

  • identity, identityFromEnv, createAgentIdentity, and registerAgent
  • generateAgentRegistration, generateOASFRecord, and getTrustConfig
  • identity, reputation, and deprecated validation registry clients
  • domain-proof and registry-signature helpers
  • registry deployment helpers such as getRegistryAddresses() and isChainSupported()
  • createAgentCardWithIdentity() for explicit manifest enrichment

Security and production checks

  • Pin the chain ID and registry addresses; never infer them from an untrusted request.
  • Make registration an explicit deployment or administration step. Do not let normal request handling create identities.
  • Verify the hosted registration document matches the on-chain agentURI, owner, services, and expected domain.
  • Use a dedicated, funded signer with transaction limits. Identity ownership is transferable and approvals are security-sensitive.
  • Index reputation events with confirmation depth and reorg handling before using them in authorization policy.
  • Do not convert a reputation score directly into permission to spend money or access sensitive data.

Troubleshooting

SymptomLikely causeFix
Missing identity configuration errorDomain, RPC URL, or chain ID is absentSet AGENT_DOMAIN, RPC_URL, and CHAIN_ID, or pass them explicitly
Build says a wallet is requiredAuto-registration was enabled without a signerAdd a developer or agent wallet, or disable auto-registration
Runtime starts but result.record is absentRegistration was disabled or failedRead a known ID with result.clients.identity.get(id), or inspect registration logs
Unsupported chain errorThe constant exists but no registry deployment is configuredUse one of the three mapped chains or provide/audit custom clients
OASF strict-mode errorOne or more structured arrays are missing or malformedSupply all five string arrays and valid URI values
result.clients.validation is undefinedValidation is intentionally excludedDo not depend on it; use a manually created compatibility client only after review

See ERC-8004 protocol support for the precise upstream-version and interoperability boundary.

On this page