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.
Security Architecture
Full details: 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).
CODEBLOCK0
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 |
|---|
| INLINECODE2 | Agent | Proxy server URL — private (e.g. http://keyring-proxy:3100) or public |
| INLINECODE4 |
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:
CODEBLOCK1
INLINECODE17 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):
CODEBLOCK2
Step 2 — Set environment variables:
CODEBLOCK3
Step 3 — Use the SDK functions (never call the proxy HTTP endpoints directly):
CODEBLOCK4
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 |
|---|
| INLINECODE24 | INLINECODE25 |
| INLINECODE26 |
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 (\n):
CODEBLOCK5
| Part | Value |
|---|
| INLINECODE30 | HTTP method, uppercase (always POST) |
| INLINECODE32 |
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:
CODEBLOCK6
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):
CODEBLOCK7
Example — create a wallet (Python):
CODEBLOCK8
Available endpoints:
| Endpoint | Body | Response |
|---|
| INLINECODE40 | INLINECODE41 | INLINECODE42 |
| INLINECODE43 |
{} |
{ hasWallet: boolean } |
|
POST /get-address |
{} |
{ address } |
|
POST /sign-message |
{ message: string } |
{ signature, address } |
|
POST /sign-transaction |
{ tx: { to, data, nonce, chainId, type, maxFeePerGas, ... } } |
{ signedTx, address } |
|
POST /sign-authorization |
{ auth: { chainId, address, nonce } } |
{ signedAuthorization } |
|
GET /health | — |
{ status: "ok", backend } (no auth required) |
MEMORY.md: Public Data Only
MEMORY.md stores the agent's public identity state — never the private key:
CODEBLOCK9
Lifecycle rules:
- 1. Before any action — Read MEMORY.md. If wallet exists, skip creation. If registered, skip re-registration.
- After wallet creation — Write address + backend info to MEMORY.md. Private key goes to proxy keystore only.
- After registration — Write agentId, agentRegistry, agentURI, chainId to MEMORY.md.
- After SIWA sign-in — Append session token under Sessions.
Template: assets/MEMORY.md.template
Deploying the Keyring Proxy
Before signing anything, the agent needs a running keyring proxy — the separate process that holds the private key and performs all cryptographic operations.
Option A — Railway (Recommended)
Deploy with one click using the Railway template:

This deploys a single keyring-proxy service built from packages/keyring-proxy/Dockerfile. Set these environment variables in Railway:
| Variable | Required | Description |
|---|
| INLINECODE62 | Yes | Shared HMAC-SHA256 secret. Must match your agent. |
| INLINECODE63 |
Conditional | Password for the encrypted-file keystore (default backend). |
|
AGENT_PRIVATE_KEY | Conditional | Hex-encoded private key (0x...) to use an existing wallet instead. |
After deployment, note the proxy URL (e.g. https://your-keyring-proxy.up.railway.app). Set it as KEYRING_PROXY_URL on your agent.
Full deployment guide with architecture details, OpenClaw gateway setup, and verification steps: https://siwa.builders.garden/docs/deploy
Option B — Docker (Self-hosted)
CODEBLOCK10
Option C — Local Development
CODEBLOCK11
Once the proxy is running, set these environment variables on the agent:
CODEBLOCK12
The proxy keystore backend is auto-detected when KEYRING_PROXY_URL is set — no need to set KEYSTORE_BACKEND manually.
Workflow: SIGN UP (Agent Registration)
Step 0: Check MEMORY.md + Keystore
CODEBLOCK13
Step 1: Create Wallet (key goes to proxy, address goes to MEMORY.md)
CODEBLOCK14
Step 1b: Fund the Wallet (REQUIRED before registration)
STOP — the wallet must have ETH for gas before any onchain transaction can succeed. Registration, URI updates, and metadata changes all require gas fees.
After creating the wallet, you MUST:
- 1. Show the user the wallet address and the target chain so they can send funds:
- Address: the value returned by
createWallet() or
getAddress()
- Chain: the chain where the agent will register (e.g. Base Sepolia chainId
84532, Base mainnet chainId
8453)
- 2. Tell the user to send ETH (or the chain's native gas token) to that address on that chain.
- Wait for the user to confirm they have funded the wallet before proceeding to registration.
For testnets, suggest using a faucet:
Do NOT attempt to register or send any onchain transaction until the wallet is funded. The transaction will fail with an insufficient funds error.
Example message to show the user:
CODEBLOCK15
Step 2: Build the Registration File
Create a JSON file following the ERC-8004 schema. Use assets/registration-template.json as a starting point.
Required fields: type, name, description, image, services, active.
After building, update MEMORY.md profile:
CODEBLOCK16
Step 3: Upload Metadata
Option A — IPFS (Pinata, recommended):
CODEBLOCK17
Option B — Base64 data URI:
CODEBLOCK18
Step 4: Register Onchain (signed via proxy)
With the proxy backend, the agent builds the transaction and delegates signing to the proxy:
CODEBLOCK19
See references/contract-addresses.md for deployed addresses per chain.
Alternative: Agent0 SDK
CODEBLOCK20
Alternative: create-8004-agent CLI
CODEBLOCK21
After npm run register, update MEMORY.md with the output agentId.
Workflow: SIGN IN (SIWA — Sign In With Agent)
Full spec: references/siwa-spec.md
Step 0: Read Public Identity from MEMORY.md
CODEBLOCK22
Step 1: Request Nonce from Server
CODEBLOCK23
Step 2: Sign via Proxy (key never exposed)
CODEBLOCK24
Step 3: Submit and Persist Session
CODEBLOCK25
SIWA Message Format
CODEBLOCK26
Server-Side Verification
The server MUST:
- 1. Recover signer from signature (EIP-191)
- Match recovered address to message address
- Validate domain binding, nonce, time window
- Call
ownerOf(agentId) onchain to confirm signer owns the agent NFT - (Optional) Evaluate
SIWAVerifyCriteria — activity status, required services, trust models, reputation score - Issue session token
INLINECODE83 in @buildersgarden/siwa/siwa accepts an optional criteria parameter (6th argument) to enforce requirements after the ownership check:
CODEBLOCK27
See the test server's verifySIWARequest() for a full reference implementation.
| Endpoint | Method | Description |
|---|
| INLINECODE87 | POST | Generate and return a nonce |
| INLINECODE88 |
POST | Accept
{ message, signature }, verify, return session/JWT |
MEMORY.md Quick Reference
| Section | When Written | Key Fields |
|---|
| Wallet | Step 1 of SIGN UP | Address, Keystore Backend, Created At |
| Registration |
Step 4 of SIGN UP | Status, Agent ID, Agent Registry, Agent URI, Chain ID |
|
Agent Profile | Step 2 of SIGN UP | Name, Description, Image |
|
Services | After adding endpoints | One line per service |
|
Sessions | After each SIWA sign-in | Token, domain, expiry per session |
|
Notes | Any time | Free-form (funding tx, faucet used, etc.) |
What is NOT in MEMORY.md: Private keys, keystore passwords, mnemonic phrases.
Reference Files
Core Library (@buildersgarden/siwa package)
- -
@buildersgarden/siwa/keystore — Secure key storage abstraction with keyring proxy support @buildersgarden/siwa/memory — MEMORY.md read/write helpers (public data only)@buildersgarden/siwa/siwa — SIWA message building, signing (via keystore), and server-side verification (with optional criteria)@buildersgarden/siwa/registry — Read agent profiles (getAgent) and reputation (getReputation) from on-chain registries. Exports ERC-8004 typed values: ServiceType, TrustModel, INLINECODE99@buildersgarden/siwa/proxy-auth — HMAC-SHA256 authentication utilities for the keyring proxy
Assets
8004 Agent 技能 v0.0.1
在链上注册AI代理(ERC-8004),并通过 SIWA(使用代理登录) 进行身份验证。
概述
ERC-8004(无需信任的代理)提供了三个链上注册表,作为每条链的单例部署:
- - 身份注册表 — ERC-721 NFT。每个代理获得一个唯一的 agentId(tokenId)和一个指向JSON注册文件的 agentURI。
- 声誉注册表 — 从客户到代理的反馈信号(评分、标签)。
- 验证注册表 — 第三方验证者证明(zkML、TEE、质押重执行)。
SIWA(使用代理登录) 是一种挑战-响应身份验证协议(受SIWE / EIP-4361启发),代理通过签署结构化消息来证明对ERC-8004身份的所有权。参见 references/siwa-spec.md。
安全架构
完整详情:references/security-model.md
代理的私钥是其链上身份的根源。必须保护其免受提示注入、意外泄露和文件系统窥探。
原则:私钥绝不进入代理进程
所有签名都委托给一个密钥环代理服务器——一个独立的进程,持有加密的私钥,仅暴露HMAC认证的签名端点。代理可以请求签名,但即使在完全被攻破的情况下(通过提示注入执行任意代码),也无法提取密钥。
代理进程 密钥环代理服务器(端口3100)
(自动检测自 (持有加密私钥)
KEYRINGPROXYURL)
createWallet()
|
+--> POST /create-wallet
+ HMAC-SHA256 头部 ---> 生成密钥,加密到磁盘
<-- 仅返回 { address }
signMessage(hello)
|
+--> POST /sign-message
+ HMAC-SHA256 头部 ---> 验证HMAC + 时间戳(30秒窗口)
加载密钥,签名,丢弃密钥
<-- 返回 { signature, address }
为什么这是安全的:
| 属性 | 详情 |
|---|
| 密钥隔离 | 私钥存在于独立的操作系统进程中;从不进入代理内存 |
| 传输认证 |
对方法 + 路径 + 主体 + 时间戳进行HMAC-SHA256;30秒重放窗口 |
|
审计追踪 | 每个签名请求都记录时间戳、端点、源IP、成功/失败 |
|
攻破限制 | 即使完全接管代理,也只能请求签名——无法提取密钥 |
环境变量:
| 变量 | 使用者 | 用途 |
|---|
| KEYRINGPROXYURL | 代理 | 代理服务器URL——私有(例如 http://keyring-proxy:3100)或公共 |
| KEYRINGPROXYSECRET |
两者 | HMAC共享密钥 |
| KEYRING
PROXYPORT | 代理服务器 | 监听端口(默认:3100) |
| AGENT
PRIVATEKEY | 代理服务器 | 十六进制编码的私钥(0x...)——使用现有钱包而非生成新钱包 |
| KEYSTORE
PASSWORD | 代理服务器 | 加密文件密钥库的密码(使用 AGENTPRIVATE_KEY 时不需要) |
自动检测:当设置了 KEYRINGPROXYURL 时,KEYSTOREBACKEND 自动默认为 proxy——无需手动设置。当在代理服务器上设置了 AGENTPRIVATEKEY 时,KEYSTOREBACKEND 默认为 env。
代理服务器默认使用AES加密的V3 JSON密钥库(scrypt KDF)存储密钥。要使用现有钱包,请在代理服务器上设置 AGENTPRIVATEKEY——密钥在运行时保存在内存中(无需加密文件)。
密钥库API
@buildersgarden/siwa/keystore 模块仅暴露以下操作——均不返回私钥:
createWallet() → { address, backend } // 创建密钥,仅返回地址
signMessage(msg) → { signature, address } // 通过代理签名,密钥从不暴露
signTransaction(tx) → { signedTx, address } // 相同模式
signAuthorization(auth) → SignedAuthorization // EIP-7702委托签名
getAddress() → string // 仅公共地址
hasWallet() → boolean
使用代理后端时,getSigner() 不可用——请改用 signMessage() / signTransaction()。
重要提示:安装并使用SDK
代理必须使用 @buildersgarden/siwa SDK 与密钥环代理交互。SDK自动处理HMAC认证。
步骤1 — 安装SDK(如果环境中尚未提供):
bash
npm install @buildersgarden/siwa
步骤2 — 设置环境变量:
KEYRINGPROXYURL=https://your-keyring-proxy.up.railway.app
KEYRINGPROXYSECRET=
步骤3 — 使用SDK函数(切勿直接调用代理HTTP端点):
typescript
import { createWallet, signMessage, getAddress } from @buildersgarden/siwa/keystore;
const info = await createWallet(); // SDK内部处理HMAC认证
const { signature } = await signMessage(msg); // SDK内部处理HMAC认证
const address = await getAddress(); // SDK内部处理HMAC认证
SDK从环境变量读取 KEYRINGPROXYURL 和 KEYRINGPROXYSECRET,并自动构建正确的HMAC头部。
备用方案:手动HMAC认证(不使用SDK)
如果绝对无法安装SDK(例如非Node.js环境、受限运行时),可以使用下面描述的HMAC协议直接调用代理HTTP端点。尽可能优先使用SDK。
每个请求所需的头部(GET /health 除外):
| 头部 | 值 |
|---|
| Content-Type | application/json |
| X-Keyring-Timestamp |
当前时间的Unix纪元
毫秒(例如 1738792800000) |
| X-Keyring-Signature | 负载字符串的HMAC-SHA256十六进制摘要(见下文) |
HMAC负载格式——一个由四个部分组成的字符串,用换行符(\n)分隔:
{METHOD}\n{PATH}\n{TIMESTAMP}\n{BODY}
| 部分 | 值 |
|---|
| METHOD | HTTP方法,大写(始终为 POST) |
| PATH |
端点路径(例如 /create-wallet、/sign-message) |
| TIMESTAMP | 与 X-Keyring-Timestamp 头部相同的值 |
| BODY | 原始JSON请求体字符串(例如 {} 或 {message:hello}) |
计算签名:
HMAC-SHA256(secret, POST\n/create-wallet\n1738792800000\n{}) → 十六进制摘要
时间戳窗口: 服务器拒绝时间戳与服务器时间相差超过 30秒 的请求。
示例 — 创建钱包(无SDK的Node.js):
typescript
import crypto from crypto;
const PROXYURL = process.env.KEYRINGPROXY_URL;
const SECRET = process.env.KEYRINGPROXYSECRET;
async function proxyRequest(path: string, body: Record = {}) {
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();
}
// 使用
const wallet = await proxyRequest(/create-wallet); // { address, backend }
const addr = await proxyRequest(/get-address); // { address }
const sig = await proxyRequest(/sign-message, { message: hello }); // { signature, address }
示例 — 创建钱包(Python):
python
import hmac, hashlib, json, time, requests, os
PROXYURL = os.environ[KEYRINGPROXY_URL]
SECRET = os.environ[KEYRINGPROXYSECRET]
def proxy_request(path, body=None):
if body is None:
body = {}
body_str =