ERC8004 Agent

TotalClaw 作者 limone-eth v0.0.1

用于在 ERC-8004 Trustless Agent 上注册 AI 代理的 8004 代理技能 标准并通过 SIWA(通过代理登录)对其进行身份验证。当代理时使用此技能 需要:(1) 创建或管理用于链上身份的以太坊钱包,(2) 在 ERC-8004 身份注册中心作为基于 NFT 的代理身份(注册),(3)通过 服务器通过使用签名质询(SIGN IN / SIWA)证明 ERC-8004 身份的所有权, (4) 构建或更新 ERC-8004 注册文件(带有端点、信任模型的元数据 JSON, 服务),(5) 将代理元数据上传到 IPFS 或 base64 数据 URI,(6) 查找或验证 代理人的链上注册。代理将公共身份状态保留在 MEMORY.md 中。私人 密钥保存在单独的密钥环代理服务器中 - 代理可以请求签名,但永远不会 访问密钥本身。 触发条件:ERC-8004、无需信任的代理、代理注册、SIWA、通过代理登录、 代理身份NFT、Agent0 SDK、代理钱包、代理密钥库、密钥环代理。

源码 ↗

安装 / 下载方式

TotalClaw CLI推荐
totalclaw skills install shop:totalclaw:limone-eth~erc8004-agent
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/totalclaw%3Alimone-eth~erc8004-agent/file -o erc8004-agent.md
Git 仓库获取源码
git clone https://github.com/openclaw/skills/commit/06b8674eda00439691524afc722b97d4431df08b
## 概述(中文)

用于在 ERC-8004 Trustless Agent 上注册 AI 代理的 8004 代理技能
标准并通过 SIWA(通过代理登录)对其进行身份验证。当代理时使用此技能
需要:(1) 创建或管理用于链上身份的以太坊钱包,(2) 在
ERC-8004 身份注册中心作为基于 NFT 的代理身份(注册),(3)通过
服务器通过使用签名质询(SIGN IN / SIWA)证明 ERC-8004 身份的所有权,
(4) 构建或更新 ERC-8004 注册文件(带有端点、信任模型的元数据 JSON,
服务),(5) 将代理元数据上传到 IPFS 或 base64 数据 URI,(6) 查找或验证
代理人的链上注册。代理将公共身份状态保留在 MEMORY.md 中。私人
密钥保存在单独的密钥环代理服务器中 - 代理可以请求签名,但永远不会
访问密钥本身。
触发条件:ERC-8004、无需信任的代理、代理注册、SIWA、通过代理登录、
代理身份NFT、Agent0 SDK、代理钱包、代理密钥库、密钥环代理。

## 原文

# 8004 Agent Skill v0.0.1

Register AI agents onchain (ERC-8004) and authenticate them via **SIWA (Sign In With Agent)**.

## Overview

ERC-8004 ("Trustless Agents") provides three onchain registries deployed as per-chain singletons:

- **Identity Registry** — ERC-721 NFTs. Each agent gets a unique `agentId` (tokenId) and an `agentURI` pointing to a JSON registration file.
- **Reputation Registry** — Feedback signals (score, tags) from clients to agents.
- **Validation Registry** — Third-party validator attestations (zkML, TEE, staked re-execution).

**SIWA (Sign In With Agent)** is a challenge-response authentication protocol (inspired by SIWE / EIP-4361) where an agent proves ownership of an ERC-8004 identity by signing a structured message. See [references/siwa-spec.md](references/siwa-spec.md).

---

## Security Architecture

> **Full details**: [references/security-model.md](references/security-model.md)

The agent's private key is the root of its onchain identity. It must be protected against prompt injection, accidental exposure, and file system snooping.

### Principle: The private key NEVER enters the agent process

All signing is delegated to a **keyring proxy server** — a separate process that holds the encrypted private key and exposes only HMAC-authenticated signing endpoints. The agent can request signatures but can never extract the key, even under full compromise (arbitrary code execution via prompt injection).

```
Agent Process                     Keyring Proxy Server (port 3100)
(auto-detected from               (holds encrypted private key)
 KEYRING_PROXY_URL)

createWallet()
  |
  +--> POST /create-wallet
       + HMAC-SHA256 header  ---> Generates key, encrypts to disk
                              <-- Returns { address } only

signMessage("hello")
  |
  +--> POST /sign-message
       + HMAC-SHA256 header  ---> Validates HMAC + timestamp (30s window)
                                  Loads key, signs, discards key
                              <-- Returns { signature, address }
```

**Why this is secure:**

| Property | Detail |
|---|---|
| **Key isolation** | Private key lives in a separate OS process; never enters agent memory |
| **Transport auth** | HMAC-SHA256 over method + path + body + timestamp; 30-second replay window |
| **Audit trail** | Every signing request is logged with timestamp, endpoint, source IP, success/failure |
| **Compromise limit** | Even full agent takeover can only request signatures — cannot extract the key |

**Environment variables:**

| Variable | Used by | Purpose |
|---|---|---|
| `KEYRING_PROXY_URL` | Agent | Proxy server URL — private (e.g. `http://keyring-proxy:3100`) or public |
| `KEYRING_PROXY_SECRET` | Both | HMAC shared secret |
| `KEYRING_PROXY_PORT` | Proxy server | Listen port (default: 3100) |
| `AGENT_PRIVATE_KEY` | Proxy server | Hex-encoded private key (0x...) — use an existing wallet instead of generating one |
| `KEYSTORE_PASSWORD` | Proxy server | Password for the encrypted-file keystore (not needed with `AGENT_PRIVATE_KEY`) |

> **Auto-detection**: When `KEYRING_PROXY_URL` is set, `KEYSTORE_BACKEND` automatically defaults to `proxy` — no need to set it manually. When `AGENT_PRIVATE_KEY` is set on the proxy server, `KEYSTORE_BACKEND` defaults to `env`.
>
> The proxy server stores keys using an AES-encrypted V3 JSON Keystore (scrypt KDF) by default. To use an existing wallet instead, set `AGENT_PRIVATE_KEY` on the proxy server — the key is held in memory at runtime (no encrypted file needed).

### Keystore API

The `@buildersgarden/siwa/keystore` module exposes ONLY these operations — none return the private key:

```
createWallet()           → { address, backend }     // Creates key, returns ONLY address
signMessage(msg)         → { signature, address }   // Signs via proxy, key never exposed
signTransaction(tx)      → { signedTx, address }    // Same pattern
signAuthorization(auth)  → SignedAuthorization       // EIP-7702 delegation signing
getAddress()             → string                    // Public address only
hasWallet()              → boolean
```

> `getSigner()` is **not available** with the proxy backend — use `signMessage()` / `signTransaction()` instead.

### IMPORTANT: Install and use the SDK

The agent MUST use the `@buildersgarden/siwa` SDK to interact with the keyring proxy. The SDK handles HMAC authentication automatically.

**Step 1 — Install the SDK** (if not already available in your environment):

```bash
npm install @buildersgarden/siwa
```

**Step 2 — Set environment variables:**

```
KEYRING_PROXY_URL=https://your-keyring-proxy.up.railway.app
KEYRING_PROXY_SECRET=<your-shared-secret>
```

**Step 3 — Use the SDK functions** (never call the proxy HTTP endpoints directly):

```typescript
import { createWallet, signMessage, getAddress } from '@buildersgarden/siwa/keystore';

const info = await createWallet();           // SDK handles HMAC auth internally
const { signature } = await signMessage(msg); // SDK handles HMAC auth internally
const address = await getAddress();           // SDK handles HMAC auth internally
```

The SDK reads `KEYRING_PROXY_URL` and `KEYRING_PROXY_SECRET` from environment variables and constructs the correct HMAC headers automatically.

### Fallback: Manual HMAC authentication (without SDK)

If you absolutely cannot install the SDK (e.g. non-Node.js environment, restricted runtime), you can call the proxy HTTP endpoints directly using the HMAC protocol described below. **Prefer the SDK whenever possible.**

**Headers required on every request** (except `GET /health`):

| Header | Value |
|---|---|
| `Content-Type` | `application/json` |
| `X-Keyring-Timestamp` | Current time as Unix epoch **milliseconds** (e.g. `1738792800000`) |
| `X-Keyring-Signature` | HMAC-SHA256 hex digest of the payload string (see below) |

**HMAC payload format** — a single string with four parts separated by newlines (`
`):

```
{METHOD}\n{PATH}\n{TIMESTAMP}\n{BODY}
```

| Part | Value |
|---|---|
| `METHOD` | HTTP method, uppercase (always `POST`) |
| `PATH` | Endpoint path (e.g. `/create-wallet`, `/sign-message`) |
| `TIMESTAMP` | Same value as the `X-Keyring-Timestamp` header |
| `BODY` | The raw JSON request body string (e.g. `{}` or `{"message":"hello"}`) |

**Compute the signature:**

```
HMAC-SHA256(secret, "POST\n/create-wallet\n1738792800000\n{}") → hex digest
```

**Timestamp window:** The server rejects requests where the timestamp differs from server time by more than **30 seconds**.

**Example — create a wallet (Node.js without SDK):**

```typescript
import crypto from 'crypto';

const PROXY_URL = process.env.KEYRING_PROXY_URL;
const SECRET = process.env.KEYRING_PROXY_SECRET;

async function proxyRequest(path: string, body: Record<string, unknown> = {}) {
  const bodyStr = JSON.stringify(body);
  const timestamp = Date.now().toString();
  const payload = `POST\n${path}\n${timestamp}\n${bodyStr}`;
  const signature = crypto.createHmac('sha256', SECRET).update(payload).digest('hex');

  const res = await fetch(`${PROXY_URL}${path}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Keyring-Timestamp': timestamp,
      'X-Keyring-Signature': signature,
    },
    body: bodyStr,
  });

  if (!res.ok) throw new Error(`${path} failed (${res.status}): ${await res.text()}`);
  return res.json();
}

// Usage
const wallet = await proxyRequest('/create-wallet');        // { address, backend }
const addr = await proxyRequest('/get-address');