{
  "schemaVersion": "1.0",
  "item": {
    "slug": "erc8004-agent",
    "name": "ERC8004 Agent",
    "source": "tencent",
    "type": "skill",
    "category": "AI 智能",
    "sourceUrl": "https://clawhub.ai/limone-eth/erc8004-agent",
    "canonicalUrl": "https://clawhub.ai/limone-eth/erc8004-agent",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/erc8004-agent",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=erc8004-agent",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "package.json",
      "SKILL.md",
      "CLAUDE.md",
      "assets/registration-template.json",
      "assets/MEMORY.template.md",
      "references/registration-guide.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/erc8004-agent"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/erc8004-agent",
    "agentPageUrl": "https://openagent3.xyz/skills/erc8004-agent/agent",
    "manifestUrl": "https://openagent3.xyz/skills/erc8004-agent/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/erc8004-agent/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "8004 Agent Skill v0.0.1",
        "body": "Register AI agents onchain (ERC-8004) and authenticate them via SIWA (Sign In With Agent)."
      },
      {
        "title": "Overview",
        "body": "ERC-8004 (\"Trustless Agents\") provides three onchain registries deployed as per-chain singletons:\n\nIdentity Registry — ERC-721 NFTs. Each agent gets a unique agentId (tokenId) and an agentURI pointing to a JSON registration file.\nReputation Registry — Feedback signals (score, tags) from clients to agents.\nValidation Registry — Third-party validator attestations (zkML, TEE, staked re-execution).\n\nSIWA (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."
      },
      {
        "title": "Security Architecture",
        "body": "Full details: references/security-model.md\n\nThe agent's private key is the root of its onchain identity. It must be protected against prompt injection, accidental exposure, and file system snooping."
      },
      {
        "title": "Principle: The private key NEVER enters the agent process",
        "body": "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).\n\nAgent Process                     Keyring Proxy Server (port 3100)\n(auto-detected from               (holds encrypted private key)\n KEYRING_PROXY_URL)\n\ncreateWallet()\n  |\n  +--> POST /create-wallet\n       + HMAC-SHA256 header  ---> Generates key, encrypts to disk\n                              <-- Returns { address } only\n\nsignMessage(\"hello\")\n  |\n  +--> POST /sign-message\n       + HMAC-SHA256 header  ---> Validates HMAC + timestamp (30s window)\n                                  Loads key, signs, discards key\n                              <-- Returns { signature, address }\n\nWhy this is secure:\n\nPropertyDetailKey isolationPrivate key lives in a separate OS process; never enters agent memoryTransport authHMAC-SHA256 over method + path + body + timestamp; 30-second replay windowAudit trailEvery signing request is logged with timestamp, endpoint, source IP, success/failureCompromise limitEven full agent takeover can only request signatures — cannot extract the key\n\nEnvironment variables:\n\nVariableUsed byPurposeKEYRING_PROXY_URLAgentProxy server URL — private (e.g. http://keyring-proxy:3100) or publicKEYRING_PROXY_SECRETBothHMAC shared secretKEYRING_PROXY_PORTProxy serverListen port (default: 3100)AGENT_PRIVATE_KEYProxy serverHex-encoded private key (0x...) — use an existing wallet instead of generating oneKEYSTORE_PASSWORDProxy serverPassword for the encrypted-file keystore (not needed with AGENT_PRIVATE_KEY)\n\nAuto-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.\nThe 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)."
      },
      {
        "title": "Keystore API",
        "body": "The @buildersgarden/siwa/keystore module exposes ONLY these operations — none return the private key:\n\ncreateWallet()           → { address, backend }     // Creates key, returns ONLY address\nsignMessage(msg)         → { signature, address }   // Signs via proxy, key never exposed\nsignTransaction(tx)      → { signedTx, address }    // Same pattern\nsignAuthorization(auth)  → SignedAuthorization       // EIP-7702 delegation signing\ngetAddress()             → string                    // Public address only\nhasWallet()              → boolean\n\ngetSigner() is not available with the proxy backend — use signMessage() / signTransaction() instead."
      },
      {
        "title": "IMPORTANT: Install and use the SDK",
        "body": "The agent MUST use the @buildersgarden/siwa SDK to interact with the keyring proxy. The SDK handles HMAC authentication automatically.\n\nStep 1 — Install the SDK (if not already available in your environment):\n\nnpm install @buildersgarden/siwa\n\nStep 2 — Set environment variables:\n\nKEYRING_PROXY_URL=https://your-keyring-proxy.up.railway.app\nKEYRING_PROXY_SECRET=<your-shared-secret>\n\nStep 3 — Use the SDK functions (never call the proxy HTTP endpoints directly):\n\nimport { createWallet, signMessage, getAddress } from '@buildersgarden/siwa/keystore';\n\nconst info = await createWallet();           // SDK handles HMAC auth internally\nconst { signature } = await signMessage(msg); // SDK handles HMAC auth internally\nconst address = await getAddress();           // SDK handles HMAC auth internally\n\nThe SDK reads KEYRING_PROXY_URL and KEYRING_PROXY_SECRET from environment variables and constructs the correct HMAC headers automatically."
      },
      {
        "title": "Fallback: Manual HMAC authentication (without SDK)",
        "body": "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.\n\nHeaders required on every request (except GET /health):\n\nHeaderValueContent-Typeapplication/jsonX-Keyring-TimestampCurrent time as Unix epoch milliseconds (e.g. 1738792800000)X-Keyring-SignatureHMAC-SHA256 hex digest of the payload string (see below)\n\nHMAC payload format — a single string with four parts separated by newlines (\\n):\n\n{METHOD}\\n{PATH}\\n{TIMESTAMP}\\n{BODY}\n\nPartValueMETHODHTTP method, uppercase (always POST)PATHEndpoint path (e.g. /create-wallet, /sign-message)TIMESTAMPSame value as the X-Keyring-Timestamp headerBODYThe raw JSON request body string (e.g. {} or {\"message\":\"hello\"})\n\nCompute the signature:\n\nHMAC-SHA256(secret, \"POST\\n/create-wallet\\n1738792800000\\n{}\") → hex digest\n\nTimestamp window: The server rejects requests where the timestamp differs from server time by more than 30 seconds.\n\nExample — create a wallet (Node.js without SDK):\n\nimport crypto from 'crypto';\n\nconst PROXY_URL = process.env.KEYRING_PROXY_URL;\nconst SECRET = process.env.KEYRING_PROXY_SECRET;\n\nasync function proxyRequest(path: string, body: Record<string, unknown> = {}) {\n  const bodyStr = JSON.stringify(body);\n  const timestamp = Date.now().toString();\n  const payload = `POST\\n${path}\\n${timestamp}\\n${bodyStr}`;\n  const signature = crypto.createHmac('sha256', SECRET).update(payload).digest('hex');\n\n  const res = await fetch(`${PROXY_URL}${path}`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      'X-Keyring-Timestamp': timestamp,\n      'X-Keyring-Signature': signature,\n    },\n    body: bodyStr,\n  });\n\n  if (!res.ok) throw new Error(`${path} failed (${res.status}): ${await res.text()}`);\n  return res.json();\n}\n\n// Usage\nconst wallet = await proxyRequest('/create-wallet');        // { address, backend }\nconst addr = await proxyRequest('/get-address');             // { address }\nconst sig = await proxyRequest('/sign-message', { message: 'hello' }); // { signature, address }\n\nExample — create a wallet (Python):\n\nimport hmac, hashlib, json, time, requests, os\n\nPROXY_URL = os.environ[\"KEYRING_PROXY_URL\"]\nSECRET = os.environ[\"KEYRING_PROXY_SECRET\"]\n\ndef proxy_request(path, body=None):\n    if body is None:\n        body = {}\n    body_str = json.dumps(body, separators=(\",\", \":\"))\n    timestamp = str(int(time.time() * 1000))\n    payload = f\"POST\\n{path}\\n{timestamp}\\n{body_str}\"\n    signature = hmac.new(SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()\n    resp = requests.post(\n        f\"{PROXY_URL}{path}\",\n        headers={\n            \"Content-Type\": \"application/json\",\n            \"X-Keyring-Timestamp\": timestamp,\n            \"X-Keyring-Signature\": signature,\n        },\n        data=body_str,\n    )\n    resp.raise_for_status()\n    return resp.json()\n\nwallet = proxy_request(\"/create-wallet\")       # {\"address\": \"0x...\", \"backend\": \"...\"}\nsig = proxy_request(\"/sign-message\", {\"message\": \"hello\"})  # {\"signature\": \"0x...\", \"address\": \"0x...\"}\n\nAvailable endpoints:\n\nEndpointBodyResponsePOST /create-wallet{}{ address, backend }POST /has-wallet{}{ 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)"
      },
      {
        "title": "MEMORY.md: Public Data Only",
        "body": "MEMORY.md stores the agent's public identity state — never the private key:\n\n## Wallet\n- **Address**: `0x1234...abcd`       <- public\n- **Keystore Backend**: `proxy`      <- which backend holds the key\n- **Created At**: `2026-02-04T...`\n\n## Registration\n- **Status**: `registered`\n- **Agent ID**: `42`\n- **Agent Registry**: `eip155:84532:0x8004AA63...`\n...\n\nLifecycle rules:\n\nBefore any action — Read MEMORY.md. If wallet exists, skip creation. If registered, skip re-registration.\nAfter wallet creation — Write address + backend info to MEMORY.md. Private key goes to proxy keystore only.\nAfter registration — Write agentId, agentRegistry, agentURI, chainId to MEMORY.md.\nAfter SIWA sign-in — Append session token under Sessions.\n\nTemplate: assets/MEMORY.md.template"
      },
      {
        "title": "Deploying the Keyring Proxy",
        "body": "Before signing anything, the agent needs a running keyring proxy — the separate process that holds the private key and performs all cryptographic operations."
      },
      {
        "title": "Option A — Railway (Recommended)",
        "body": "Deploy with one click using the Railway template:\n\nThis deploys a single keyring-proxy service built from packages/keyring-proxy/Dockerfile. Set these environment variables in Railway:\n\nVariableRequiredDescriptionKEYRING_PROXY_SECRETYesShared HMAC-SHA256 secret. Must match your agent.KEYSTORE_PASSWORDConditionalPassword for the encrypted-file keystore (default backend).AGENT_PRIVATE_KEYConditionalHex-encoded private key (0x...) to use an existing wallet instead.\n\nAfter deployment, note the proxy URL (e.g. https://your-keyring-proxy.up.railway.app). Set it as KEYRING_PROXY_URL on your agent.\n\nFull deployment guide with architecture details, OpenClaw gateway setup, and verification steps: https://siwa.builders.garden/docs/deploy"
      },
      {
        "title": "Option B — Docker (Self-hosted)",
        "body": "docker build -f packages/keyring-proxy/Dockerfile -t keyring-proxy .\ndocker run -p 3100:3100 \\\n  -e KEYRING_PROXY_SECRET=your-secret \\\n  -e KEYSTORE_PASSWORD=your-password \\\n  keyring-proxy"
      },
      {
        "title": "Option C — Local Development",
        "body": "cd packages/siwa-testing\npnpm run proxy\n\nOnce the proxy is running, set these environment variables on the agent:\n\nKEYRING_PROXY_URL=http://localhost:3100   # or your Railway URL\nKEYRING_PROXY_SECRET=your-shared-secret\n\nThe proxy keystore backend is auto-detected when KEYRING_PROXY_URL is set — no need to set KEYSTORE_BACKEND manually."
      },
      {
        "title": "Step 0: Check MEMORY.md + Keystore",
        "body": "import { hasWallet } from '@buildersgarden/siwa/keystore';\nimport { ensureMemoryExists, hasWalletRecord, isRegistered } from '@buildersgarden/siwa/memory';\n\nensureMemoryExists('./MEMORY.md', './assets/MEMORY.md.template');\n\nif (await hasWallet() && isRegistered('./MEMORY.md')) {\n  // Already registered — skip to SIGN IN or update\n}\nif (await hasWallet() && hasWalletRecord('./MEMORY.md')) {\n  // Wallet exists — skip to Step 2\n}\n// Otherwise proceed to Step 1"
      },
      {
        "title": "Step 1: Create Wallet (key goes to proxy, address goes to MEMORY.md)",
        "body": "import { createWallet } from '@buildersgarden/siwa/keystore';\nimport { writeMemoryField } from '@buildersgarden/siwa/memory';\n\nconst info = await createWallet();  // <- key created in proxy, NEVER returned\n\n// Write ONLY public data to MEMORY.md\nwriteMemoryField('Address', info.address);\nwriteMemoryField('Keystore Backend', info.backend);\nif (info.keystorePath) writeMemoryField('Keystore Path', info.keystorePath);\nwriteMemoryField('Created At', new Date().toISOString());"
      },
      {
        "title": "Step 1b: Fund the Wallet (REQUIRED before registration)",
        "body": "STOP — the wallet must have ETH for gas before any onchain transaction can succeed. Registration, URI updates, and metadata changes all require gas fees.\n\nAfter creating the wallet, you MUST:\n\nShow the user the wallet address and the target chain so they can send funds:\n\nAddress: the value returned by createWallet() or getAddress()\nChain: the chain where the agent will register (e.g. Base Sepolia chainId 84532, Base mainnet chainId 8453)\n\n\nTell the user to send ETH (or the chain's native gas token) to that address on that chain.\nWait for the user to confirm they have funded the wallet before proceeding to registration.\n\nFor testnets, suggest using a faucet:\n\nBase Sepolia: Base Sepolia faucet or bridge from ETH Sepolia\nETH Sepolia: Sepolia faucet\n\nDo NOT attempt to register or send any onchain transaction until the wallet is funded. The transaction will fail with an insufficient funds error.\n\nExample message to show the user:\n\nWallet created successfully!\n\n  Address: 0x1234...abcd\n  Chain:   Base Sepolia (chainId 84532)\n\nPlease send some ETH to this address on Base Sepolia to cover gas fees.\nYou can use a faucet: https://www.alchemy.com/faucets/base-sepolia\n\nLet me know once the wallet is funded and I'll proceed with registration."
      },
      {
        "title": "Step 2: Build the Registration File",
        "body": "Create a JSON file following the ERC-8004 schema. Use assets/registration-template.json as a starting point.\n\nRequired fields: type, name, description, image, services, active.\n\nAfter building, update MEMORY.md profile:\n\nwriteMemoryField('Name', registrationFile.name);\nwriteMemoryField('Description', registrationFile.description);"
      },
      {
        "title": "Step 3: Upload Metadata",
        "body": "Option A — IPFS (Pinata, recommended):\n\nconst res = await fetch('https://api.pinata.cloud/pinning/pinJSONToIPFS', {\n  method: 'POST',\n  headers: {\n    'Content-Type': 'application/json',\n    'Authorization': `Bearer ${process.env.PINATA_JWT}`\n  },\n  body: JSON.stringify({ pinataContent: registrationFile })\n});\nconst { IpfsHash } = await res.json();\nconst agentURI = `ipfs://${IpfsHash}`;\n\nOption B — Base64 data URI:\n\nconst encoded = Buffer.from(JSON.stringify(registrationFile)).toString('base64');\nconst agentURI = `data:application/json;base64,${encoded}`;"
      },
      {
        "title": "Step 4: Register Onchain (signed via proxy)",
        "body": "With the proxy backend, the agent builds the transaction and delegates signing to the proxy:\n\nimport { signTransaction, getAddress } from '@buildersgarden/siwa/keystore';\nimport { writeMemoryField } from '@buildersgarden/siwa/memory';\n\nconst provider = new ethers.JsonRpcProvider(process.env.RPC_URL);\nconst address = await getAddress();\n\nconst IDENTITY_REGISTRY_ABI = [\n  'function register(string agentURI) external returns (uint256 agentId)',\n  'event Registered(uint256 indexed agentId, string agentURI, address indexed owner)'\n];\n\n// Build the transaction\nconst iface = new ethers.Interface(IDENTITY_REGISTRY_ABI);\nconst data = iface.encodeFunctionData('register', [agentURI]);\nconst nonce = await provider.getTransactionCount(address);\nconst feeData = await provider.getFeeData();\n\nconst txReq = {\n  to: REGISTRY_ADDRESS, data, nonce, chainId,\n  type: 2,\n  maxFeePerGas: feeData.maxFeePerGas,\n  maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,\n  gasLimit: (await provider.estimateGas({ to: REGISTRY_ADDRESS, data, from: address })) * 120n / 100n,\n};\n\n// Sign via proxy — key never enters this process\nconst { signedTx } = await signTransaction(txReq);\nconst txResponse = await provider.broadcastTransaction(signedTx);\nconst receipt = await txResponse.wait();\n\n// Parse event for agentId\nfor (const log of receipt.logs) {\n  try {\n    const parsed = iface.parseLog({ topics: log.topics as string[], data: log.data });\n    if (parsed?.name === 'Registered') {\n      const agentId = parsed.args.agentId.toString();\n      const agentRegistry = `eip155:${chainId}:${REGISTRY_ADDRESS}`;\n\n      // Persist PUBLIC results to MEMORY.md\n      writeMemoryField('Status', 'registered');\n      writeMemoryField('Agent ID', agentId);\n      writeMemoryField('Agent Registry', agentRegistry);\n      writeMemoryField('Agent URI', agentURI);\n      writeMemoryField('Chain ID', chainId.toString());\n      writeMemoryField('Registered At', new Date().toISOString());\n    }\n  } catch { /* skip non-matching logs */ }\n}\n\nSee references/contract-addresses.md for deployed addresses per chain."
      },
      {
        "title": "Alternative: Agent0 SDK",
        "body": "import { SDK } from 'agent0-sdk';\nimport { readMemory } from '@buildersgarden/siwa/memory';\n\n// Note: Agent0 SDK takes a private key string. If using the SDK,\n// you'll need a non-proxy backend or load the key within a narrow scope.\n// Prefer the signTransaction() approach above for proxy integration."
      },
      {
        "title": "Alternative: create-8004-agent CLI",
        "body": "npx create-8004-agent\n\nAfter npm run register, update MEMORY.md with the output agentId."
      },
      {
        "title": "Workflow: SIGN IN (SIWA — Sign In With Agent)",
        "body": "Full spec: references/siwa-spec.md"
      },
      {
        "title": "Step 0: Read Public Identity from MEMORY.md",
        "body": "import { readMemory, isRegistered } from '@buildersgarden/siwa/memory';\n\nconst memory = readMemory('./MEMORY.md');\nif (!isRegistered()) {\n  throw new Error('Agent not registered. Run SIGN UP workflow first.');\n}\n\nconst address = memory['Address'];\nconst agentId = parseInt(memory['Agent ID']);\nconst agentRegistry = memory['Agent Registry'];\nconst chainId = parseInt(memory['Chain ID']);"
      },
      {
        "title": "Step 1: Request Nonce from Server",
        "body": "const nonceRes = await fetch('https://api.targetservice.com/siwa/nonce', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ address, agentId, agentRegistry })\n});\nconst { nonce, issuedAt, expirationTime } = await nonceRes.json();"
      },
      {
        "title": "Step 2: Sign via Proxy (key never exposed)",
        "body": "import { signSIWAMessage } from '@buildersgarden/siwa/siwa';\n\n// signSIWAMessage internally calls keystore.signMessage()\n// which delegates to the keyring proxy — the key never enters this process.\nconst { message, signature } = await signSIWAMessage({\n  domain: 'api.targetservice.com',\n  address,\n  statement: 'Authenticate as a registered ERC-8004 agent.',\n  uri: 'https://api.targetservice.com/siwa',\n  agentId,\n  agentRegistry,\n  chainId,\n  nonce,\n  issuedAt,\n  expirationTime\n});"
      },
      {
        "title": "Step 3: Submit and Persist Session",
        "body": "import { appendToMemorySection } from '@buildersgarden/siwa/memory';\n\nconst verifyRes = await fetch('https://api.targetservice.com/siwa/verify', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ message, signature })\n});\nconst session = await verifyRes.json();\n\nif (session.success) {\n  appendToMemorySection('Sessions',\n    `- **${agentId}@api.targetservice.com**: \\`${session.token}\\` (exp: ${expirationTime || 'none'})`\n  );\n}"
      },
      {
        "title": "SIWA Message Format",
        "body": "{domain} wants you to sign in with your Agent account:\n{address}\n\n{statement}\n\nURI: {uri}\nVersion: 1\nAgent ID: {agentId}\nAgent Registry: {agentRegistry}\nChain ID: {chainId}\nNonce: {nonce}\nIssued At: {issuedAt}\n[Expiration Time: {expirationTime}]\n[Not Before: {notBefore}]\n[Request ID: {requestId}]"
      },
      {
        "title": "Server-Side Verification",
        "body": "The server MUST:\n\nRecover signer from signature (EIP-191)\nMatch recovered address to message address\nValidate domain binding, nonce, time window\nCall ownerOf(agentId) onchain to confirm signer owns the agent NFT\n(Optional) Evaluate SIWAVerifyCriteria — activity status, required services, trust models, reputation score\nIssue session token\n\nverifySIWA() in @buildersgarden/siwa/siwa accepts an optional criteria parameter (6th argument) to enforce requirements after the ownership check:\n\nimport { verifySIWA } from '@buildersgarden/siwa/siwa';\n\nconst result = await verifySIWA(message, signature, domain, nonceValid, provider, {\n  mustBeActive: true,              // agent metadata.active must be true\n  requiredServices: ['MCP'],       // ServiceType values from ERC-8004\n  requiredTrust: ['reputation'],   // TrustModel values from ERC-8004\n  minScore: 0.5,                   // minimum reputation score\n  minFeedbackCount: 10,            // minimum feedback count\n  reputationRegistryAddress: '0x8004BAa1...9b63',\n});\n\n// result.agent contains the full AgentProfile when criteria are provided\n\nSee the test server's verifySIWARequest() for a full reference implementation.\n\nEndpointMethodDescription/siwa/noncePOSTGenerate and return a nonce/siwa/verifyPOSTAccept { message, signature }, verify, return session/JWT"
      },
      {
        "title": "MEMORY.md Quick Reference",
        "body": "SectionWhen WrittenKey FieldsWalletStep 1 of SIGN UPAddress, Keystore Backend, Created AtRegistrationStep 4 of SIGN UPStatus, Agent ID, Agent Registry, Agent URI, Chain IDAgent ProfileStep 2 of SIGN UPName, Description, ImageServicesAfter adding endpointsOne line per serviceSessionsAfter each SIWA sign-inToken, domain, expiry per sessionNotesAny timeFree-form (funding tx, faucet used, etc.)\n\nWhat is NOT in MEMORY.md: Private keys, keystore passwords, mnemonic phrases."
      },
      {
        "title": "Reference Files",
        "body": "references/security-model.md — Threat model, keystore architecture, prompt injection defense\nreferences/siwa-spec.md — Full SIWA protocol specification (message ABNF, field definitions, security considerations)\nreferences/contract-addresses.md — Deployed registry addresses per chain, ABI fragments\nreferences/registration-guide.md — Detailed registration file schema, endpoint types, update flows"
      },
      {
        "title": "Core Library (@buildersgarden/siwa package)",
        "body": "@buildersgarden/siwa/keystore — Secure key storage abstraction with keyring proxy support\n@buildersgarden/siwa/memory — MEMORY.md read/write helpers (public data only)\n@buildersgarden/siwa/siwa — SIWA message building, signing (via keystore), and server-side verification (with optional criteria)\n@buildersgarden/siwa/registry — Read agent profiles (getAgent) and reputation (getReputation) from on-chain registries. Exports ERC-8004 typed values: ServiceType, TrustModel, ReputationTag\n@buildersgarden/siwa/proxy-auth — HMAC-SHA256 authentication utilities for the keyring proxy"
      },
      {
        "title": "Assets",
        "body": "assets/MEMORY.md.template — Template for the agent's public identity memory file\nassets/registration-template.json — Starter registration file template"
      }
    ],
    "body": "8004 Agent Skill v0.0.1\n\nRegister AI agents onchain (ERC-8004) and authenticate them via SIWA (Sign In With Agent).\n\nOverview\n\nERC-8004 (\"Trustless Agents\") provides three onchain registries deployed as per-chain singletons:\n\nIdentity Registry — ERC-721 NFTs. Each agent gets a unique agentId (tokenId) and an agentURI pointing to a JSON registration file.\nReputation Registry — Feedback signals (score, tags) from clients to agents.\nValidation Registry — Third-party validator attestations (zkML, TEE, staked re-execution).\n\nSIWA (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.\n\nSecurity Architecture\n\nFull details: references/security-model.md\n\nThe agent's private key is the root of its onchain identity. It must be protected against prompt injection, accidental exposure, and file system snooping.\n\nPrinciple: The private key NEVER enters the agent process\n\nAll 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).\n\nAgent Process                     Keyring Proxy Server (port 3100)\n(auto-detected from               (holds encrypted private key)\n KEYRING_PROXY_URL)\n\ncreateWallet()\n  |\n  +--> POST /create-wallet\n       + HMAC-SHA256 header  ---> Generates key, encrypts to disk\n                              <-- Returns { address } only\n\nsignMessage(\"hello\")\n  |\n  +--> POST /sign-message\n       + HMAC-SHA256 header  ---> Validates HMAC + timestamp (30s window)\n                                  Loads key, signs, discards key\n                              <-- Returns { signature, address }\n\n\nWhy this is secure:\n\nProperty\tDetail\nKey isolation\tPrivate key lives in a separate OS process; never enters agent memory\nTransport auth\tHMAC-SHA256 over method + path + body + timestamp; 30-second replay window\nAudit trail\tEvery signing request is logged with timestamp, endpoint, source IP, success/failure\nCompromise limit\tEven full agent takeover can only request signatures — cannot extract the key\n\nEnvironment variables:\n\nVariable\tUsed by\tPurpose\nKEYRING_PROXY_URL\tAgent\tProxy server URL — private (e.g. http://keyring-proxy:3100) or public\nKEYRING_PROXY_SECRET\tBoth\tHMAC shared secret\nKEYRING_PROXY_PORT\tProxy server\tListen port (default: 3100)\nAGENT_PRIVATE_KEY\tProxy server\tHex-encoded private key (0x...) — use an existing wallet instead of generating one\nKEYSTORE_PASSWORD\tProxy server\tPassword for the encrypted-file keystore (not needed with AGENT_PRIVATE_KEY)\n\nAuto-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.\n\nThe 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).\n\nKeystore API\n\nThe @buildersgarden/siwa/keystore module exposes ONLY these operations — none return the private key:\n\ncreateWallet()           → { address, backend }     // Creates key, returns ONLY address\nsignMessage(msg)         → { signature, address }   // Signs via proxy, key never exposed\nsignTransaction(tx)      → { signedTx, address }    // Same pattern\nsignAuthorization(auth)  → SignedAuthorization       // EIP-7702 delegation signing\ngetAddress()             → string                    // Public address only\nhasWallet()              → boolean\n\n\ngetSigner() is not available with the proxy backend — use signMessage() / signTransaction() instead.\n\nIMPORTANT: Install and use the SDK\n\nThe agent MUST use the @buildersgarden/siwa SDK to interact with the keyring proxy. The SDK handles HMAC authentication automatically.\n\nStep 1 — Install the SDK (if not already available in your environment):\n\nnpm install @buildersgarden/siwa\n\n\nStep 2 — Set environment variables:\n\nKEYRING_PROXY_URL=https://your-keyring-proxy.up.railway.app\nKEYRING_PROXY_SECRET=<your-shared-secret>\n\n\nStep 3 — Use the SDK functions (never call the proxy HTTP endpoints directly):\n\nimport { createWallet, signMessage, getAddress } from '@buildersgarden/siwa/keystore';\n\nconst info = await createWallet();           // SDK handles HMAC auth internally\nconst { signature } = await signMessage(msg); // SDK handles HMAC auth internally\nconst address = await getAddress();           // SDK handles HMAC auth internally\n\n\nThe SDK reads KEYRING_PROXY_URL and KEYRING_PROXY_SECRET from environment variables and constructs the correct HMAC headers automatically.\n\nFallback: Manual HMAC authentication (without SDK)\n\nIf 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.\n\nHeaders required on every request (except GET /health):\n\nHeader\tValue\nContent-Type\tapplication/json\nX-Keyring-Timestamp\tCurrent time as Unix epoch milliseconds (e.g. 1738792800000)\nX-Keyring-Signature\tHMAC-SHA256 hex digest of the payload string (see below)\n\nHMAC payload format — a single string with four parts separated by newlines (\\n):\n\n{METHOD}\\n{PATH}\\n{TIMESTAMP}\\n{BODY}\n\nPart\tValue\nMETHOD\tHTTP method, uppercase (always POST)\nPATH\tEndpoint path (e.g. /create-wallet, /sign-message)\nTIMESTAMP\tSame value as the X-Keyring-Timestamp header\nBODY\tThe raw JSON request body string (e.g. {} or {\"message\":\"hello\"})\n\nCompute the signature:\n\nHMAC-SHA256(secret, \"POST\\n/create-wallet\\n1738792800000\\n{}\") → hex digest\n\n\nTimestamp window: The server rejects requests where the timestamp differs from server time by more than 30 seconds.\n\nExample — create a wallet (Node.js without SDK):\n\nimport crypto from 'crypto';\n\nconst PROXY_URL = process.env.KEYRING_PROXY_URL;\nconst SECRET = process.env.KEYRING_PROXY_SECRET;\n\nasync function proxyRequest(path: string, body: Record<string, unknown> = {}) {\n  const bodyStr = JSON.stringify(body);\n  const timestamp = Date.now().toString();\n  const payload = `POST\\n${path}\\n${timestamp}\\n${bodyStr}`;\n  const signature = crypto.createHmac('sha256', SECRET).update(payload).digest('hex');\n\n  const res = await fetch(`${PROXY_URL}${path}`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      'X-Keyring-Timestamp': timestamp,\n      'X-Keyring-Signature': signature,\n    },\n    body: bodyStr,\n  });\n\n  if (!res.ok) throw new Error(`${path} failed (${res.status}): ${await res.text()}`);\n  return res.json();\n}\n\n// Usage\nconst wallet = await proxyRequest('/create-wallet');        // { address, backend }\nconst addr = await proxyRequest('/get-address');             // { address }\nconst sig = await proxyRequest('/sign-message', { message: 'hello' }); // { signature, address }\n\n\nExample — create a wallet (Python):\n\nimport hmac, hashlib, json, time, requests, os\n\nPROXY_URL = os.environ[\"KEYRING_PROXY_URL\"]\nSECRET = os.environ[\"KEYRING_PROXY_SECRET\"]\n\ndef proxy_request(path, body=None):\n    if body is None:\n        body = {}\n    body_str = json.dumps(body, separators=(\",\", \":\"))\n    timestamp = str(int(time.time() * 1000))\n    payload = f\"POST\\n{path}\\n{timestamp}\\n{body_str}\"\n    signature = hmac.new(SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()\n    resp = requests.post(\n        f\"{PROXY_URL}{path}\",\n        headers={\n            \"Content-Type\": \"application/json\",\n            \"X-Keyring-Timestamp\": timestamp,\n            \"X-Keyring-Signature\": signature,\n        },\n        data=body_str,\n    )\n    resp.raise_for_status()\n    return resp.json()\n\nwallet = proxy_request(\"/create-wallet\")       # {\"address\": \"0x...\", \"backend\": \"...\"}\nsig = proxy_request(\"/sign-message\", {\"message\": \"hello\"})  # {\"signature\": \"0x...\", \"address\": \"0x...\"}\n\n\nAvailable endpoints:\n\nEndpoint\tBody\tResponse\nPOST /create-wallet\t{}\t{ address, backend }\nPOST /has-wallet\t{}\t{ hasWallet: boolean }\nPOST /get-address\t{}\t{ address }\nPOST /sign-message\t{ message: string }\t{ signature, address }\nPOST /sign-transaction\t{ tx: { to, data, nonce, chainId, type, maxFeePerGas, ... } }\t{ signedTx, address }\nPOST /sign-authorization\t{ auth: { chainId, address, nonce } }\t{ signedAuthorization }\nGET /health\t—\t{ status: \"ok\", backend } (no auth required)\nMEMORY.md: Public Data Only\n\nMEMORY.md stores the agent's public identity state — never the private key:\n\n## Wallet\n- **Address**: `0x1234...abcd`       <- public\n- **Keystore Backend**: `proxy`      <- which backend holds the key\n- **Created At**: `2026-02-04T...`\n\n## Registration\n- **Status**: `registered`\n- **Agent ID**: `42`\n- **Agent Registry**: `eip155:84532:0x8004AA63...`\n...\n\n\nLifecycle rules:\n\nBefore any action — Read MEMORY.md. If wallet exists, skip creation. If registered, skip re-registration.\nAfter wallet creation — Write address + backend info to MEMORY.md. Private key goes to proxy keystore only.\nAfter registration — Write agentId, agentRegistry, agentURI, chainId to MEMORY.md.\nAfter SIWA sign-in — Append session token under Sessions.\n\nTemplate: assets/MEMORY.md.template\n\nDeploying the Keyring Proxy\n\nBefore signing anything, the agent needs a running keyring proxy — the separate process that holds the private key and performs all cryptographic operations.\n\nOption A — Railway (Recommended)\n\nDeploy with one click using the Railway template:\n\nThis deploys a single keyring-proxy service built from packages/keyring-proxy/Dockerfile. Set these environment variables in Railway:\n\nVariable\tRequired\tDescription\nKEYRING_PROXY_SECRET\tYes\tShared HMAC-SHA256 secret. Must match your agent.\nKEYSTORE_PASSWORD\tConditional\tPassword for the encrypted-file keystore (default backend).\nAGENT_PRIVATE_KEY\tConditional\tHex-encoded private key (0x...) to use an existing wallet instead.\n\nAfter deployment, note the proxy URL (e.g. https://your-keyring-proxy.up.railway.app). Set it as KEYRING_PROXY_URL on your agent.\n\nFull deployment guide with architecture details, OpenClaw gateway setup, and verification steps: https://siwa.builders.garden/docs/deploy\n\nOption B — Docker (Self-hosted)\ndocker build -f packages/keyring-proxy/Dockerfile -t keyring-proxy .\ndocker run -p 3100:3100 \\\n  -e KEYRING_PROXY_SECRET=your-secret \\\n  -e KEYSTORE_PASSWORD=your-password \\\n  keyring-proxy\n\nOption C — Local Development\ncd packages/siwa-testing\npnpm run proxy\n\n\nOnce the proxy is running, set these environment variables on the agent:\n\nKEYRING_PROXY_URL=http://localhost:3100   # or your Railway URL\nKEYRING_PROXY_SECRET=your-shared-secret\n\n\nThe proxy keystore backend is auto-detected when KEYRING_PROXY_URL is set — no need to set KEYSTORE_BACKEND manually.\n\nWorkflow: SIGN UP (Agent Registration)\nStep 0: Check MEMORY.md + Keystore\nimport { hasWallet } from '@buildersgarden/siwa/keystore';\nimport { ensureMemoryExists, hasWalletRecord, isRegistered } from '@buildersgarden/siwa/memory';\n\nensureMemoryExists('./MEMORY.md', './assets/MEMORY.md.template');\n\nif (await hasWallet() && isRegistered('./MEMORY.md')) {\n  // Already registered — skip to SIGN IN or update\n}\nif (await hasWallet() && hasWalletRecord('./MEMORY.md')) {\n  // Wallet exists — skip to Step 2\n}\n// Otherwise proceed to Step 1\n\nStep 1: Create Wallet (key goes to proxy, address goes to MEMORY.md)\nimport { createWallet } from '@buildersgarden/siwa/keystore';\nimport { writeMemoryField } from '@buildersgarden/siwa/memory';\n\nconst info = await createWallet();  // <- key created in proxy, NEVER returned\n\n// Write ONLY public data to MEMORY.md\nwriteMemoryField('Address', info.address);\nwriteMemoryField('Keystore Backend', info.backend);\nif (info.keystorePath) writeMemoryField('Keystore Path', info.keystorePath);\nwriteMemoryField('Created At', new Date().toISOString());\n\nStep 1b: Fund the Wallet (REQUIRED before registration)\n\nSTOP — the wallet must have ETH for gas before any onchain transaction can succeed. Registration, URI updates, and metadata changes all require gas fees.\n\nAfter creating the wallet, you MUST:\n\nShow the user the wallet address and the target chain so they can send funds:\nAddress: the value returned by createWallet() or getAddress()\nChain: the chain where the agent will register (e.g. Base Sepolia chainId 84532, Base mainnet chainId 8453)\nTell the user to send ETH (or the chain's native gas token) to that address on that chain.\nWait for the user to confirm they have funded the wallet before proceeding to registration.\n\nFor testnets, suggest using a faucet:\n\nBase Sepolia: Base Sepolia faucet or bridge from ETH Sepolia\nETH Sepolia: Sepolia faucet\n\nDo NOT attempt to register or send any onchain transaction until the wallet is funded. The transaction will fail with an insufficient funds error.\n\nExample message to show the user:\n\nWallet created successfully!\n\n  Address: 0x1234...abcd\n  Chain:   Base Sepolia (chainId 84532)\n\nPlease send some ETH to this address on Base Sepolia to cover gas fees.\nYou can use a faucet: https://www.alchemy.com/faucets/base-sepolia\n\nLet me know once the wallet is funded and I'll proceed with registration.\n\nStep 2: Build the Registration File\n\nCreate a JSON file following the ERC-8004 schema. Use assets/registration-template.json as a starting point.\n\nRequired fields: type, name, description, image, services, active.\n\nAfter building, update MEMORY.md profile:\n\nwriteMemoryField('Name', registrationFile.name);\nwriteMemoryField('Description', registrationFile.description);\n\nStep 3: Upload Metadata\n\nOption A — IPFS (Pinata, recommended):\n\nconst res = await fetch('https://api.pinata.cloud/pinning/pinJSONToIPFS', {\n  method: 'POST',\n  headers: {\n    'Content-Type': 'application/json',\n    'Authorization': `Bearer ${process.env.PINATA_JWT}`\n  },\n  body: JSON.stringify({ pinataContent: registrationFile })\n});\nconst { IpfsHash } = await res.json();\nconst agentURI = `ipfs://${IpfsHash}`;\n\n\nOption B — Base64 data URI:\n\nconst encoded = Buffer.from(JSON.stringify(registrationFile)).toString('base64');\nconst agentURI = `data:application/json;base64,${encoded}`;\n\nStep 4: Register Onchain (signed via proxy)\n\nWith the proxy backend, the agent builds the transaction and delegates signing to the proxy:\n\nimport { signTransaction, getAddress } from '@buildersgarden/siwa/keystore';\nimport { writeMemoryField } from '@buildersgarden/siwa/memory';\n\nconst provider = new ethers.JsonRpcProvider(process.env.RPC_URL);\nconst address = await getAddress();\n\nconst IDENTITY_REGISTRY_ABI = [\n  'function register(string agentURI) external returns (uint256 agentId)',\n  'event Registered(uint256 indexed agentId, string agentURI, address indexed owner)'\n];\n\n// Build the transaction\nconst iface = new ethers.Interface(IDENTITY_REGISTRY_ABI);\nconst data = iface.encodeFunctionData('register', [agentURI]);\nconst nonce = await provider.getTransactionCount(address);\nconst feeData = await provider.getFeeData();\n\nconst txReq = {\n  to: REGISTRY_ADDRESS, data, nonce, chainId,\n  type: 2,\n  maxFeePerGas: feeData.maxFeePerGas,\n  maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,\n  gasLimit: (await provider.estimateGas({ to: REGISTRY_ADDRESS, data, from: address })) * 120n / 100n,\n};\n\n// Sign via proxy — key never enters this process\nconst { signedTx } = await signTransaction(txReq);\nconst txResponse = await provider.broadcastTransaction(signedTx);\nconst receipt = await txResponse.wait();\n\n// Parse event for agentId\nfor (const log of receipt.logs) {\n  try {\n    const parsed = iface.parseLog({ topics: log.topics as string[], data: log.data });\n    if (parsed?.name === 'Registered') {\n      const agentId = parsed.args.agentId.toString();\n      const agentRegistry = `eip155:${chainId}:${REGISTRY_ADDRESS}`;\n\n      // Persist PUBLIC results to MEMORY.md\n      writeMemoryField('Status', 'registered');\n      writeMemoryField('Agent ID', agentId);\n      writeMemoryField('Agent Registry', agentRegistry);\n      writeMemoryField('Agent URI', agentURI);\n      writeMemoryField('Chain ID', chainId.toString());\n      writeMemoryField('Registered At', new Date().toISOString());\n    }\n  } catch { /* skip non-matching logs */ }\n}\n\n\nSee references/contract-addresses.md for deployed addresses per chain.\n\nAlternative: Agent0 SDK\nimport { SDK } from 'agent0-sdk';\nimport { readMemory } from '@buildersgarden/siwa/memory';\n\n// Note: Agent0 SDK takes a private key string. If using the SDK,\n// you'll need a non-proxy backend or load the key within a narrow scope.\n// Prefer the signTransaction() approach above for proxy integration.\n\nAlternative: create-8004-agent CLI\nnpx create-8004-agent\n\n\nAfter npm run register, update MEMORY.md with the output agentId.\n\nWorkflow: SIGN IN (SIWA — Sign In With Agent)\n\nFull spec: references/siwa-spec.md\n\nStep 0: Read Public Identity from MEMORY.md\nimport { readMemory, isRegistered } from '@buildersgarden/siwa/memory';\n\nconst memory = readMemory('./MEMORY.md');\nif (!isRegistered()) {\n  throw new Error('Agent not registered. Run SIGN UP workflow first.');\n}\n\nconst address = memory['Address'];\nconst agentId = parseInt(memory['Agent ID']);\nconst agentRegistry = memory['Agent Registry'];\nconst chainId = parseInt(memory['Chain ID']);\n\nStep 1: Request Nonce from Server\nconst nonceRes = await fetch('https://api.targetservice.com/siwa/nonce', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ address, agentId, agentRegistry })\n});\nconst { nonce, issuedAt, expirationTime } = await nonceRes.json();\n\nStep 2: Sign via Proxy (key never exposed)\nimport { signSIWAMessage } from '@buildersgarden/siwa/siwa';\n\n// signSIWAMessage internally calls keystore.signMessage()\n// which delegates to the keyring proxy — the key never enters this process.\nconst { message, signature } = await signSIWAMessage({\n  domain: 'api.targetservice.com',\n  address,\n  statement: 'Authenticate as a registered ERC-8004 agent.',\n  uri: 'https://api.targetservice.com/siwa',\n  agentId,\n  agentRegistry,\n  chainId,\n  nonce,\n  issuedAt,\n  expirationTime\n});\n\nStep 3: Submit and Persist Session\nimport { appendToMemorySection } from '@buildersgarden/siwa/memory';\n\nconst verifyRes = await fetch('https://api.targetservice.com/siwa/verify', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ message, signature })\n});\nconst session = await verifyRes.json();\n\nif (session.success) {\n  appendToMemorySection('Sessions',\n    `- **${agentId}@api.targetservice.com**: \\`${session.token}\\` (exp: ${expirationTime || 'none'})`\n  );\n}\n\nSIWA Message Format\n{domain} wants you to sign in with your Agent account:\n{address}\n\n{statement}\n\nURI: {uri}\nVersion: 1\nAgent ID: {agentId}\nAgent Registry: {agentRegistry}\nChain ID: {chainId}\nNonce: {nonce}\nIssued At: {issuedAt}\n[Expiration Time: {expirationTime}]\n[Not Before: {notBefore}]\n[Request ID: {requestId}]\n\nServer-Side Verification\n\nThe server MUST:\n\nRecover signer from signature (EIP-191)\nMatch recovered address to message address\nValidate domain binding, nonce, time window\nCall ownerOf(agentId) onchain to confirm signer owns the agent NFT\n(Optional) Evaluate SIWAVerifyCriteria — activity status, required services, trust models, reputation score\nIssue session token\n\nverifySIWA() in @buildersgarden/siwa/siwa accepts an optional criteria parameter (6th argument) to enforce requirements after the ownership check:\n\nimport { verifySIWA } from '@buildersgarden/siwa/siwa';\n\nconst result = await verifySIWA(message, signature, domain, nonceValid, provider, {\n  mustBeActive: true,              // agent metadata.active must be true\n  requiredServices: ['MCP'],       // ServiceType values from ERC-8004\n  requiredTrust: ['reputation'],   // TrustModel values from ERC-8004\n  minScore: 0.5,                   // minimum reputation score\n  minFeedbackCount: 10,            // minimum feedback count\n  reputationRegistryAddress: '0x8004BAa1...9b63',\n});\n\n// result.agent contains the full AgentProfile when criteria are provided\n\n\nSee the test server's verifySIWARequest() for a full reference implementation.\n\nEndpoint\tMethod\tDescription\n/siwa/nonce\tPOST\tGenerate and return a nonce\n/siwa/verify\tPOST\tAccept { message, signature }, verify, return session/JWT\nMEMORY.md Quick Reference\nSection\tWhen Written\tKey Fields\nWallet\tStep 1 of SIGN UP\tAddress, Keystore Backend, Created At\nRegistration\tStep 4 of SIGN UP\tStatus, Agent ID, Agent Registry, Agent URI, Chain ID\nAgent Profile\tStep 2 of SIGN UP\tName, Description, Image\nServices\tAfter adding endpoints\tOne line per service\nSessions\tAfter each SIWA sign-in\tToken, domain, expiry per session\nNotes\tAny time\tFree-form (funding tx, faucet used, etc.)\n\nWhat is NOT in MEMORY.md: Private keys, keystore passwords, mnemonic phrases.\n\nReference Files\nreferences/security-model.md — Threat model, keystore architecture, prompt injection defense\nreferences/siwa-spec.md — Full SIWA protocol specification (message ABNF, field definitions, security considerations)\nreferences/contract-addresses.md — Deployed registry addresses per chain, ABI fragments\nreferences/registration-guide.md — Detailed registration file schema, endpoint types, update flows\nCore Library (@buildersgarden/siwa package)\n@buildersgarden/siwa/keystore — Secure key storage abstraction with keyring proxy support\n@buildersgarden/siwa/memory — MEMORY.md read/write helpers (public data only)\n@buildersgarden/siwa/siwa — SIWA message building, signing (via keystore), and server-side verification (with optional criteria)\n@buildersgarden/siwa/registry — Read agent profiles (getAgent) and reputation (getReputation) from on-chain registries. Exports ERC-8004 typed values: ServiceType, TrustModel, ReputationTag\n@buildersgarden/siwa/proxy-auth — HMAC-SHA256 authentication utilities for the keyring proxy\nAssets\nassets/MEMORY.md.template — Template for the agent's public identity memory file\nassets/registration-template.json — Starter registration file template"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/limone-eth/erc8004-agent",
    "publisherUrl": "https://clawhub.ai/limone-eth/erc8004-agent",
    "owner": "limone-eth",
    "version": "0.0.2",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/erc8004-agent",
    "downloadUrl": "https://openagent3.xyz/downloads/erc8004-agent",
    "agentUrl": "https://openagent3.xyz/skills/erc8004-agent/agent",
    "manifestUrl": "https://openagent3.xyz/skills/erc8004-agent/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/erc8004-agent/agent.md"
  }
}