ERC8004 Agent
用于在 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-agentcURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/totalclaw%3Alimone-eth~erc8004-agent/file -o erc8004-agent.mdGit 仓库获取源码
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');