This project is a work in progress. APIs and documentation may change.

SIWA Documentation

Sign In With Agent — v1.0

Agent? If you are an AI agent, read /skill.md instead — it contains structured instructions for wallet creation, registration, and SIWA authentication.

Getting Started

SIWA gives your AI agent an onchain identity — a wallet, a verifiable profile, and secure authentication. The agent can prove who it is to any service, and every request is cryptographically signed. Private keys are kept safe in a separate process, so even if the agent is compromised, the keys stay protected.

Deploy

Spin up a secure onchain agent in one click. This deploys everything you need — a signing proxy that keeps keys safe and an agent gateway, pre-wired and ready to go.

Deploy on Railway

Once deployed, your agent has a wallet, can register onchain, and authenticate with any SIWA-compatible service. For configuration details, see the deployment guide.

How It Works

  1. 1.The agent asks the service for a nonce (a one-time challenge), sending its address and agent ID.
  2. 2.The service returns the nonce along with timestamps.
  3. 3.The agent builds a SIWA message containing those fields and signs it with its private key.
  4. 4.The agent sends the message and signature back to the service.
  5. 5.The service verifies the signature and calls the blockchain to confirm the agent actually owns that identity NFT.
  6. 6.If everything checks out, the service returns a verification receipt. The agent attaches this receipt to every subsequent request, signed with ERC-8128 HTTP Message Signatures.

The SDK gives you two functions to implement this:

  • Agent-side: call signSIWAMessage(), which builds the message and signs it in one step (steps 3-4 above).
  • Server-side: call verifySIWA() to validate the signature, check all fields, and recover the agent's identity (step 5).
SIWA authentication flow: Agent requests nonce, signs SIWA message, Service verifies signature and checks onchain ownership, returns HMAC-signed verification receipt, then subsequent API requests use ERC-8128 HTTP signatures

Before signing in or registering on ERC-8004, the agent needs a wallet. The SDK provides createWallet() for this. The agent uses it to generate a wallet whose private key is stored in a keyring proxy (a separate process), so the agent never touches it directly. This is what keeps the key safe even if the agent is compromised.

For wallet setup, proxy deployment, and onchain registration, see the deployment guide and the keyring proxy section below.

Network Topology

A deployed SIWA agent runs as a set of containers on a private network. Each component has a single job:

  • Keyring Proxy — holds the agent's encrypted private key and performs all signing. Never exposed publicly.
  • OpenClaw — the AI agent gateway. Routes messages from users, delegates signing to the keyring proxy, and handles onchain verification and registration.
  • 2FA Gateway + Server (optional) — adds Telegram-based owner approval before high-value signing operations. The gateway receives Telegram webhooks; the server manages approval flows. The agent owner gets a Telegram message and taps to approve or reject.
SIWA network topology: Users connect to OpenClaw gateway, which delegates signing to the Keyring Proxy over a private network. Optional 2FA flow routes through a 2FA Server and Gateway to Telegram for owner approval.

The keyring proxy, OpenClaw, and 2FA server communicate over a private Docker network — none of them need public internet access. Only the 2FA gateway (for Telegram webhooks) and OpenClaw (for user-facing chat) are exposed externally.

API Reference

Install the SDK: npm install @buildersgarden/siwa

Signing

All signing is delegated to the keyring proxy — the agent process never touches the private key.

FunctionReturnsDescription
createWallet(){ address, backend }Create a new wallet. Key stored in proxy, never returned.
getAddress()stringGet the wallet's public address.
hasWallet()booleanCheck if a wallet exists.
signMessage(msg){ signature, address }Sign a message (EIP-191).
signRawMessage(rawHex){ signature, address }Sign raw bytes without EIP-191 prefix (used by ERC-8128).
signTransaction(tx){ signedTx, address }Sign a transaction.
signAuthorization(auth)SignedAuthorizationEIP-7702 delegation signing.

Import from @buildersgarden/siwa/keystore. All functions accept an optional KeystoreConfig ({proxyUrl, proxySecret}).

SIWA Message Signing

FunctionReturnsDescription
buildSIWAMessage(fields)stringBuild a formatted SIWA message string.
signSIWAMessage(fields, keystoreConfig?){ message, signature, address }Build, sign, and return the SIWA message. Address auto-resolved from keystore.

Import from @buildersgarden/siwa.

ERC-8128 Request Signing

After SIWA sign-in, every subsequent API request is signed with ERC-8128 HTTP Message Signatures and carries a verification receipt.

FunctionReturnsDescription
signAuthenticatedRequest(request, receipt, config, chainId)RequestAttach receipt + ERC-8128 signature to an outgoing request.
createProxySigner(config, chainId)EthHttpSignerCreate an RFC 9421 signer backed by the keyring proxy.
attachReceipt(request, receipt)RequestSet the X-SIWA-Receipt header on a request.

Import from @buildersgarden/siwa/erc8128.

Verification

Server-side functions for verifying agent identity and authenticating requests.

SIWA Verification

Verify a SIWA signature, check onchain ownership, and optionally validate agent profile and reputation — all in one call.

FunctionReturnsDescription
verifySIWA(msg, sig, domain, nonceValid, client, criteria?)SIWAVerificationResultVerify signature + onchain ownership. client is a viem PublicClient.

Import from @buildersgarden/siwa. The optional criteria argument validates the agent's profile after the ownership check:

Criteria FieldTypeDescription
mustBeActivebooleanRequire metadata.active === true.
requiredServicesServiceType[]Agent must expose all listed service types (e.g. 'MCP', 'A2A').
requiredTrustTrustModel[]Agent must support all listed trust models.
minScorenumberMinimum reputation score.
minFeedbackCountnumberMinimum reputation feedback count.
reputationRegistryAddressstringRequired when using minScore or minFeedbackCount.
custom(agent) => booleanCustom validation function receiving the full AgentProfile.
import { verifySIWA } from '@buildersgarden/siwa';
const result = await verifySIWA(
message,
signature,
'api.example.com',
(nonce) => nonceStore.consume(nonce),
client,
{
mustBeActive: true,
requiredServices: ['MCP'],
minScore: 0.5,
reputationRegistryAddress: '0x8004BAa1...9b63',
}
);

ERC-8128 Request Verification

Verify the ERC-8128 signature and receipt on incoming requests.

FunctionReturnsDescription
verifyAuthenticatedRequest(request, options)AuthResultVerify ERC-8128 signature + receipt + optional onchain check.
expressToFetchRequest(req)RequestConvert an Express request to a Fetch API Request.
nextjsToFetchRequest(req)RequestNormalize a Next.js request for proxy situations.

Import from @buildersgarden/siwa/erc8128. VerifyOptions: receiptSecret, rpcUrl, verifyOnchain, publicClient.

Receipts

Stateless HMAC-signed tokens that prove onchain registration was checked during SIWA sign-in.

ExportReturnsDescription
createReceipt(payload, options){ receipt, expiresAt }Create an HMAC-signed receipt.
verifyReceipt(receipt, secret)ReceiptPayload | nullVerify and decode. Returns null if invalid or expired.
DEFAULT_RECEIPT_TTLnumberDefault TTL: 30 minutes (1800000 ms).

Import from @buildersgarden/siwa/receipt.

Server Wrappers

High-level middleware that handles ERC-8128 verification, receipt checking, CORS, and error responses — so you don't have to wire the low-level functions yourself.

Next.js

ExportDescription
withSiwa(handler, options?)Wrap a route handler with ERC-8128 auth. Handles body cloning, URL normalization, CORS, and 401 on failure.
siwaOptions()Return a 204 OPTIONS response with CORS headers.
corsJson(data, init?)JSON Response with CORS headers.
corsHeaders()Raw CORS headers record for custom responses.
import { withSiwa, siwaOptions } from "@buildersgarden/siwa/next";
export const POST = withSiwa(async (agent, req) => {
const body = await req.json();
return { received: body, agent: { address: agent.address, agentId: agent.agentId } };
});
export const GET = withSiwa(async (agent) => {
return { message: `Hello Agent #${agent.agentId}!` };
});
export { siwaOptions as OPTIONS };

Options: receiptSecret (defaults to RECEIPT_SECRET or SIWA_SECRET env), rpcUrl, verifyOnchain.

Express

ExportDescription
siwaMiddleware(options?)Auth middleware: verifies ERC-8128 + receipt, sets req.agent, returns 401 on failure.
siwaJsonParser()express.json() with rawBody capture for Content-Digest verification.
siwaCors(options?)CORS middleware with SIWA-specific headers.
import express from 'express';
import { siwaMiddleware, siwaJsonParser, siwaCors } from "@buildersgarden/siwa/express";
const app = express();
app.use(siwaJsonParser());
app.use(siwaCors());
app.get('/api/protected', siwaMiddleware(), (req, res) => {
res.json({ agent: req.agent });
});

Identity & Registry

Read and write agent identity state, and query onchain profiles and reputation.

Identity File

The agent's IDENTITY.md stores 4 public fields: Address, Agent ID, Agent Registry, Chain ID.

FunctionDescription
ensureIdentityExists(path, template)Initialize IDENTITY.md from template if missing.
readIdentity(path)Parse IDENTITY.md into a typed AgentIdentity object.
writeIdentityField(key, value, path)Write a single field to IDENTITY.md.
hasWalletRecord(path)Check if an address is recorded in IDENTITY.md.
isRegistered({ identityPath, client? })Check registration (local cache or onchain ownerOf).

Import from @buildersgarden/siwa/identity.

Onchain Registry

FunctionReturnsDescription
getAgent(agentId, options)AgentProfileRead agent profile from the Identity Registry (owner, tokenURI, agentWallet, metadata).
getReputation(agentId, options)ReputationSummaryRead agent reputation summary from the Reputation Registry.

Import from @buildersgarden/siwa/registry. Both accept a viem PublicClient via options.client.

TypeValues
ServiceType'web' | 'A2A' | 'MCP' | 'OASF' | 'ENS' | 'DID' | 'email'
TrustModel'reputation' | 'crypto-economic' | 'tee-attestation'
ReputationTag'starred' | 'reachable' | 'ownerVerified' | 'uptime' | 'successRate' | 'responseTime' | 'blocktimeFreshness' | 'revenues' | 'tradingYield'

Helpers

FunctionModuleDescription
computeHMAC(secret, method, path, body, timestamp)@buildersgarden/siwa/proxy-authCompute HMAC-SHA256 signature for a keyring proxy request.

Security Model

The agent's private key is the root of its onchain identity. SIWA's security architecture ensures the key never enters the agent process.

Keyring Proxy

All signing is delegated to a separate keyring proxy server over HMAC-authenticated HTTP. The proxy holds the encrypted key and performs all cryptographic operations.

Agent Process Keyring Proxy
(KEYSTORE_BACKEND=proxy) (holds encrypted key)
signMessage("hello")
|
+--> POST /sign-message
+ HMAC-SHA256 header ---> Validates HMAC + timestamp (30s)
Loads key, signs, discards
<-- Returns { signature, address }
PropertyDetail
Key isolationPrivate key lives in a separate OS process; never enters agent memory.
Transport authHMAC-SHA256 over method + path + body + timestamp; 30-second replay window.
Audit trailEvery signing request logged with timestamp, endpoint, source IP.
Compromise limitEven full agent takeover can only request signatures — cannot extract key.

Threat Model

ThreatMitigation
Prompt injection exfiltrationKey never in any file the agent reads into context.
Context window leakageKey loaded inside function, used, and discarded — never returned.
File system snoopingAES-encrypted V3 JSON Keystore (scrypt KDF).
Log / error exposureSigning functions return only signatures, never raw keys.
Accidental commitNo file in the project ever contains the plaintext key.

IDENTITY.md: Public Data Only

The agent's identity file stores only public state — address, agentId, agentRegistry, chainId. The private key is never written to IDENTITY.md or any other file the agent reads.

2FA via Telegram

For high-value operations, the keyring proxy can require owner approval before signing. This adds a second factor — the agent can request a signature, but the owner must explicitly approve it through Telegram.

Agent requests signature
|
+--> Keyring Proxy
|
+--> 2FA Server (approval queue)
|
+--> 2FA Gateway --> Telegram bot message
Owner taps Approve / Reject
<-- Callback to 2FA Server
<-- Signature (if approved)
<-- Returns to agent

The flow adds two components to the private network:

ComponentRole
2FA ServerManages the approval queue. Receives signing requests from the keyring proxy, holds them until the owner responds, and returns the result.
2FA GatewayConnects to Telegram Bot API. Sends approval messages and receives webhook callbacks. This is the only 2FA component exposed to the internet.

Both the keyring proxy and the 2FA server run inside the private network — they are never exposed publicly. Only the 2FA gateway needs internet access for Telegram webhooks.

PropertyDetail
ScopeConfigurable per operation type — e.g. require approval for transactions but not for message signing.
TimeoutPending approvals expire after a configurable window (default 5 minutes).
AuditEvery approval request and response is logged with timestamp and Telegram user ID.
FallbackIf the owner doesn't respond within the timeout, the request is rejected by default.

Protocol Specification

Message Format

{domain} wants you to sign in with your Agent account:
{address}
{statement}
URI: {uri}
Version: 1
Agent ID: {agentId}
Agent Registry: {agentRegistry}
Chain ID: {chainId}
Nonce: {nonce}
Issued At: {issuedAt}
[Expiration Time: {expirationTime}]
[Not Before: {notBefore}]
[Request ID: {requestId}]

Field Definitions

FieldRequiredDescription
domainYesOrigin domain requesting authentication.
addressYesEIP-55 checksummed Ethereum address.
statementNoHuman-readable purpose string.
uriYesRFC 3986 URI of the resource.
versionYesMust be "1".
agentIdYesERC-721 tokenId in the Identity Registry.
agentRegistryYeseip155:{chainId}:{registryAddress}
chainIdYesEIP-155 Chain ID.
nonceYesServer-generated, >= 8 alphanumeric chars.
issuedAtYesRFC 3339 datetime.
expirationTimeNoRFC 3339 datetime.
notBeforeNoRFC 3339 datetime.
requestIdNoOpaque request identifier.

Authentication Flow

1. Nonce Request — Agent sends address, agentId, agentRegistry to POST /siwa/nonce. Server returns nonce + timestamps.

2. Agent Signs — Agent builds SIWA message and signs via EIP-191 personal_sign.

3. Verification — Agent submits message + signature to POST /siwa/verify.

4. Server Checks — Parse message, recover signer, verify address match, check domain binding, validate nonce + time window, call ownerOf(agentId) onchain.

5. Receipt — Issue an HMAC-signed verification receipt with address, agentId, agentRegistry, chainId. Agent uses this with ERC-8128 per-request signatures.

SIWA vs SIWE

AspectSIWE (EIP-4361)SIWA
PurposeHuman wallet authAgent identity auth
Identity proofOwns an Ethereum addressOwns an ERC-8004 agent NFT
Onchain checkNone requiredownerOf(agentId) REQUIRED
Extra fieldsNoneagentId, agentRegistry
SigningEIP-191EIP-191 (same)
Message prefix...your Ethereum account...your Agent account

Contract Addresses

Mainnet

ChainChain IDIdentity RegistryReputation Registry
Base84530x8004A169FB4a3325136EB29fA0ceB6D2e539a4320x8004BAa17C55a88189AE136b182e5fdA19dE9b63

Testnets

ChainChain IDIdentity Registry
Base Sepolia845320x8004A818BFB912233c491871b3d84c89A494BD9e
ETH Sepolia111551110x8004a6090Cd10A7288092483047B097295Fb8847
Linea Sepolia591410x8004aa7C931bCE1233973a0C6A667f73F66282e7
Polygon Amoy800020x8004ad19E14B9e0654f73353e8a0B600D46C2898

Solana

NetworkProgram ID
DevnetHvF3JqhahcX7JfhbDRYYCJ7S3f6nJdrqu5yi9shyTREp

Agent Registry String Format

{namespace}:{chainId}:{identityRegistryAddress}
Examples:
eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 (Base)
eip155:84532:0x8004A818BFB912233c491871b3d84c89A494BD9e (Base Sepolia)

Public RPC Endpoints

ChainRPC URL
Basehttps://mainnet.base.org
Base Sepoliahttps://sepolia.base.org
ETH Sepoliahttps://rpc.sepolia.org
Linea Sepoliahttps://rpc.sepolia.linea.build
Polygon Amoyhttps://rpc-amoy.polygon.technology

For production use, use a provider like Alchemy or Infura with your own API key.

Explorer

View any registered agent at 8004scan.io