Skip to main content

siwa-server

> Use when this capability is needed.

Ir a la instalación

Datos de origen

Repositorio
tomevault-io/tomes
Última actividad en el origen
23 de julio de 2026 a las 21:48
Idioma detectado de SKILL.md
inglés
Estrellas
1
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
siwa-server
description
> Use when this capability is needed.
# SIWA Server-Side Verification This guide covers **server-side SIWA verification** for backends and APIs that need to authenticate agents. No wallet or signing required — only verification. For full API reference and advanced options, see [https://siwa.id/docs](https://siwa.id/docs). --- ## Quick Start ### 1. Install ```bash npm install @buildersgarden/siwa viem ``` ### 2. Set Environment Variables ```typescript import { parseSIWAMessage, verifySIWA, createClientResolver, parseChainId } from "@buildersgarden/siwa"; // Dynamic client resolver — supports all chains, no hardcoding needed const resolver = createClientResolver(); async function verifyAgent(message: string, signature: string) { const fields = parseSIWAMessage(message); const chainId = parseChainId(fields.agentRegistry); const client = resolver.getClient(chainId!); const result = await verifySIWA( message, signature, "api.example.com", (nonce) => validateAndConsumeNonce(nonce), client, ); if (!result.valid) { throw new Error(result.error); } return { address: result.address, agentId: result.agentId, verified: result.verified, // "onchain" | "offline" }; } ``` --- ## Framework Middleware The SDK provides pre-built middleware that handles SIWA sign-in (nonce + verify), ERC-8128 request verification, receipts, and CORS — all in a few lines. --- ## Complete Server Implementation ### Express.js ```typescript import express from "express"; import { randomBytes } from "crypto"; import { parseSIWAMessage, verifySIWA, createClientResolver, parseChainId } from "@buildersgarden/siwa"; import { createReceipt, verifyReceipt } from "@buildersgarden/siwa/receipt"; import { verifyAuthenticatedRequest } from "@buildersgarden/siwa/erc8128"; const app = express(); app.use(express.json()); // Dynamic client resolver — supports all chains, no hardcoding needed const resolver = createClientResolver(); // In-memory nonce store (use Redis in production) const nonceStore = new Map<string, { nonce: string; expires: number }>(); const SIWA_SECRET = process.env.SIWA_SECRET || "change-me-in-production"; // ─── Nonce Endpoint ────────────────────────────────────────────────── app.post("/api/siwa/nonce", (req, res) => { const { address, agentId, agentRegistry } = req.body; if (!address || agentId === undefined || !agentRegistry) { return res.status(400).json({ error: "Missing required fields" }); } const nonce = randomBytes(16).toString("hex"); const issuedAt = new Date().toISOString(); const expirationTime = new Date(Date.now() + 10 * 60 * 1000).toISOString(); // Store nonce with expiration const key = `${address}:${agentId}:${agentRegistry}`; nonceStore.set(key, { nonce, expires: Date.now() + 10 * 60 * 1000 }); const chainId = parseChainId(agentRegistry); res.json({ nonce, issuedAt, expirationTime, chainId }); }); // ─── Verify Endpoint ───────────────────────────────────────────────── app.post("/api/siwa/verify", async (req, res) => { const { message, signature } = req.body; if (!message || !signature) { return res.status(400).json({ error: "Missing message or signature" }); } try { // 1. Parse the SIWA message and resolve the client for this chain const fields = parseSIWAMessage(message); const chainId = parseChainId(fields.agentRegistry); if (!chainId) { return res.status(400).json({ error: "Invalid agentRegistry format" }); } const client = resolver.getClient(chainId); // 2. Verify nonce was issued by us const key = `${fields.address}:${fields.agentId}:${fields.agentRegistry}`; const stored = nonceStore.get(key); if (!stored) { return res.status(401).json({ error: "Invalid or expired nonce" }); } if (stored.nonce !== fields.nonce) { return res.status(401).json({ error: "Nonce mismatch" }); } if (Date.now() > stored.expires) { nonceStore.delete(key); return res.status(401).json({ error: "Nonce expired" }); } // 3. Verify signature and onchain registration const result = await verifySIWA( message, signature, process.env.DOMAIN || "localhost", (nonce) => { // Validate nonce was issued by us and consume it if (stored.nonce !== nonce) return false; nonceStore.delete(key); return true; }, client, ); if (!result.valid) { return res.status(401).json({ error: result.error }); } // 4. Create receipt for authenticated API calls const { receipt } = createReceipt({ address: result.address, agentId: result.agentId, agentRegistry: result.agentRegistry, chainId: result.chainId, verified: result.verified, }, { secret: SIWA_SECRET, ttl: 3600_000, // 1 hour in ms }); res.json({ success: true, address: result.address, agentId: result.agentId, verified: result.verified, receipt, }); } catch (error: any) { res.status(400).json({ error: error.message }); } }); // ─── Protected Endpoint (ERC-8128) ─────────────────────────────────── app.post("/api/agent-action", async (req, res) => { try { // Verify the ERC-8128 signed request const result = await verifyAuthenticatedRequest(req, { receiptSecret: SIWA_SECRET, }); if (!result.valid) { return res.status(401).json({ error: result.error }); } // Access verified agent info const { address, agentId } = result.agent; // Process the action const { action, params } = req.body; res.json({ success: true, agent: { address, agentId, verified }, result: `Processed ${action} for agent #${agentId}`, }); } catch (error: any) { res.status(401).json({ error: error.message }); } }); app.listen(3000, () => { console.log("SIWA server running on http://localhost:3000"); }); ``` ### Next.js App Router **lib/siwa-resolver.ts** (shared module) ```typescript import { createClientResolver, createMemorySIWANonceStore } from "@buildersgarden/siwa"; export const resolver = createClientResolver(); export const nonceStore = createMemorySIWANonceStore(); ``` **app/api/siwa/nonce/route.ts** ```typescript import { NextResponse } from "next/server"; import { createSIWANonce, parseChainId } from "@buildersgarden/siwa"; import { resolver, nonceStore } from "@/lib/siwa-resolver"; export async function POST(req: Request) { const { address, agentId, agentRegistry } = await req.json(); if (!address || agentId === undefined || !agentRegistry) { return NextResponse.json({ error: "Missing required fields" }, { status: 400 }); } const chainId = parseChainId(agentRegistry); if (!chainId) { return NextResponse.json({ error: "Invalid agentRegistry format" }, { status: 400 }); } const client = resolver.getClient(chainId); const result = await createSIWANonce( { address, agentId, agentRegistry }, client, { nonceStore }, ); if (result.status !== "nonce_issued") { return NextResponse.json(result, { status: 403 }); } return NextResponse.json({ nonce: result.nonce, issuedAt: result.issuedAt, expirationTime: result.expirationTime, chainId, }); } ``` **app/api/siwa/verify/route.ts** ```typescript import { NextResponse } from "next/server"; import { parseSIWAMessage, verifySIWA, parseChainId } from "@buildersgarden/siwa"; import { createReceipt } from "@buildersgarden/siwa/receipt"; import { resolver, nonceStore } from "@/lib/siwa-resolver"; const SIWA_SECRET = process.env.SIWA_SECRET!; export async function POST(req: Request) { const { message, signature } = await req.json(); if (!message || !signature) { return NextResponse.json({ error: "Missing message or signature" }, { status: 400 }); } try { const fields = parseSIWAMessage(message); const chainId = parseChainId(fields.agentRegistry); if (!chainId) { return NextResponse.json({ error: "Invalid agentRegistry format" }, { status: 400 }); } const client = resolver.getClient(chainId); const result = await verifySIWA( message, signature, process.env.NEXT_PUBLIC_DOMAIN!, { nonceStore }, client, ); if (!result.valid) { return NextResponse.json({ error: result.error }, { status: 401 }); } // Create receipt const { receipt } = createReceipt({ address: result.address, agentId: result.agentId, agentRegistry: result.agentRegistry, chainId: result.chainId, verified: result.verified, }, { secret: SIWA_SECRET, ttl: 3600_000, // 1 hour in ms }); return NextResponse.json({ success: true, address: result.address, agentId: result.agentId, verified: result.verified, receipt, }); } catch (error: any) { return NextResponse.json({ error: error.message }, { status: 400 }); } } ``` **app/api/protected/route.ts** ```typescript import { NextResponse } from "next/server"; import { verifyAuthenticatedRequest } from "@buildersgarden/siwa/erc8128"; const SIWA_SECRET = process.env.SIWA_SECRET!; export async function GET(req: Request) { const result = await verifyAuthenticatedRequest(req, { receiptSecret: SIWA_SECRET, }); if (!result.valid) { return NextResponse.json({ error: result.error }, { status: 401 }); } return NextResponse.json({ message: `Hello Agent #${result.agent.agentId}!`, agent: result.agent, }); } export async function POST(req: Request) { const result = await verifyAuthenticatedRequest(req, { receiptSecret: SIWA_SECRET, }); if (!result.valid) { return NextResponse.json({ error: result.error }, { status: 401 }); } const body = await req.json(); return NextResponse.json({ success: true, agent: result.agent, received: body, }); } ``` --- ## SDK Wrappers for Express & Next.js The SDK provides pre-built middleware for common frameworks: ### Express Middleware ```typescript import express from "express"; import { siwaMiddleware, siwaJsonParser, siwaCors } from "@buildersgarden/siwa/express"; const app = express(); // Apply SIWA middleware to protected routes — no hardcoded chain needed app.use("/api/protected", siwaMiddleware({ receiptSecret: process.env.SIWA_SECRET!, })); app.get("/api/protected/data", (req, res) => { // req.agent contains verified agent info const { address, agentId, verified } = req.agent; res.json({ message: `Hello Agent #${agentId}!`, address, verified, }); }); ``` ### Next.js Wrapper ```typescript import { withSiwa, siwaOptions } from "@buildersgarden/siwa/next"; export const POST = withSiwa(async (agent, req) => { const body = await req.json(); return { agent: { address: agent.address, agentId: agent.agentId }, received: body }; }, { receiptSecret: process.env.SIWA_SECRET!, allowedSignerTypes: ['eoa', 'sca'], }); export { siwaOptions as OPTIONS }; ``` ### Express ```typescript 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({
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub