# Identity Agent Template - AI Coding Guide

This guide helps AI coding agents understand and extend this ERC-8004 identity-enabled agent project.

## Project Overview

This is a Bun HTTP agent with ERC-8004 identity registration and trust metadata. It combines `@lucid-agents/core` for the agent framework and `@lucid-agents/identity` for on-chain identity management.

**Key Files:**
- `src/agent.ts` - Agent definition with identity setup and entrypoints
- `src/index.ts` - Bun HTTP server setup
- `.env` - Configuration (domain, RPC URLs, private key, etc.)

**Key Dependencies:**
- `@lucid-agents/core` - Agent app framework
- `@lucid-agents/identity` - ERC-8004 identity helpers
- `viem` - Ethereum client library
- `zod` - Schema validation

## Build & Development Commands

```bash
# Install dependencies
bun install

# Start in development mode (watch mode)
bun run dev

# Start once (production)
bun run start

# Type check
bunx tsc --noEmit
```

## Template Arguments

This template accepts the following configuration arguments (see `template.schema.json`):

- `AGENT_NAME` - Set automatically from project name
- `AGENT_DESCRIPTION` - Human-readable description
- `AGENT_VERSION` - Semantic version
- `AGENT_DOMAIN` - Domain that hosts your agent (e.g., "agent.example.com")
- `PAYMENTS_FACILITATOR_URL` - x402 facilitator endpoint
- `PAYMENTS_FACILITATOR_AUTH` - Optional facilitator bearer token (defaults to `DREAMS_AUTH_TOKEN` at runtime)
- `PAYMENTS_NETWORK` - Network identifier
- `PAYMENTS_RECEIVABLE_ADDRESS` - Address for receiving payments
- `RPC_URL` - Blockchain RPC endpoint (e.g., "https://sepolia.base.org")
- `CHAIN_ID` - Chain ID (e.g., "84532" for Base Sepolia)
- `IDENTITY_AUTO_REGISTER` - Auto-register on ERC-8004 registry (boolean: true/false)
- `PRIVATE_KEY` - Wallet private key (required for on-chain operations)

## What is ERC-8004?

ERC-8004 is a standard for on-chain agent identity and trust. It provides:

1. **Identity Registry** - Register your agent with a domain → address mapping
2. **Reputation Registry** - Track agent reputation feedback and scores
3. **Validation Registry** - Store validation requests and responses

## How Identity Bootstrap Works

The template automatically sets up identity when the agent starts:

```typescript
// From src/agent.ts
// Runtime is created in ADAPTER_APP_CREATION
const identity = await createAgentIdentity({
  runtime,
  domain: process.env.AGENT_DOMAIN,
  autoRegister: process.env.IDENTITY_AUTO_REGISTER === "true",
});

if (identity.didRegister) {
  console.log("Registered agent on-chain!");
  console.log("Transaction:", identity.transactionHash);
} else if (identity.trust) {
  console.log("Found existing registration");
  console.log("Agent ID:", identity.record?.agentId);
}
```

This:
- Checks if the agent is already registered
- If `autoRegister` is true and not registered, registers on-chain
- **Sets the tokenURI** to `https://{domain}/.well-known/agent-registration.json`
- Retrieves trust configuration for the manifest

**⚠️ CRITICAL: After registration, you MUST host the ERC-8004 registration file at the tokenURI**

When you register an agent, the ERC-8004 registry stores a `tokenURI` pointing to your registration file. This URI is set to:
```
https://{your-domain}/.well-known/agent-registration.json
```

**Why this matters:**
- The `tokenURI` is stored **on-chain** in the ERC-8004 registry contract
- Anyone querying the registry will fetch the registration file from this URL to verify your agent's identity
- Without hosting the registration file, your agent's on-chain identity **cannot be verified**
- The registration file must match the ERC-8004 format (not Agent Card format)

The template automatically generates and displays the registration JSON after registration. You must:
1. Copy the generated registration JSON
2. Host it at the exact tokenURI location (`/.well-known/agent-registration.json`)
3. Ensure it's accessible over HTTPS with `Content-Type: application/json`

## ERC-8004 Registration File vs Agent Card

**Important:** There are two different files that serve different purposes:

### ERC-8004 Registration File (`/.well-known/agent-registration.json`)
- **Purpose:** On-chain identity verification
- **Stored:** On-chain as `tokenURI` in ERC-8004 registry
- **Format:** ERC-8004-specific format (see example below)
- **Required:** Yes, if you register on ERC-8004 registry
- **Generated by:** `generateAgentRegistration()` from `@lucid-agents/identity`

### Agent Card (`/.well-known/agent-card.json`)
- **Purpose:** A2A protocol agent discovery
- **Stored:** Off-chain at agent domain
- **Format:** A2A protocol format (skills, entrypoints, etc.)
- **Required:** Yes, for A2A protocol communication
- **Generated by:** Agent runtime automatically

**Both must be hosted** - they serve different protocols and purposes. The Agent Card includes ERC-8004 registration info converted to A2A format.

## How to Use the Identity Module

### Manual Registration

```typescript
import { createAgent } from "@lucid-agents/core";
import { http } from "@lucid-agents/http";
import { wallets } from "@lucid-agents/wallet";
import { walletsFromEnv } from "@lucid-agents/wallet";
import {
  createAgentIdentity,
  getTrustConfig,
} from "@lucid-agents/identity";

// Create agent first (must have wallets.agent configured)
const agent = await createAgent({
  name: "my-agent",
  version: "0.1.0",
})
  .use(http())
  .use(wallets({ config: walletsFromEnv() }))
  .build();

// Then create identity using the agent
const identity = await createAgentIdentity({
  runtime: agent,
  domain: "agent.example.com",
  autoRegister: true, // Will register if not already registered
});

// Check registration status
if (identity.record) {
  console.log("Agent ID:", identity.record.agentId);
  console.log("Address:", identity.record.agentAddress);
  console.log("Domain:", identity.record.agentDomain);
}
```

### Working with the Identity Registry

```typescript
// Access the identity registry client
import { identityClient } from "./agent";

// Query by domain
const record = await identityClient?.resolveByDomain("agent.example.com");
if (record) {
  console.log("Found agent:", record.agentId);
}

// Query by address
const recordByAddress = await identityClient?.resolveByAddress("0x...");

// Query by agent ID
const recordById = await identityClient?.resolveById(1);
```

### Working with the Reputation Registry

```typescript
// Access the reputation registry client
import { reputationClient } from "./agent";

// Get reputation summary
const summary = await reputationClient?.getSummary(agentId);
if (summary) {
  const score =
    summary.valueDecimals === 0
      ? Number(summary.value)
      : Number(summary.value) / 10 ** summary.valueDecimals;
  console.log("Score:", score);
  console.log("Reviews:", summary.count);
}

// Give feedback (requires a wallet client)
await reputationClient?.giveFeedback({
  toAgentId: 1n,
  value: 95,
  valueDecimals: 0,
  tag1: "reliable",
  tag2: "fast",
});
```

### Working with the Validation Registry

```typescript
// Access the validation registry client
import { validationClient } from "./agent";
import { hashValidationRequest } from "@lucid-agents/identity";

const requestBody = '{"input":"validation data here"}';
const requestHash = hashValidationRequest(requestBody);

// Submit a validation request
await validationClient?.validationRequest({
  validatorAddress: "0x...",
  agentId: 1n,
  requestUri: "ipfs://...",
  requestBody,
});

// Get validation status
const request = await validationClient?.getValidationStatus(requestHash);

// Submit validation response
await validationClient?.validationResponse({
  requestHash,
  response: 1,
  responseUri: "ipfs://...",
  responseHash: "0x...",
  tag: "validation",
});
```

## How to Add Trust-Aware Entrypoints

### Basic Entrypoint with Identity Info

```typescript
addEntrypoint({
  key: "verify-identity",
  description: "Return agent identity information",
  output: z.object({
    agentId: z.number().optional(),
    domain: z.string().optional(),
    address: z.string().optional(),
    isRegistered: z.boolean(),
  }),
  handler: async () => {
    const { identityClient } = await import("./agent");

    // Get this agent's identity
    const domain = process.env.AGENT_DOMAIN;
    const record = domain
      ? await identityClient?.resolveByDomain(domain)
      : null;

    return {
      output: {
        agentId: record?.agentId,
        domain: record?.agentDomain,
        address: record?.agentAddress,
        isRegistered: !!record,
      },
    };
  },
});
```

### Entrypoint that Checks Caller Identity

```typescript
addEntrypoint({
  key: "trusted-operation",
  description: "Operation that requires caller to be registered",
  input: z.object({
    callerAddress: z.string(),
    data: z.string(),
  }),
  output: z.object({
    result: z.string(),
    callerReputation: z.number().optional(),
  }),
  handler: async ({ input }) => {
    const { identityClient, reputationClient } = await import("./agent");

    // Verify caller is registered
    const callerRecord = await identityClient?.resolveByAddress(
      input.callerAddress as `0x${string}`
    );

    if (!callerRecord) {
      throw new Error("Caller not registered on identity registry");
    }

    // Get caller reputation
    const reputation = await reputationClient?.getSummary(
      BigInt(callerRecord.agentId)
    );
    const score =
      reputation
        ? reputation.valueDecimals === 0
          ? Number(reputation.value)
          : Number(reputation.value) / 10 ** reputation.valueDecimals
        : undefined;

    if (score !== undefined && score < 50) {
      throw new Error("Caller reputation too low");
    }

    // Perform trusted operation
    const result = `Processed data for agent ${callerRecord.agentId}`;

    return {
      output: {
        result,
        callerReputation: score,
      },
    };
  },
});
```

### Entrypoint for Validation Workflow

```typescript
addEntrypoint({
  key: "request-validation",
  description: "Submit a validation request for this agent",
  input: z.object({
    taskDescription: z.string(),
    requestorAddress: z.string(),
  }),
  output: z.object({
    requestHash: z.string(),
    status: z.string(),
  }),
  handler: async ({ input }) => {
    const { validationClient, identityClient } = await import("./agent");
    const { hashValidationRequest } = await import("@lucid-agents/identity");

    // Get this agent's ID
    const domain = process.env.AGENT_DOMAIN;
    const record = domain
      ? await identityClient?.resolveByDomain(domain)
      : null;

    if (!record) {
      throw new Error("Agent not registered");
    }

    // Submit validation request
    const requestBody = JSON.stringify({
      taskDescription: input.taskDescription,
      requestor: input.requestorAddress,
    });
    const requestHash = hashValidationRequest(requestBody);
    await validationClient?.validationRequest({
      validatorAddress: input.requestorAddress as `0x${string}`,
      agentId: BigInt(record.agentId),
      requestUri: `https://${record.agentDomain}/validation/${requestHash}.json`,
      requestBody,
    });

    return {
      output: {
        requestHash,
        status: "submitted",
      },
    };
  },
});
```

## Trust Configuration

The agent's trust metadata is automatically included in the manifest:

```typescript
const trustConfig = getTrustConfig(identity);

const { app, addEntrypoint } = createAgentApp(
  { /* meta */ },
  {
    payments: {
      facilitatorUrl: process.env.PAYMENTS_FACILITATOR_URL,
      facilitatorAuth: process.env.PAYMENTS_FACILITATOR_AUTH,
      payTo: process.env.PAYMENTS_RECEIVABLE_ADDRESS,
      network: process.env.PAYMENTS_NETWORK,
    },
    trust: trustConfig, // Automatically populated
  }
);
```

This adds to `/.well-known/agent.json`:
- `registrations[]` - Your identity registration(s)
- `trustModels[]` - Supported trust models
- `ValidationRequestsURI` - Where validation requests are stored
- `ValidationResponsesURI` - Where validation responses are stored
- `FeedbackDataURI` - Where feedback data is stored

## Environment Variables Guide

Required in `.env`:

```bash
# Agent registration file
AGENT_NAME=my-identity-agent
AGENT_VERSION=0.1.0
AGENT_DESCRIPTION=Verifiable agent with on-chain identity
AGENT_DOMAIN=agent.example.com

# Blockchain configuration
RPC_URL=https://sepolia.base.org
CHAIN_ID=84532
PRIVATE_KEY=0x...  # Required for on-chain operations

# Identity configuration (boolean: true or false)
IDENTITY_AUTO_REGISTER=true

# Payment configuration
PAYMENTS_FACILITATOR_URL=https://facilitator.daydreams.systems
PAYMENTS_FACILITATOR_AUTH=
PAYMENTS_NETWORK=ethereum
PAYMENTS_RECEIVABLE_ADDRESS=0x...
```

## Testing Your Agent

### Check Identity Registration

```bash
# Start the agent
bun run dev

# Check if registered (look for console output)
# "Registered agent on-chain!" or "Found existing registration"

# Test identity verification entrypoint
curl -X POST http://localhost:3000/entrypoints/verify-identity/invoke \
  -H "Content-Type: application/json" \
  -d '{"input": {}}'
```

### View Trust Metadata in Manifest

```bash
curl http://localhost:3000/.well-known/agent.json | jq '.registrations, .trustModels'
```

### Test with Another Agent's Address

```bash
curl -X POST http://localhost:3000/entrypoints/trusted-operation/invoke \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "callerAddress": "0x...",
      "data": "test"
    }
  }'
```

## Common Patterns

### Lazy Identity Loading

```typescript
let cachedIdentity: any = null;

handler: async ({ input }) => {
  if (!cachedIdentity) {
    const { identityClient } = await import("./agent");
    const domain = process.env.AGENT_DOMAIN;
    cachedIdentity = await identityClient?.resolveByDomain(domain!);
  }

  // Use cachedIdentity
  return {
    output: { agentId: cachedIdentity?.agentId },
  };
}
```

### Cross-Agent Trust Verification

```typescript
async function verifyAgentTrust(agentAddress: string): Promise<boolean> {
  const { identityClient, reputationClient } = await import("./agent");

  // Check if agent is registered
  const record = await identityClient?.resolveByAddress(
    agentAddress as `0x${string}`
  );
  if (!record) return false;

  // Check reputation threshold
  const summary = await reputationClient?.getSummary(BigInt(record.agentId));
  if (!summary) return false;
  const score =
    summary.valueDecimals === 0
      ? Number(summary.value)
      : Number(summary.value) / 10 ** summary.valueDecimals;
  return score >= 70;
}

addEntrypoint({
  key: "collaborate",
  input: z.object({
    partnerAddress: z.string(),
  }),
  handler: async ({ input }) => {
    const isTrusted = await verifyAgentTrust(input.partnerAddress);
    if (!isTrusted) {
      throw new Error("Partner agent not trusted");
    }

    // Proceed with collaboration
    return { output: { status: "collaboration initiated" } };
  },
});
```

## On-Chain Registration Details

When `IDENTITY_AUTO_REGISTER=true` and the agent is not registered:

1. The agent reads `AGENT_DOMAIN` and `PRIVATE_KEY` from `.env`
2. Creates a wallet from the private key
3. Connects to the blockchain via `RPC_URL`
4. Calls the Identity Registry contract to register
5. Stores the domain → address mapping on-chain
6. Transaction hash is logged to console

**Gas costs**: Registration requires gas fees. Ensure the wallet has sufficient balance for the target network.

## Hosting ERC-8004 Registration File

After registration, you **MUST** host the ERC-8004 registration file at the tokenURI. The template automatically generates this for you:

```typescript
import { generateAgentRegistration } from "@lucid-agents/identity";

const registration = generateAgentRegistration(identity, {
  name: "My Agent",
  description: "An intelligent assistant",
  image: "https://agent.example.com/og.png",
  services: [
    {
      name: "A2A",
      endpoint: "https://agent.example.com/.well-known/agent-card.json"
    }
  ]
});

// Host this JSON at: https://{your-domain}/.well-known/agent-registration.json
```

**Example ERC-8004 registration file format:**
```json
{
  "type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
  "name": "My Agent",
  "description": "An intelligent assistant",
  "domain": "agent.example.com",
  "image": "https://agent.example.com/og.png",
  "services": [
    {
      "name": "A2A",
      "endpoint": "https://agent.example.com/.well-known/agent-card.json"
    }
  ],
  "registrations": [
    {
      "agentId": "1",
      "agentRegistry": "eip155:84532:0x..."
    }
  ],
  "supportedTrust": ["feedback", "inference-validation"]
}
```

**Why this is required:**
1. The ERC-8004 registry contract stores the `tokenURI` **on-chain**
2. Anyone querying the registry will fetch the registration file from this URL to verify your agent's identity
3. Without hosting the file, your agent's on-chain identity **cannot be verified**
4. The registration file must be accessible over HTTPS with proper `Content-Type: application/json`

**Note:** This is different from the Agent Card format. The Agent Card (for A2A protocol) is automatically generated by the runtime at `/.well-known/agent-card.json`. You need to host **both** files:
- `/.well-known/agent-registration.json` - ERC-8004 registration file (you must host this manually)
- `/.well-known/agent-card.json` - Agent Card (automatically generated by runtime)

## Troubleshooting

### "Agent not registered"

Ensure:
1. `IDENTITY_AUTO_REGISTER=true` in `.env`
2. `PRIVATE_KEY` is set and has gas for transactions
3. `RPC_URL` and `CHAIN_ID` are correct
4. Network is supported (check Identity Registry deployment)

### Registration transaction fails

Check:
1. Wallet has sufficient gas
2. RPC URL is accessible
3. Domain is not already registered by another address
4. Private key format is correct (0x-prefixed hex)

### Can't query other agents

Verify:
1. RPC URL is correct and responsive
2. Identity Registry contract address is correct for the network
3. Target agent is actually registered on-chain

### Trust metadata not appearing in manifest

Ensure:
1. Identity is initialized before creating the app
2. `getTrustConfig(identity)` is called
3. Trust config is passed to `createAgentApp` options

## Security Considerations

1. **Private Key Security**: Never commit `.env` to version control
2. **Domain Verification**: Ensure you control the domain you register
3. **Reputation**: Monitor your agent's on-chain reputation
4. **Trust Thresholds**: Set appropriate reputation thresholds for operations
5. **Validation**: Implement proper validation workflows for critical tasks

## Next Steps

1. **Configure environment** - Set domain, RPC URL, and private key
2. **Test registration** - Run agent and verify on-chain registration
3. **Add trust-aware entrypoints** - Implement features that check identity
4. **Monitor reputation** - Track your agent's on-chain reputation
5. **Deploy** - Use Bun-compatible hosting with secure key management

## Additional Resources

- [ERC-8004 Specification](https://github.com/ethereum/ERCs/issues/8004)
- [@lucid-agents/identity docs](../../../identity/README.md)
- [viem documentation](https://viem.sh/)
- [Base network docs](https://docs.base.org/)
