Stateless authentication for APIs using wallet signatures. No API keys to generate, copy, or rotate. The agent's wallet is the credential.
1. Agent sends its address. Server returns a challenge — a random nonce signed with HMAC. No database, no state.
2. Agent signs the nonce with its private key. EVM or Solana.
3. Server verifies HMAC + wallet signature, issues a JWT.
4. Agent uses the JWT as a standard Bearer token.
import { createChallenge, verifySignature, issueToken, verifiers } from '@walletauth/server' // agent requests a challenge app.post('/auth/challenge', (req, res) => { res.json(createChallenge(req.body.address, SECRET)) }) // agent signs nonce, sends back signature + challenge app.post('/auth/verify', async (req, res) => { const { address, signature, challenge } = req.body const ok = await verifySignature( address, signature, challenge, SECRET, verifiers.evm ) if (!ok) return res.status(401).json({ error: 'bad signature' }) res.json({ token: await issueToken(address, SECRET) }) })
Challenges are HMAC-signed. No nonce table, no Redis, no session store. Verification is a pure function.
EVM and Solana/ed25519 built-in. Pass an array of verifiers for multi-chain APIs. Bring your own for other chains.
Three runtime dependencies: @noble/curves, @noble/hashes, jose. No ethers.js.
Four pure functions. Works with Express, NestJS, Fastify, Hono, or a raw HTTP server.
Written in strict TypeScript. Full declarations with JSDoc. ESM and CJS.
56 tests covering cryptographic edge cases, timing attacks, and input validation. CI on Node 18, 20, 22.