@wtflabs/x402-server 0.0.1-beta.10

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/README.md ADDED
@@ -0,0 +1,630 @@
1
+ # @wtflabs/x402-server
2
+
3
+ Server SDK for the x402 Payment Protocol. Handles payment verification and settlement with automatic token detection.
4
+
5
+ ## Features
6
+
7
+ ✅ **Simple API** - Just 2 required parameters to get started
8
+ ✅ **Automatic Token Detection** - Built on `@wtflabs/x402-detector`
9
+ ✅ **Payment Processing** - Verify and settle payments via `@wtflabs/x402-facilitator`
10
+ ✅ **Dynamic Requirements** - Create payment requirements on-the-fly
11
+ ✅ **Performance Optimized** - Built-in caching, non-blocking initialization
12
+ ✅ **Framework Middlewares** - Ready-to-use Express and Hono middlewares
13
+ ✅ **Zod Validation** - Runtime type safety for all data
14
+ ✅ **Decoupled Design** - Facilitator and Server are independent
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @wtflabs/x402-server viem
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### Option 1: Using Middleware (Easiest)
25
+
26
+ ```typescript
27
+ import express from "express";
28
+ import { createExpressMiddleware, X402Server } from "@wtflabs/x402-server";
29
+ import { Facilitator } from "@wtflabs/x402-facilitator";
30
+
31
+ const app = express();
32
+
33
+ // 1. Create facilitator
34
+ const facilitator = new Facilitator({
35
+ recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
36
+ });
37
+
38
+ // 2. Create server
39
+ const server = new X402Server({
40
+ client: createPublicClient({ chain: bscTestnet, transport: http() }),
41
+ facilitator,
42
+ });
43
+
44
+ // 3. Create middleware
45
+ const paymentMiddleware = createExpressMiddleware({
46
+ server,
47
+ getToken: () => "0x25d066c4C68C8A6332DfDB4230263608305Ca991", // USDC
48
+ getAmount: () => "1000",
49
+ });
50
+
51
+ // 4. Use middleware
52
+ app.post("/api/resource", paymentMiddleware, (req, res) => {
53
+ const { payer, txHash } = req.x402!;
54
+ res.json({ data: "resource", payer, txHash });
55
+ });
56
+ ```
57
+
58
+ ### Option 2: Manual Processing
59
+
60
+ ```typescript
61
+ import { X402Server } from "@wtflabs/x402-server";
62
+ import { Facilitator } from "@wtflabs/x402-facilitator";
63
+ import { createPublicClient, http } from "viem";
64
+ import { bscTestnet } from "viem/chains";
65
+
66
+ // 1. Create facilitator
67
+ const facilitator = new Facilitator({
68
+ recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
69
+ waitUntil: "confirmed",
70
+ });
71
+
72
+ // 2. Create server instance
73
+ const server = new X402Server({
74
+ client: createPublicClient({
75
+ chain: bscTestnet,
76
+ transport: http(),
77
+ }),
78
+ facilitator,
79
+ });
80
+
81
+ // 3. Optional: Pre-warm cache (non-blocking)
82
+ server.initialize([
83
+ "0x25d066c4C68C8A6332DfDB4230263608305Ca991", // USDC
84
+ ]);
85
+
86
+ // 4. Handle payment in your route
87
+ app.post("/api/resource", async (req, res) => {
88
+ // Create payment requirements
89
+ const requirements = await server.createRequirements({
90
+ asset: "0x25d066c4C68C8A6332DfDB4230263608305Ca991",
91
+ maxAmountRequired: "1000", // wei
92
+ });
93
+
94
+ // Process payment (parse → verify → settle)
95
+ const result = await server.process(
96
+ req.headers["x-payment"],
97
+ requirements
98
+ );
99
+
100
+ if (!result.success) {
101
+ return res.status(402).json(result.response);
102
+ }
103
+
104
+ // Payment successful!
105
+ res.json({
106
+ message: "Access granted",
107
+ payer: result.data.payer,
108
+ txHash: result.data.txHash,
109
+ data: "your protected resource",
110
+ });
111
+ });
112
+ ```
113
+
114
+ ## API Reference
115
+
116
+ ### Constructor
117
+
118
+ ```typescript
119
+ const server = new X402Server(config: X402ServerConfig)
120
+ ```
121
+
122
+ **Required:**
123
+ - `client: PublicClient` - Viem PublicClient instance
124
+ - `facilitator: Facilitator` - Facilitator instance (handles payment processing)
125
+
126
+ **Optional:**
127
+ - `network?: string` - Network name (auto-detected from client if not provided)
128
+
129
+ **Example:**
130
+ ```typescript
131
+ import { Facilitator } from "@wtflabs/x402-facilitator";
132
+ import { createPublicClient, http } from "viem";
133
+ import { bscTestnet } from "viem/chains";
134
+
135
+ // 1. Create viem client
136
+ const client = createPublicClient({
137
+ chain: bscTestnet,
138
+ transport: http(),
139
+ });
140
+
141
+ // 2. Create facilitator first
142
+ const facilitator = new Facilitator({
143
+ recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
144
+ waitUntil: "confirmed", // optional: "confirmed" | "finalized"
145
+ });
146
+
147
+ // 3. Create server with client and facilitator
148
+ const server = new X402Server({
149
+ client,
150
+ facilitator,
151
+ network: "bsc-testnet", // optional
152
+ });
153
+ ```
154
+
155
+ ### Methods
156
+
157
+ #### `initialize(tokens: string[]): Promise<InitResult>`
158
+
159
+ Pre-warm the token detection cache. Non-blocking, can run in background.
160
+
161
+ ```typescript
162
+ // Wait for initialization
163
+ await server.initialize([tokenAddress]);
164
+
165
+ // Or run in background
166
+ server.initialize([tokenAddress]).then(result => {
167
+ if (result.success) console.log("✅ Cache ready");
168
+ });
169
+ ```
170
+
171
+ #### `createRequirements(config): Promise<PaymentRequirements>`
172
+
173
+ Create payment requirements. Supports dynamic amounts and auto-detection.
174
+
175
+ ```typescript
176
+ const requirements = await server.createRequirements({
177
+ // Required
178
+ asset: "0x...", // Token contract address
179
+ maxAmountRequired: "1000", // Amount in wei (as string)
180
+
181
+ // Optional - network and scheme
182
+ network?: "bsc-testnet", // Network name (overrides server config)
183
+ scheme?: "exact", // Payment scheme (currently only "exact")
184
+ outputSchema?: {}, // Output schema (for validation)
185
+
186
+ // Optional - payment type
187
+ paymentType?: "permit" | "eip3009" | "permit2" | "auto",
188
+
189
+ // Optional - resource description
190
+ resource?: "https://api.example.com/data",
191
+ description?: "Premium API access",
192
+ mimeType?: "application/json",
193
+ maxTimeoutSeconds?: 300,
194
+
195
+ // Optional - extra metadata
196
+ extra?: {}, // Additional metadata
197
+
198
+ // Optional - performance
199
+ autoDetect?: true, // Set to false for fast mode (requires manual paymentType)
200
+ });
201
+ ```
202
+
203
+ #### `process(paymentHeader, requirements): Promise<ProcessResult>`
204
+
205
+ Complete payment processing (parse → verify → settle).
206
+
207
+ ```typescript
208
+ const result = await server.process(
209
+ request.headers["x-payment"],
210
+ requirements
211
+ );
212
+
213
+ if (result.success) {
214
+ console.log("Payer:", result.data.payer);
215
+ console.log("TxHash:", result.data.txHash);
216
+ } else {
217
+ console.log("Error:", result.response.error);
218
+ // Return 402 with result.response
219
+ }
220
+ ```
221
+
222
+ #### Advanced: Step-by-Step Processing
223
+
224
+ ```typescript
225
+ // 1. Parse payment header
226
+ const parsed = server.parse(paymentHeader, requirements);
227
+ if (!parsed.success) {
228
+ return res.status(402).json(parsed.response402);
229
+ }
230
+
231
+ // 2. Verify payment
232
+ const verified = await server.verify(parsed.data);
233
+ if (!verified.success) {
234
+ return res.status(402).json(
235
+ server.get402Response(requirements, verified.error)
236
+ );
237
+ }
238
+ console.log("Payer:", verified.payer);
239
+
240
+ // 3. Settle payment (optional - you can skip this for verify-only mode)
241
+ const settled = await server.settle(parsed.data);
242
+ if (!settled.success) {
243
+ return res.status(402).json(
244
+ server.get402Response(requirements, settled.error)
245
+ );
246
+ }
247
+ console.log("TxHash:", settled.txHash);
248
+ ```
249
+
250
+ ### Utility Methods
251
+
252
+ ```typescript
253
+ // Generate 402 response
254
+ const response402 = server.get402Response(requirements, error?);
255
+
256
+ // Clear token cache
257
+ await server.clearCache(tokenAddress?); // specific or all
258
+
259
+ // Get cache stats
260
+ const stats = server.getCacheStats();
261
+ console.log(stats.size, stats.keys);
262
+
263
+ // Get underlying instances (for advanced usage)
264
+ const facilitator = server.getFacilitator();
265
+ const detector = server.getDetector();
266
+ const client = server.getClient();
267
+ ```
268
+
269
+ ## Usage Examples
270
+
271
+ ### Example 1: Fixed Amount
272
+
273
+ ```typescript
274
+ import { X402Server } from "@wtflabs/x402-server";
275
+ import { Facilitator } from "@wtflabs/x402-facilitator";
276
+
277
+ // 1. Create facilitator
278
+ const facilitator = new Facilitator({
279
+ recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
280
+ });
281
+
282
+ // 2. Create server
283
+ const server = new X402Server({ client, facilitator });
284
+
285
+ // 3. Pre-warm cache (optional)
286
+ await server.initialize(["0xUSDC"]);
287
+
288
+ // 4. Fixed requirements
289
+ const requirements = await server.createRequirements({
290
+ asset: "0xUSDC",
291
+ maxAmountRequired: "1000",
292
+ description: "Access to premium API",
293
+ });
294
+
295
+ app.post("/premium-api", async (req, res) => {
296
+ const result = await server.process(req.headers["x-payment"], requirements);
297
+
298
+ if (!result.success) {
299
+ return res.status(402).json(result.response);
300
+ }
301
+
302
+ res.json({ data: "premium content" });
303
+ });
304
+ ```
305
+
306
+ ### Example 2: Dynamic Pricing
307
+
308
+ ```typescript
309
+ app.post("/api/compute", async (req, res) => {
310
+ const { complexity } = req.body;
311
+
312
+ // Calculate price based on complexity
313
+ const price = calculatePrice(complexity);
314
+
315
+ // Dynamic requirements
316
+ const requirements = await server.createRequirements({
317
+ asset: "0xUSDC",
318
+ maxAmountRequired: price,
319
+ description: `Compute task (${complexity})`,
320
+ });
321
+
322
+ const result = await server.process(req.headers["x-payment"], requirements);
323
+
324
+ if (!result.success) {
325
+ return res.status(402).json(result.response);
326
+ }
327
+
328
+ // Execute computation
329
+ const computeResult = await performComputation(complexity);
330
+ res.json({ result: computeResult, paid: price });
331
+ });
332
+ ```
333
+
334
+ ### Example 3: Multiple Tokens
335
+
336
+ ```typescript
337
+ import { X402Server } from "@wtflabs/x402-server";
338
+ import { Facilitator } from "@wtflabs/x402-facilitator";
339
+
340
+ // 1. Create facilitator
341
+ const facilitator = new Facilitator({
342
+ recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
343
+ });
344
+
345
+ // 2. Create server
346
+ const server = new X402Server({ client, facilitator });
347
+
348
+ // 3. Pre-warm multiple tokens
349
+ await server.initialize(["0xUSDC", "0xDAI", "0xUSDT"]);
350
+
351
+ app.get("/premium-api", async (req, res) => {
352
+ // Return multiple payment options
353
+ const accepts = await Promise.all([
354
+ server.createRequirements({ asset: "0xUSDC", maxAmountRequired: "1000" }),
355
+ server.createRequirements({ asset: "0xDAI", maxAmountRequired: "1000" }),
356
+ server.createRequirements({ asset: "0xUSDT", maxAmountRequired: "1000" }),
357
+ ]);
358
+
359
+ res.status(402).json({
360
+ x402Version: 1,
361
+ accepts,
362
+ });
363
+ });
364
+
365
+ app.post("/premium-api", async (req, res) => {
366
+ // User pays with their chosen token
367
+ const parsed = server.parse(req.headers["x-payment"], accepts[0]);
368
+ if (!parsed.success) {
369
+ return res.status(402).json(parsed.response402);
370
+ }
371
+
372
+ // Detect which token was used
373
+ const tokenUsed = parsed.data.payload.payload.authorization.token;
374
+
375
+ // Create matching requirements
376
+ const requirements = await server.createRequirements({
377
+ asset: tokenUsed,
378
+ maxAmountRequired: "1000",
379
+ });
380
+
381
+ const result = await server.process(req.headers["x-payment"], requirements);
382
+ // ...
383
+ });
384
+ ```
385
+
386
+ ### Example 4: Fast Mode (Skip Detection)
387
+
388
+ ```typescript
389
+ // For maximum performance, skip auto-detection
390
+ const requirements = await server.createRequirements({
391
+ asset: "0xUSDC",
392
+ maxAmountRequired: "1000",
393
+ paymentType: "permit", // Manually specify
394
+ autoDetect: false, // Skip detection (<1ms)
395
+ });
396
+ ```
397
+
398
+ ## Framework Integration
399
+
400
+ ### Express
401
+
402
+ ```typescript
403
+ import express from "express";
404
+ import { X402Server } from "@wtflabs/x402-server";
405
+ import { Facilitator } from "@wtflabs/x402-facilitator";
406
+ import { createPublicClient, http } from "viem";
407
+ import { bscTestnet } from "viem/chains";
408
+
409
+ const app = express();
410
+
411
+ // Create client
412
+ const client = createPublicClient({
413
+ chain: bscTestnet,
414
+ transport: http(),
415
+ });
416
+
417
+ // Create facilitator
418
+ const facilitator = new Facilitator({
419
+ recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
420
+ });
421
+
422
+ // Create server
423
+ const server = new X402Server({ client, facilitator });
424
+
425
+ app.post("/api/resource", async (req, res) => {
426
+ const requirements = await server.createRequirements({
427
+ asset: "0xUSDC",
428
+ maxAmountRequired: "1000",
429
+ });
430
+
431
+ const result = await server.process(req.headers["x-payment"], requirements);
432
+
433
+ if (!result.success) {
434
+ return res.status(402).json(result.response);
435
+ }
436
+
437
+ res.json({ data: "resource" });
438
+ });
439
+ ```
440
+
441
+ ### Hono
442
+
443
+ ```typescript
444
+ import { Hono } from "hono";
445
+ import { X402Server } from "@wtflabs/x402-server";
446
+ import { Facilitator } from "@wtflabs/x402-facilitator";
447
+ import { createPublicClient, http } from "viem";
448
+ import { bscTestnet } from "viem/chains";
449
+
450
+ const app = new Hono();
451
+
452
+ // Create client
453
+ const client = createPublicClient({
454
+ chain: bscTestnet,
455
+ transport: http(),
456
+ });
457
+
458
+ // Create facilitator
459
+ const facilitator = new Facilitator({
460
+ recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
461
+ });
462
+
463
+ // Create server
464
+ const server = new X402Server({ client, facilitator });
465
+
466
+ app.post("/api/resource", async (c) => {
467
+ const requirements = await server.createRequirements({
468
+ asset: "0xUSDC",
469
+ maxAmountRequired: "1000",
470
+ });
471
+
472
+ const result = await server.process(c.req.header("x-payment"), requirements);
473
+
474
+ if (!result.success) {
475
+ return c.json(result.response, 402);
476
+ }
477
+
478
+ return c.json({ data: "resource" });
479
+ });
480
+ ```
481
+
482
+ ### Next.js API Route
483
+
484
+ ```typescript
485
+ import { X402Server } from "@wtflabs/x402-server";
486
+ import { Facilitator } from "@wtflabs/x402-facilitator";
487
+ import { NextRequest } from "next/server";
488
+ import { createPublicClient, http } from "viem";
489
+ import { bscTestnet } from "viem/chains";
490
+
491
+ // Create client
492
+ const client = createPublicClient({
493
+ chain: bscTestnet,
494
+ transport: http(),
495
+ });
496
+
497
+ // Create facilitator
498
+ const facilitator = new Facilitator({
499
+ recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
500
+ });
501
+
502
+ // Create server
503
+ const server = new X402Server({ client, facilitator });
504
+
505
+ export async function POST(req: NextRequest) {
506
+ const requirements = await server.createRequirements({
507
+ asset: "0xUSDC",
508
+ maxAmountRequired: "1000",
509
+ });
510
+
511
+ const result = await server.process(
512
+ req.headers.get("x-payment") || undefined,
513
+ requirements
514
+ );
515
+
516
+ if (!result.success) {
517
+ return Response.json(result.response, { status: 402 });
518
+ }
519
+
520
+ return Response.json({ data: "resource" });
521
+ }
522
+ ```
523
+
524
+ ## Performance
525
+
526
+ | Operation | First Call | Cached Call |
527
+ |-----------|-----------|-------------|
528
+ | `createRequirements(autoDetect: true)` | 2-5s | <1ms |
529
+ | `createRequirements(autoDetect: false)` | <1ms | <1ms |
530
+ | `process()` | 2-5s + network | <1ms + network |
531
+
532
+ **Tips:**
533
+ - Use `initialize()` on startup to pre-warm cache
534
+ - Set `autoDetect: false` for maximum speed (requires manual `paymentType`)
535
+ - Cache persists for the lifetime of the server instance
536
+
537
+ ## Error Handling
538
+
539
+ ```typescript
540
+ const result = await server.process(paymentHeader, requirements);
541
+
542
+ if (!result.success) {
543
+ // 402 response with error details
544
+ console.log(result.response.error);
545
+ return res.status(402).json(result.response);
546
+ }
547
+
548
+ // Success!
549
+ console.log(result.data.payer);
550
+ console.log(result.data.txHash);
551
+ ```
552
+
553
+ ## TypeScript Support
554
+
555
+ Full TypeScript support with comprehensive type definitions:
556
+
557
+ ```typescript
558
+ import type {
559
+ X402ServerConfig,
560
+ CreateRequirementsConfig,
561
+ PaymentRequirements,
562
+ ProcessResult,
563
+ InitResult,
564
+ } from "@wtflabs/x402-server";
565
+ ```
566
+
567
+ ## Middlewares
568
+
569
+ ### Express Middleware
570
+
571
+ ```typescript
572
+ import { createExpressMiddleware } from "@wtflabs/x402-server";
573
+
574
+ const middleware = createExpressMiddleware({
575
+ server,
576
+ getToken: (req) => req.body.token,
577
+ getAmount: (req) => calculatePrice(req.body),
578
+ onPaymentSuccess: async (req, payer, txHash) => {
579
+ console.log(`Payment from ${payer}: ${txHash}`);
580
+ },
581
+ });
582
+
583
+ app.post("/api", middleware, (req, res) => {
584
+ const { payer, txHash } = req.x402!;
585
+ res.json({ data: "resource", payer, txHash });
586
+ });
587
+ ```
588
+
589
+ ### Hono Middleware
590
+
591
+ ```typescript
592
+ import { createHonoMiddleware } from "@wtflabs/x402-server";
593
+
594
+ const middleware = createHonoMiddleware({
595
+ server,
596
+ getToken: async (c) => (await c.req.json()).token,
597
+ getAmount: async (c) => calculatePrice(await c.req.json()),
598
+ onPaymentSuccess: async (c, payer, txHash) => {
599
+ console.log(`Payment from ${payer}: ${txHash}`);
600
+ },
601
+ });
602
+
603
+ app.post("/api", middleware, (c) => {
604
+ const x402 = c.get("x402")!;
605
+ return c.json({ data: "resource", payer: x402.payer });
606
+ });
607
+ ```
608
+
609
+ **See [MIDDLEWARES.md](./MIDDLEWARES.md) for detailed guide.**
610
+
611
+ ## Documentation
612
+
613
+ - **[README.md](./README.md)** - This file
614
+ - **[QUICK-START.md](./QUICK-START.md)** - 5-minute quick start guide
615
+ - **[USAGE.md](./USAGE.md)** - Detailed usage documentation
616
+ - **[MIDDLEWARES.md](./MIDDLEWARES.md)** - Express and Hono middleware guide
617
+ - **[ZOD-VALIDATION.md](./ZOD-VALIDATION.md)** - Zod validation explained
618
+ - **[ARCHITECTURE.md](./ARCHITECTURE.md)** - Architecture and design
619
+ - **[examples/](./examples/)** - Complete examples
620
+
621
+ ## License
622
+
623
+ Apache-2.0
624
+
625
+ ## Related Packages
626
+
627
+ - `@wtflabs/x402-detector` - Token detection (used internally)
628
+ - `@wtflabs/x402-facilitator` - Payment processing (used internally)
629
+ - `@wtflabs/x402-fetch` - Client SDK
630
+ - `@wtflabs/x402` - Core protocol types