@buildersgarden/siwa 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/erc8128.d.ts CHANGED
@@ -18,18 +18,26 @@ export interface VerifyOptions {
18
18
  verifyOnchain?: boolean;
19
19
  publicClient?: PublicClient;
20
20
  }
21
+ /** Verified agent identity returned from a successful auth check. */
22
+ export interface SiwaAgent {
23
+ address: string;
24
+ agentId: number;
25
+ agentRegistry: string;
26
+ chainId: number;
27
+ }
21
28
  export type AuthResult = {
22
29
  valid: true;
23
- agent: {
24
- address: string;
25
- agentId: number;
26
- agentRegistry: string;
27
- chainId: number;
28
- };
30
+ agent: SiwaAgent;
29
31
  } | {
30
32
  valid: false;
31
33
  error: string;
32
34
  };
35
+ /**
36
+ * Resolve the receipt secret from an explicit value or environment variables.
37
+ *
38
+ * Checks (in order): `explicit` → `RECEIPT_SECRET` env → `SIWA_SECRET` env → throws.
39
+ */
40
+ export declare function resolveReceiptSecret(explicit?: string): string;
33
41
  /** Header name for the verification receipt */
34
42
  export declare const RECEIPT_HEADER = "X-SIWA-Receipt";
35
43
  /**
package/dist/erc8128.js CHANGED
@@ -12,6 +12,18 @@
12
12
  import { signRequest, verifyRequest, } from '@slicekit/erc8128';
13
13
  import { signRawMessage, getAddress } from './keystore.js';
14
14
  import { verifyReceipt } from './receipt.js';
15
+ /**
16
+ * Resolve the receipt secret from an explicit value or environment variables.
17
+ *
18
+ * Checks (in order): `explicit` → `RECEIPT_SECRET` env → `SIWA_SECRET` env → throws.
19
+ */
20
+ export function resolveReceiptSecret(explicit) {
21
+ const secret = explicit || process.env.RECEIPT_SECRET || process.env.SIWA_SECRET;
22
+ if (!secret) {
23
+ throw new Error('Missing receipt secret: pass receiptSecret option, or set RECEIPT_SECRET / SIWA_SECRET env var');
24
+ }
25
+ return secret;
26
+ }
15
27
  /** Header name for the verification receipt */
16
28
  export const RECEIPT_HEADER = 'X-SIWA-Receipt';
17
29
  // ---------------------------------------------------------------------------
@@ -0,0 +1,61 @@
1
+ /**
2
+ * express.ts
3
+ *
4
+ * Server-side wrappers for Express applications.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { siwaMiddleware, siwaJsonParser, siwaCors } from "@buildersgarden/siwa/express";
9
+ *
10
+ * app.use(siwaJsonParser());
11
+ * app.use(siwaCors());
12
+ * app.get('/api/protected', siwaMiddleware(), (req, res) => {
13
+ * res.json({ agent: req.agent });
14
+ * });
15
+ * ```
16
+ */
17
+ import type { RequestHandler } from 'express';
18
+ import { type SiwaAgent, type VerifyOptions } from './erc8128.js';
19
+ export type { SiwaAgent };
20
+ declare global {
21
+ namespace Express {
22
+ interface Request {
23
+ agent?: SiwaAgent;
24
+ rawBody?: string;
25
+ }
26
+ }
27
+ }
28
+ export interface SiwaMiddlewareOptions {
29
+ /** HMAC secret for receipt verification. Defaults to RECEIPT_SECRET or SIWA_SECRET env. */
30
+ receiptSecret?: string;
31
+ /** RPC URL for optional onchain verification. */
32
+ rpcUrl?: string;
33
+ /** Enable onchain ownerOf check. */
34
+ verifyOnchain?: boolean;
35
+ /** Public client for ERC-1271 or onchain checks. */
36
+ publicClient?: VerifyOptions['publicClient'];
37
+ }
38
+ export interface SiwaCorsOptions {
39
+ /** Allowed origin(s). Defaults to "*". */
40
+ origin?: string;
41
+ /** Allowed HTTP methods. */
42
+ methods?: string;
43
+ /** Allowed headers. */
44
+ headers?: string;
45
+ }
46
+ /**
47
+ * CORS middleware pre-configured with SIWA-specific headers.
48
+ * Handles OPTIONS preflight automatically.
49
+ */
50
+ export declare function siwaCors(options?: SiwaCorsOptions): RequestHandler;
51
+ /**
52
+ * `express.json()` pre-configured with rawBody capture for Content-Digest verification.
53
+ */
54
+ export declare function siwaJsonParser(): RequestHandler;
55
+ /**
56
+ * Express middleware that verifies ERC-8128 HTTP Message Signatures + SIWA receipt.
57
+ *
58
+ * On success, sets `req.agent` with the verified agent identity.
59
+ * On failure, responds with 401.
60
+ */
61
+ export declare function siwaMiddleware(options?: SiwaMiddlewareOptions): RequestHandler;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * express.ts
3
+ *
4
+ * Server-side wrappers for Express applications.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { siwaMiddleware, siwaJsonParser, siwaCors } from "@buildersgarden/siwa/express";
9
+ *
10
+ * app.use(siwaJsonParser());
11
+ * app.use(siwaCors());
12
+ * app.get('/api/protected', siwaMiddleware(), (req, res) => {
13
+ * res.json({ agent: req.agent });
14
+ * });
15
+ * ```
16
+ */
17
+ import express from 'express';
18
+ import { verifyAuthenticatedRequest, expressToFetchRequest, resolveReceiptSecret, } from './erc8128.js';
19
+ // ---------------------------------------------------------------------------
20
+ // CORS middleware
21
+ // ---------------------------------------------------------------------------
22
+ const DEFAULT_SIWA_HEADERS = 'Content-Type, X-SIWA-Receipt, Signature, Signature-Input, Content-Digest';
23
+ /**
24
+ * CORS middleware pre-configured with SIWA-specific headers.
25
+ * Handles OPTIONS preflight automatically.
26
+ */
27
+ export function siwaCors(options) {
28
+ const origin = options?.origin ?? '*';
29
+ const methods = options?.methods ?? 'GET, POST, OPTIONS';
30
+ const headers = options?.headers ?? DEFAULT_SIWA_HEADERS;
31
+ return (req, res, next) => {
32
+ res.header('Access-Control-Allow-Origin', origin);
33
+ res.header('Access-Control-Allow-Methods', methods);
34
+ res.header('Access-Control-Allow-Headers', headers);
35
+ if (req.method === 'OPTIONS') {
36
+ res.sendStatus(204);
37
+ return;
38
+ }
39
+ next();
40
+ };
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // JSON parser with rawBody capture
44
+ // ---------------------------------------------------------------------------
45
+ /**
46
+ * `express.json()` pre-configured with rawBody capture for Content-Digest verification.
47
+ */
48
+ export function siwaJsonParser() {
49
+ return express.json({
50
+ verify: (req, _res, buf) => {
51
+ req.rawBody = buf.toString();
52
+ },
53
+ });
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Auth middleware
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * Express middleware that verifies ERC-8128 HTTP Message Signatures + SIWA receipt.
60
+ *
61
+ * On success, sets `req.agent` with the verified agent identity.
62
+ * On failure, responds with 401.
63
+ */
64
+ export function siwaMiddleware(options) {
65
+ return async (req, res, next) => {
66
+ const hasSignature = req.headers['signature'] && req.headers['x-siwa-receipt'];
67
+ if (!hasSignature) {
68
+ res.status(401).json({
69
+ error: 'Unauthorized — provide ERC-8128 Signature + X-SIWA-Receipt headers',
70
+ });
71
+ return;
72
+ }
73
+ try {
74
+ const secret = resolveReceiptSecret(options?.receiptSecret);
75
+ const fetchReq = expressToFetchRequest(req);
76
+ const result = await verifyAuthenticatedRequest(fetchReq, {
77
+ receiptSecret: secret,
78
+ rpcUrl: options?.rpcUrl,
79
+ verifyOnchain: options?.verifyOnchain,
80
+ publicClient: options?.publicClient,
81
+ });
82
+ if (!result.valid) {
83
+ res.status(401).json({ error: result.error });
84
+ return;
85
+ }
86
+ req.agent = result.agent;
87
+ next();
88
+ }
89
+ catch (err) {
90
+ res.status(401).json({ error: `ERC-8128 auth failed: ${err.message}` });
91
+ }
92
+ };
93
+ }
package/dist/next.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * next.ts
3
+ *
4
+ * Server-side wrappers for Next.js App Router route handlers.
5
+ * Uses only web standard APIs (Request, Response, Headers) — no `next` dependency needed.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { withSiwa, siwaOptions } from "@buildersgarden/siwa/next";
10
+ *
11
+ * export const POST = withSiwa(async (agent, req) => {
12
+ * const body = await req.json();
13
+ * return { received: body, agent: { address: agent.address, agentId: agent.agentId } };
14
+ * });
15
+ *
16
+ * export { siwaOptions as OPTIONS };
17
+ * ```
18
+ */
19
+ import { type SiwaAgent } from './erc8128.js';
20
+ export type { SiwaAgent };
21
+ export interface WithSiwaOptions {
22
+ /** HMAC secret for receipt verification. Defaults to RECEIPT_SECRET or SIWA_SECRET env. */
23
+ receiptSecret?: string;
24
+ /** RPC URL for optional onchain verification. */
25
+ rpcUrl?: string;
26
+ /** Enable onchain ownerOf check. */
27
+ verifyOnchain?: boolean;
28
+ }
29
+ /** CORS headers required by SIWA-authenticated requests. */
30
+ export declare function corsHeaders(): Record<string, string>;
31
+ /** Return a JSON Response with CORS headers. */
32
+ export declare function corsJson(data: unknown, init?: {
33
+ status?: number;
34
+ }): Response;
35
+ /** Return a 204 OPTIONS response with CORS headers. */
36
+ export declare function siwaOptions(): Response;
37
+ type SiwaHandler = (agent: SiwaAgent, req: Request) => Promise<Record<string, unknown> | Response> | Record<string, unknown> | Response;
38
+ /**
39
+ * Wrap a Next.js route handler with SIWA ERC-8128 authentication.
40
+ *
41
+ * - Clones POST/PUT/PATCH requests so the body is available after verification
42
+ * - Normalizes the request URL for reverse-proxy environments
43
+ * - Returns 401 with CORS headers on auth failure
44
+ * - If the handler returns a plain object, it is auto-wrapped in a JSON Response with CORS headers
45
+ */
46
+ export declare function withSiwa(handler: SiwaHandler, options?: WithSiwaOptions): (req: Request) => Promise<Response>;
package/dist/next.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * next.ts
3
+ *
4
+ * Server-side wrappers for Next.js App Router route handlers.
5
+ * Uses only web standard APIs (Request, Response, Headers) — no `next` dependency needed.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { withSiwa, siwaOptions } from "@buildersgarden/siwa/next";
10
+ *
11
+ * export const POST = withSiwa(async (agent, req) => {
12
+ * const body = await req.json();
13
+ * return { received: body, agent: { address: agent.address, agentId: agent.agentId } };
14
+ * });
15
+ *
16
+ * export { siwaOptions as OPTIONS };
17
+ * ```
18
+ */
19
+ import { verifyAuthenticatedRequest, nextjsToFetchRequest, resolveReceiptSecret, } from './erc8128.js';
20
+ // ---------------------------------------------------------------------------
21
+ // CORS helpers
22
+ // ---------------------------------------------------------------------------
23
+ /** CORS headers required by SIWA-authenticated requests. */
24
+ export function corsHeaders() {
25
+ return {
26
+ 'Access-Control-Allow-Origin': '*',
27
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
28
+ 'Access-Control-Allow-Headers': 'Content-Type, X-SIWA-Receipt, Signature, Signature-Input, Content-Digest',
29
+ };
30
+ }
31
+ /** Return a JSON Response with CORS headers. */
32
+ export function corsJson(data, init) {
33
+ return new Response(JSON.stringify(data), {
34
+ status: init?.status ?? 200,
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ ...corsHeaders(),
38
+ },
39
+ });
40
+ }
41
+ /** Return a 204 OPTIONS response with CORS headers. */
42
+ export function siwaOptions() {
43
+ return new Response(null, { status: 204, headers: corsHeaders() });
44
+ }
45
+ /**
46
+ * Wrap a Next.js route handler with SIWA ERC-8128 authentication.
47
+ *
48
+ * - Clones POST/PUT/PATCH requests so the body is available after verification
49
+ * - Normalizes the request URL for reverse-proxy environments
50
+ * - Returns 401 with CORS headers on auth failure
51
+ * - If the handler returns a plain object, it is auto-wrapped in a JSON Response with CORS headers
52
+ */
53
+ export function withSiwa(handler, options) {
54
+ return async (req) => {
55
+ const secret = resolveReceiptSecret(options?.receiptSecret);
56
+ // Clone for body-consuming methods so handler can still read the body
57
+ const hasBody = !['GET', 'HEAD'].includes(req.method);
58
+ const verifyReq = hasBody ? req.clone() : req;
59
+ const verifyOptions = {
60
+ receiptSecret: secret,
61
+ rpcUrl: options?.rpcUrl,
62
+ verifyOnchain: options?.verifyOnchain,
63
+ };
64
+ const result = await verifyAuthenticatedRequest(nextjsToFetchRequest(verifyReq), verifyOptions);
65
+ if (!result.valid) {
66
+ return corsJson({ error: result.error }, { status: 401 });
67
+ }
68
+ const response = await handler(result.agent, req);
69
+ if (response instanceof Response) {
70
+ // Merge CORS headers into existing response
71
+ const headers = new Headers(response.headers);
72
+ for (const [k, v] of Object.entries(corsHeaders())) {
73
+ headers.set(k, v);
74
+ }
75
+ return new Response(response.body, {
76
+ status: response.status,
77
+ statusText: response.statusText,
78
+ headers,
79
+ });
80
+ }
81
+ return corsJson(response);
82
+ };
83
+ }
@@ -8,6 +8,7 @@
8
8
  * npm install viem
9
9
  */
10
10
  import { type PublicClient } from 'viem';
11
+ import { type KeystoreConfig } from './keystore.js';
11
12
  /** Service endpoint types defined in ERC-8004 */
12
13
  export type ServiceType = 'web' | 'A2A' | 'MCP' | 'OASF' | 'ENS' | 'DID' | 'email';
13
14
  /** Trust models defined in ERC-8004 */
@@ -72,3 +73,22 @@ export declare function getAgent(agentId: number, options: GetAgentOptions): Pro
72
73
  * @param options Reputation registry address, client, and optional filters
73
74
  */
74
75
  export declare function getReputation(agentId: number, options: GetReputationOptions): Promise<ReputationSummary>;
76
+ export interface RegisterAgentOptions {
77
+ agentURI: string;
78
+ chainId: number;
79
+ rpcUrl?: string;
80
+ keystoreConfig: KeystoreConfig;
81
+ }
82
+ export interface RegisterAgentResult {
83
+ agentId: string;
84
+ txHash: string;
85
+ registryAddress: string;
86
+ agentRegistry: string;
87
+ }
88
+ /**
89
+ * Register an agent on the ERC-8004 Identity Registry in a single call.
90
+ *
91
+ * Builds, signs (via keyring proxy), and broadcasts the `register(agentURI)`
92
+ * transaction, then waits for confirmation and parses the `Registered` event.
93
+ */
94
+ export declare function registerAgent(options: RegisterAgentOptions): Promise<RegisterAgentResult>;
package/dist/registry.js CHANGED
@@ -7,7 +7,9 @@
7
7
  * Dependencies:
8
8
  * npm install viem
9
9
  */
10
- import { zeroAddress, } from 'viem';
10
+ import { zeroAddress, createPublicClient, http, encodeFunctionData, parseEventLogs, } from 'viem';
11
+ import { getRegistryAddress, getAgentRegistryString, RPC_ENDPOINTS } from './addresses.js';
12
+ import { getAddress, signTransaction } from './keystore.js';
11
13
  // ─── ABI Fragments ──────────────────────────────────────────────────
12
14
  const IDENTITY_REGISTRY_ABI = [
13
15
  {
@@ -31,6 +33,22 @@ const IDENTITY_REGISTRY_ABI = [
31
33
  inputs: [{ name: 'agentId', type: 'uint256' }],
32
34
  outputs: [{ name: '', type: 'address' }],
33
35
  },
36
+ {
37
+ name: 'register',
38
+ type: 'function',
39
+ stateMutability: 'nonpayable',
40
+ inputs: [{ name: 'agentURI', type: 'string' }],
41
+ outputs: [{ name: 'agentId', type: 'uint256' }],
42
+ },
43
+ {
44
+ name: 'Registered',
45
+ type: 'event',
46
+ inputs: [
47
+ { name: 'agentId', type: 'uint256', indexed: true },
48
+ { name: 'agentURI', type: 'string', indexed: false },
49
+ { name: 'owner', type: 'address', indexed: true },
50
+ ],
51
+ },
34
52
  ];
35
53
  const REPUTATION_REGISTRY_ABI = [
36
54
  {
@@ -139,3 +157,61 @@ export async function getReputation(agentId, options) {
139
157
  const score = Number(rawValue) / 10 ** decimals;
140
158
  return { count: Number(count), score, rawValue, decimals };
141
159
  }
160
+ /**
161
+ * Register an agent on the ERC-8004 Identity Registry in a single call.
162
+ *
163
+ * Builds, signs (via keyring proxy), and broadcasts the `register(agentURI)`
164
+ * transaction, then waits for confirmation and parses the `Registered` event.
165
+ */
166
+ export async function registerAgent(options) {
167
+ const { agentURI, chainId, keystoreConfig } = options;
168
+ const registryAddress = getRegistryAddress(chainId);
169
+ const rpcUrl = options.rpcUrl || RPC_ENDPOINTS[chainId];
170
+ if (!rpcUrl) {
171
+ throw new Error(`No RPC URL provided and no default endpoint for chain ${chainId}.`);
172
+ }
173
+ const publicClient = createPublicClient({ transport: http(rpcUrl) });
174
+ const address = await getAddress(keystoreConfig);
175
+ if (!address) {
176
+ throw new Error('Could not resolve wallet address from keyring proxy.');
177
+ }
178
+ const data = encodeFunctionData({
179
+ abi: IDENTITY_REGISTRY_ABI,
180
+ functionName: 'register',
181
+ args: [agentURI],
182
+ });
183
+ const nonce = await publicClient.getTransactionCount({ address: address });
184
+ const feeData = await publicClient.estimateFeesPerGas();
185
+ const gasEstimate = await publicClient.estimateGas({
186
+ to: registryAddress,
187
+ data,
188
+ account: address,
189
+ });
190
+ const gas = (gasEstimate * 120n) / 100n;
191
+ const txReq = {
192
+ to: registryAddress,
193
+ data,
194
+ nonce,
195
+ chainId,
196
+ type: 2,
197
+ maxFeePerGas: feeData.maxFeePerGas,
198
+ maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
199
+ gas,
200
+ };
201
+ const { signedTx } = await signTransaction(txReq, keystoreConfig);
202
+ const txHash = await publicClient.sendRawTransaction({
203
+ serializedTransaction: signedTx,
204
+ });
205
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
206
+ const logs = parseEventLogs({
207
+ abi: IDENTITY_REGISTRY_ABI,
208
+ logs: receipt.logs,
209
+ eventName: 'Registered',
210
+ });
211
+ if (logs.length === 0) {
212
+ throw new Error(`Registration tx ${txHash} succeeded but no Registered event was found.`);
213
+ }
214
+ const agentId = logs[0].args.agentId.toString();
215
+ const agentRegistry = getAgentRegistryString(chainId);
216
+ return { agentId, txHash, registryAddress, agentRegistry };
217
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildersgarden/siwa",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -38,6 +38,14 @@
38
38
  "./erc8128": {
39
39
  "types": "./dist/erc8128.d.ts",
40
40
  "default": "./dist/erc8128.js"
41
+ },
42
+ "./next": {
43
+ "types": "./dist/next.d.ts",
44
+ "default": "./dist/next.js"
45
+ },
46
+ "./express": {
47
+ "types": "./dist/express.d.ts",
48
+ "default": "./dist/express.js"
41
49
  }
42
50
  },
43
51
  "main": "./dist/index.js",
@@ -61,7 +69,16 @@
61
69
  "@slicekit/erc8128": "^0.1.0",
62
70
  "viem": "^2.21.0"
63
71
  },
72
+ "peerDependencies": {
73
+ "express": "^4.0.0 || ^5.0.0"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "express": {
77
+ "optional": true
78
+ }
79
+ },
64
80
  "devDependencies": {
81
+ "@types/express": "^4.17.0",
65
82
  "@types/node": "^25.2.1",
66
83
  "typescript": "^5.5.0"
67
84
  }