详细分析 ▾
运行时依赖
版本
SIWA 0.0.4 brings major documentation overhaul and new wallet-specific guides. - Major overhaul of documentation for clarity and quick starts. - Added wallet-specific skill docs (Bankr, Circle, Privy, Private Key, Keyring Proxy). - Introduced a new modular SKILL.md with a concise overview of SDK modules and examples. - Dedicated server-side verification guide. - Removed outdated unified keyring skill; replaced with self-hosted proxy doc. - New sections for x402 payment flows and reverse CAPTCHA support for agent verification.
安装命令
点击复制技能文档
Sign-In With Agent (SIWA) lets AI agents authenticate with services using their ERC-8004 onchain identity.
Install
npm install @buildersgarden/siwa
Skills
Agent-Side (Signing)
Choose based on your wallet provider:
- Bankr — Bankr Agent API wallets
- Circle — Circle developer-controlled wallets
- Privy — Privy server wallets
- 私有 键 — Raw 私有 键 (viem LocalAccount)
- Keyring Proxy — Self-hosted proxy 带有 可选 2FA
Server-Side (Verification)
- Server-Side Verification — 下一个.js, Express, Hono, Fastify
SDK Modules
| Import | Description |
|---|---|
@buildersgarden/siwa | Core: signSIWAMessage, verifySIWA, createSIWANonce, parseSIWAMessage, buildSIWAMessage, createClientResolver, parseChainId |
@buildersgarden/siwa/signer | Signer factories (see wallet-specific skills above) |
@buildersgarden/siwa/erc8128 | ERC-8128 HTTP signing/verification |
@buildersgarden/siwa/receipt | HMAC receipt helpers |
@buildersgarden/siwa/nonce-store | Nonce stores (Memory, Redis, KV) |
@buildersgarden/siwa/identity | SIWA_IDENTITY.md helpers |
@buildersgarden/siwa/registry | Onchain agent registration |
@buildersgarden/siwa/client-resolver | Dynamic PublicClient resolution for multi-chain servers |
@buildersgarden/siwa/next | Next.js middleware (withSiwa, siwaOptions) |
@buildersgarden/siwa/express | Express middleware (siwaMiddleware, siwaJsonParser, siwaCors) |
@buildersgarden/siwa/hono | Hono middleware (siwaMiddleware, siwaCors) |
@buildersgarden/siwa/fastify | Fastify middleware (siwaPlugin, siwaAuth) |
@buildersgarden/siwa/x402 | x402 payment helpers |
@buildersgarden/siwa/captcha | Reverse CAPTCHA (prove you're an AI) |
x402 Payments (Agent-Side)
When an API requires payment, it returns HTTP 402 with a Payment-Required header. The agent decodes the payment options, constructs a signed payment, and retries with a Payment-Signature header — all while maintaining SIWA authentication.
Handling 402 响应
import { encodeX402Header, decodeX402Header, type PaymentRequired, type PaymentPayload, } from "@buildersgarden/siwa/x402"; import { signAuthenticatedRequest } from "@buildersgarden/siwa/erc8128";// 1. Make initial authenticated request (may get 402) const signedRequest = await signAuthenticatedRequest( new Request("https://api.example.com/premium", { method: "POST" }), receipt, signer, 84532, );
const res = await fetch(signedRequest);
if (res.status === 402) { // 2. Decode payment requirements from header const header = res.headers.get("Payment-Required"); const { accepts, resource } = decodeX402Header(header!);
// 3. Pick a payment option and construct payload const option = accepts[0]; const payload: PaymentPayload = { signature: "0x...", // sign the payment with your wallet payment: { scheme: option.scheme, network: option.network, amount: option.amount, asset: option.asset, payTo: option.payTo, }, resource, };
// 4. Retry with both SIWA auth + payment header const retryRequest = await signAuthenticatedRequest( new Request("https://api.example.com/premium", { method: "POST", headers: { "Payment-Signature": encodeX402Header(payload), }, }), receipt, signer, 84532, );
const paidRes = await fetch(retryRequest); // paidRes.headers.get("Payment-Response") contains { txHash, ... } }
x402 Headers
| Header | Direction | Description |
|---|---|---|
Payment-Required | Server → Agent | Base64-encoded JSON with accepted payment options. Sent with 402. |
Payment-Signature | Agent → Server | Base64-encoded signed payment payload. |
Payment-Response | Server → Agent | Base64-encoded settlement result with transaction hash. |
Pay-Once Sessions
Some endpoints use pay-once mode: the first request requires payment, subsequent requests from the same agent to the same resource pass through without payment until the session expires. If you receive a 200 on a previously-paid endpoint, the session is still active — no need to pay again.
Captcha (Reverse CAPTCHA)
SIWA includes a "reverse CAPTCHA" mechanism — inspired by MoltCaptcha — that proves an entity is an AI agent, not a human. Challenges exploit how LLMs generate text in a single autoregressive pass (satisfying multiple constraints simultaneously), while humans must iterate.
Two integration points:
- 签名-在...中 flow — server requires captcha 之前 issuing nonce
- Per-请求 — 中间件 randomly challenges agents 期间 authenticated API calls
Agent-Side: Handling Captcha Challenge
The SDK provides two convenience wrappers for the captcha retry pattern:
签名-在...中 Captcha: solveCaptchaChallenge()
import { solveCaptchaChallenge } from "@buildersgarden/siwa/captcha";// 1. Request nonce const nonceRes = await fetch("/api/siwa/nonce", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address, agentId, agentRegistry }), }); const data = await nonceRes.json();
// 2. Detect + solve captcha if required const captcha = await solveCaptchaChallenge(data, async (challenge) => { // LLM generates text satisfying all constraints in a single pass // challenge: { topic, format, lineCount, asciiTarget, wordCount?, timeLimitSeconds, ... } // Your LLM generates text satisfying all constraints in one pass. // Use any provider (Anthropic, OpenAI, etc.) — the solver just returns a string. return await generateText(challenge); });
if (captcha.solved) { // 3. Retry with challenge response const retryRes = await fetch("/api/siwa/nonce", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address, agentId, agentRegistry, challengeResponse: captcha.challengeResponse }), }); }
Per-请求 Captcha: retryWithCaptcha()
import { signAuthenticatedRequest, retryWithCaptcha } from "@buildersgarden/siwa/erc8128";const url = "https://api.example.com/action"; const body = JSON.stringify({ key: "value" });
// 1. Sign and send const signed = await signAuthenticatedRequest( new Request(url, { method: "POST", body }), receipt, signer, chainId, ); const response = await fetch(signed);
// 2. Detect + solve captcha, re-sign, and get retry request const result = await retryWithCaptcha( response, new Request(url, { method: "POST", body }), // fresh request (original body consumed) receipt, signer, chainId, async (challenge) => generateText(challenge), // your LLM solver );
if (result.retry) { const retryResponse = await fetch(result.request); }
Note: Pass a fresh, unconsumed Request to retryWithCaptcha — the original is consumed after signing/sending.Difficulty Levels
| Level | Time Limit | Constraints |
|---|---|---|
easy | 30s | Line count + ASCII sum of first chars |
medium | 20s | + word count |
hard | 15s | + character at specific position |
extreme | 10s | + total character count |