@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 viemChoose a mode
| Mode | Network calls | Wallet required | Use when |
|---|---|---|---|
| Advertise existing trust | None during build | No | You already have registry entries and only need Agent Card metadata |
| Read registry | Registry reads | No | You want to inspect a known agent ID through public RPC |
| Auto-register | Registry write | Yes, funded for gas | You intentionally want the SDK to create a new identity |
| Direct registry clients | Reads and optional writes | For writes | You 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:
| Variable | Meaning |
|---|---|
AGENT_DOMAIN | Domain placed in the identity proof and registration URI |
IDENTITY_AGENT_ID | Optional known token ID; skips domain-document discovery |
RPC_URL | EVM JSON-RPC endpoint |
CHAIN_ID | Numeric EVM chain ID |
REGISTER_IDENTITY or IDENTITY_AUTO_REGISTER | Whether to write a missing registration |
IDENTITY_A2A_ENDPOINT | Explicit A2A service URL in the registration document |
IDENTITY_A2A_VERSION | A2A service version label |
IDENTITY_WEBSITE, IDENTITY_TWITTER, IDENTITY_EMAIL | Optional service endpoints |
IDENTITY_INCLUDE_A2A, IDENTITY_INCLUDE_WEB, IDENTITY_INCLUDE_TWITTER, IDENTITY_INCLUDE_EMAIL | Select generated services |
IDENTITY_INCLUDE_OASF | Enable 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.
Advertise an existing identity without a wallet
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, anddidRegisterfrom identity bootstrapstatus,domain, andisNewRegistrationclients.identityandclients.reputationwhen clients could be createdregistrationandoasfRecordwhen 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.jsonA 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:
| Network | Chain ID | Identity and reputation | Validation |
|---|---|---|---|
| Ethereum mainnet | 1 | Configured | Zero address / not deployed |
| Ethereum Sepolia | 11155111 | Configured | Deprecated compatibility address |
| Base Sepolia | 84532 | Configured | Deprecated 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, andregisterAgentgenerateAgentRegistration,generateOASFRecord, andgetTrustConfig- identity, reputation, and deprecated validation registry clients
- domain-proof and registry-signature helpers
- registry deployment helpers such as
getRegistryAddresses()andisChainSupported() 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
| Symptom | Likely cause | Fix |
|---|---|---|
| Missing identity configuration error | Domain, RPC URL, or chain ID is absent | Set AGENT_DOMAIN, RPC_URL, and CHAIN_ID, or pass them explicitly |
| Build says a wallet is required | Auto-registration was enabled without a signer | Add a developer or agent wallet, or disable auto-registration |
Runtime starts but result.record is absent | Registration was disabled or failed | Read a known ID with result.clients.identity.get(id), or inspect registration logs |
| Unsupported chain error | The constant exists but no registry deployment is configured | Use one of the three mapped chains or provide/audit custom clients |
| OASF strict-mode error | One or more structured arrays are missing or malformed | Supply all five string arrays and valid URI values |
result.clients.validation is undefined | Validation is intentionally excluded | Do 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.