@wtflabs/x402-server 0.0.1-beta.10 → 0.0.1-beta.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/README.md +678 -209
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.mts +3 -3
- package/dist/esm/index.mjs +5 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/index.d.mts +217 -0
- package/dist/index.d.ts +217 -0
- package/dist/index.js +632 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +602 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,61 +1,140 @@
|
|
|
1
1
|
# @wtflabs/x402-server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
完整的服务端 SDK,用于 x402 支付协议。提供支付验证、结算和自动 Token 检测功能。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## ✨ 特性
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
✅ **Zod
|
|
14
|
-
|
|
7
|
+
- 🚀 **简洁 API** - 仅需 2 个必填参数即可开始
|
|
8
|
+
- 🔍 **自动 Token 检测** - 基于 `@wtflabs/x402-detector` 自动识别支付类型
|
|
9
|
+
- 💰 **支付处理** - 通过 `@wtflabs/x402-facilitator` 验证和结算支付
|
|
10
|
+
- ⚡ **动态需求** - 实时创建支付要求
|
|
11
|
+
- 🎯 **性能优化** - 内置缓存、非阻塞初始化
|
|
12
|
+
- 🔌 **框架中间件** - Express 和 Hono 中间件开箱即用
|
|
13
|
+
- ✅ **Zod 验证** - 运行时类型安全
|
|
14
|
+
- 🔒 **100% 类型安全** - 零 `any` 类型,完整 TypeScript 支持
|
|
15
|
+
- 🎨 **解耦设计** - Facilitator 和 Server 独立运行
|
|
15
16
|
|
|
16
|
-
##
|
|
17
|
+
## 📦 安装
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
|
-
npm install @wtflabs/x402-server viem
|
|
20
|
+
npm install @wtflabs/x402-server @wtflabs/x402-facilitator viem
|
|
20
21
|
```
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
**可选依赖**(根据框架选择):
|
|
24
|
+
```bash
|
|
25
|
+
# 使用 Express
|
|
26
|
+
npm install express
|
|
27
|
+
|
|
28
|
+
# 使用 Hono
|
|
29
|
+
npm install hono
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 🚀 快速开始
|
|
33
|
+
|
|
34
|
+
### 方式 1:使用中间件(推荐)
|
|
23
35
|
|
|
24
|
-
|
|
36
|
+
#### Express 中间件
|
|
25
37
|
|
|
26
38
|
```typescript
|
|
27
39
|
import express from "express";
|
|
28
40
|
import { createExpressMiddleware, X402Server } from "@wtflabs/x402-server";
|
|
29
41
|
import { Facilitator } from "@wtflabs/x402-facilitator";
|
|
42
|
+
import { createPublicClient, http } from "viem";
|
|
43
|
+
import { bscTestnet } from "viem/chains";
|
|
30
44
|
|
|
31
45
|
const app = express();
|
|
32
46
|
|
|
33
|
-
// 1.
|
|
47
|
+
// 1. 创建 viem client
|
|
48
|
+
const client = createPublicClient({
|
|
49
|
+
chain: bscTestnet,
|
|
50
|
+
transport: http(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 2. 创建 facilitator
|
|
34
54
|
const facilitator = new Facilitator({
|
|
35
55
|
recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
|
|
56
|
+
waitUntil: "confirmed",
|
|
36
57
|
});
|
|
37
58
|
|
|
38
|
-
//
|
|
59
|
+
// 3. 创建 server
|
|
39
60
|
const server = new X402Server({
|
|
40
|
-
client
|
|
61
|
+
client,
|
|
41
62
|
facilitator,
|
|
42
63
|
});
|
|
43
64
|
|
|
44
|
-
//
|
|
65
|
+
// 4. 创建中间件
|
|
45
66
|
const paymentMiddleware = createExpressMiddleware({
|
|
46
67
|
server,
|
|
47
68
|
getToken: () => "0x25d066c4C68C8A6332DfDB4230263608305Ca991", // USDC
|
|
48
|
-
getAmount: () => "
|
|
69
|
+
getAmount: () => "1000000", // 1 USDC (6 decimals)
|
|
49
70
|
});
|
|
50
71
|
|
|
51
|
-
//
|
|
52
|
-
app.post("/api/
|
|
72
|
+
// 5. 使用中间件
|
|
73
|
+
app.post("/api/premium", paymentMiddleware, (req, res) => {
|
|
53
74
|
const { payer, txHash } = req.x402!;
|
|
54
|
-
res.json({
|
|
75
|
+
res.json({
|
|
76
|
+
success: true,
|
|
77
|
+
data: "Premium content",
|
|
78
|
+
payer,
|
|
79
|
+
txHash,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
app.listen(3000, () => {
|
|
84
|
+
console.log("Server running on http://localhost:3000");
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### Hono 中间件
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { Hono } from "hono";
|
|
92
|
+
import { createHonoMiddleware, X402Server } from "@wtflabs/x402-server";
|
|
93
|
+
import { Facilitator } from "@wtflabs/x402-facilitator";
|
|
94
|
+
import { createPublicClient, http } from "viem";
|
|
95
|
+
import { bscTestnet } from "viem/chains";
|
|
96
|
+
|
|
97
|
+
const app = new Hono();
|
|
98
|
+
|
|
99
|
+
// 1. 创建 viem client
|
|
100
|
+
const client = createPublicClient({
|
|
101
|
+
chain: bscTestnet,
|
|
102
|
+
transport: http(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// 2. 创建 facilitator
|
|
106
|
+
const facilitator = new Facilitator({
|
|
107
|
+
recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// 3. 创建 server
|
|
111
|
+
const server = new X402Server({
|
|
112
|
+
client,
|
|
113
|
+
facilitator,
|
|
55
114
|
});
|
|
115
|
+
|
|
116
|
+
// 4. 创建中间件
|
|
117
|
+
const paymentMiddleware = createHonoMiddleware({
|
|
118
|
+
server,
|
|
119
|
+
getToken: () => "0x25d066c4C68C8A6332DfDB4230263608305Ca991",
|
|
120
|
+
getAmount: () => "1000000",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// 5. 使用中间件
|
|
124
|
+
app.post("/api/premium", paymentMiddleware, (c) => {
|
|
125
|
+
const x402 = c.get("x402") as { payer: string; txHash: string };
|
|
126
|
+
return c.json({
|
|
127
|
+
success: true,
|
|
128
|
+
data: "Premium content",
|
|
129
|
+
payer: x402.payer,
|
|
130
|
+
txHash: x402.txHash,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export default app;
|
|
56
135
|
```
|
|
57
136
|
|
|
58
|
-
###
|
|
137
|
+
### 方式 2:手动处理
|
|
59
138
|
|
|
60
139
|
```typescript
|
|
61
140
|
import { X402Server } from "@wtflabs/x402-server";
|
|
@@ -63,37 +142,42 @@ import { Facilitator } from "@wtflabs/x402-facilitator";
|
|
|
63
142
|
import { createPublicClient, http } from "viem";
|
|
64
143
|
import { bscTestnet } from "viem/chains";
|
|
65
144
|
|
|
66
|
-
// 1.
|
|
145
|
+
// 1. 创建 viem client
|
|
146
|
+
const client = createPublicClient({
|
|
147
|
+
chain: bscTestnet,
|
|
148
|
+
transport: http(),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// 2. 创建 facilitator
|
|
67
152
|
const facilitator = new Facilitator({
|
|
68
153
|
recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
|
|
69
154
|
waitUntil: "confirmed",
|
|
70
155
|
});
|
|
71
156
|
|
|
72
|
-
//
|
|
157
|
+
// 3. 创建 server
|
|
73
158
|
const server = new X402Server({
|
|
74
|
-
client
|
|
75
|
-
chain: bscTestnet,
|
|
76
|
-
transport: http(),
|
|
77
|
-
}),
|
|
159
|
+
client,
|
|
78
160
|
facilitator,
|
|
161
|
+
network: "bsc-testnet", // 可选
|
|
79
162
|
});
|
|
80
163
|
|
|
81
|
-
//
|
|
164
|
+
// 4. 可选:预热缓存(非阻塞)
|
|
82
165
|
server.initialize([
|
|
83
166
|
"0x25d066c4C68C8A6332DfDB4230263608305Ca991", // USDC
|
|
84
167
|
]);
|
|
85
168
|
|
|
86
|
-
//
|
|
169
|
+
// 5. 在路由中处理支付
|
|
87
170
|
app.post("/api/resource", async (req, res) => {
|
|
88
|
-
//
|
|
171
|
+
// 创建支付要求
|
|
89
172
|
const requirements = await server.createRequirements({
|
|
90
173
|
asset: "0x25d066c4C68C8A6332DfDB4230263608305Ca991",
|
|
91
|
-
maxAmountRequired: "
|
|
174
|
+
maxAmountRequired: "1000000",
|
|
175
|
+
description: "Premium API access",
|
|
92
176
|
});
|
|
93
177
|
|
|
94
|
-
//
|
|
178
|
+
// 处理支付(parse → verify → settle)
|
|
95
179
|
const result = await server.process(
|
|
96
|
-
req.headers["x-payment"],
|
|
180
|
+
req.headers["x-payment"] as string,
|
|
97
181
|
requirements
|
|
98
182
|
);
|
|
99
183
|
|
|
@@ -101,108 +185,131 @@ app.post("/api/resource", async (req, res) => {
|
|
|
101
185
|
return res.status(402).json(result.response);
|
|
102
186
|
}
|
|
103
187
|
|
|
104
|
-
//
|
|
188
|
+
// 支付成功
|
|
105
189
|
res.json({
|
|
106
190
|
message: "Access granted",
|
|
107
191
|
payer: result.data.payer,
|
|
108
192
|
txHash: result.data.txHash,
|
|
109
|
-
data: "
|
|
193
|
+
data: "Your protected resource",
|
|
110
194
|
});
|
|
111
195
|
});
|
|
112
196
|
```
|
|
113
197
|
|
|
114
|
-
## API
|
|
198
|
+
## 📚 API 参考
|
|
115
199
|
|
|
116
|
-
###
|
|
200
|
+
### X402Server 构造函数
|
|
117
201
|
|
|
118
202
|
```typescript
|
|
119
203
|
const server = new X402Server(config: X402ServerConfig)
|
|
120
204
|
```
|
|
121
205
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
206
|
+
#### 参数
|
|
207
|
+
|
|
208
|
+
**必填:**
|
|
209
|
+
- `client: PublicClient` - Viem PublicClient 实例
|
|
210
|
+
- `facilitator: Facilitator` - Facilitator 实例(处理支付)
|
|
211
|
+
|
|
212
|
+
**可选:**
|
|
213
|
+
- `network?: string` - 网络名称(默认从 client 自动检测)
|
|
125
214
|
|
|
126
|
-
|
|
127
|
-
- `network?: string` - Network name (auto-detected from client if not provided)
|
|
215
|
+
#### 示例
|
|
128
216
|
|
|
129
|
-
**Example:**
|
|
130
217
|
```typescript
|
|
131
218
|
import { Facilitator } from "@wtflabs/x402-facilitator";
|
|
132
219
|
import { createPublicClient, http } from "viem";
|
|
133
220
|
import { bscTestnet } from "viem/chains";
|
|
134
221
|
|
|
135
|
-
// 1. Create viem client
|
|
136
222
|
const client = createPublicClient({
|
|
137
223
|
chain: bscTestnet,
|
|
138
224
|
transport: http(),
|
|
139
225
|
});
|
|
140
226
|
|
|
141
|
-
// 2. Create facilitator first
|
|
142
227
|
const facilitator = new Facilitator({
|
|
143
228
|
recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
|
|
144
|
-
waitUntil: "confirmed",
|
|
229
|
+
waitUntil: "confirmed",
|
|
145
230
|
});
|
|
146
231
|
|
|
147
|
-
// 3. Create server with client and facilitator
|
|
148
232
|
const server = new X402Server({
|
|
149
233
|
client,
|
|
150
234
|
facilitator,
|
|
151
|
-
network: "bsc-testnet", //
|
|
235
|
+
network: "bsc-testnet", // 可选
|
|
152
236
|
});
|
|
153
237
|
```
|
|
154
238
|
|
|
155
|
-
###
|
|
239
|
+
### 核心方法
|
|
156
240
|
|
|
157
241
|
#### `initialize(tokens: string[]): Promise<InitResult>`
|
|
158
242
|
|
|
159
|
-
|
|
243
|
+
预热 Token 检测缓存。非阻塞,可以在后台运行。
|
|
160
244
|
|
|
161
245
|
```typescript
|
|
162
|
-
//
|
|
246
|
+
// 等待初始化完成
|
|
163
247
|
await server.initialize([tokenAddress]);
|
|
164
248
|
|
|
165
|
-
//
|
|
249
|
+
// 或在后台运行
|
|
166
250
|
server.initialize([tokenAddress]).then(result => {
|
|
167
|
-
if (result.success)
|
|
251
|
+
if (result.success) {
|
|
252
|
+
console.log("✅ Cache ready");
|
|
253
|
+
}
|
|
168
254
|
});
|
|
169
255
|
```
|
|
170
256
|
|
|
171
257
|
#### `createRequirements(config): Promise<PaymentRequirements>`
|
|
172
258
|
|
|
173
|
-
|
|
259
|
+
创建支付要求。支持动态金额和自动检测。
|
|
174
260
|
|
|
175
|
-
|
|
176
|
-
const requirements = await server.createRequirements({
|
|
177
|
-
// Required
|
|
178
|
-
asset: "0x...", // Token contract address
|
|
179
|
-
maxAmountRequired: "1000", // Amount in wei (as string)
|
|
261
|
+
**参数:**
|
|
180
262
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
263
|
+
```typescript
|
|
264
|
+
interface CreateRequirementsConfig {
|
|
265
|
+
// 必填
|
|
266
|
+
asset: string; // Token 合约地址
|
|
267
|
+
maxAmountRequired: string; // 金额(wei,字符串格式)
|
|
268
|
+
|
|
269
|
+
// 可选 - 网络和方案
|
|
270
|
+
network?: string; // 网络名称(覆盖全局配置)
|
|
271
|
+
scheme?: "exact"; // 支付方案(目前仅支持 "exact")
|
|
272
|
+
outputSchema?: Record<string, unknown>;
|
|
273
|
+
|
|
274
|
+
// 可选 - 支付类型
|
|
275
|
+
paymentType?: "permit" | "eip3009" | "permit2" | "auto";
|
|
276
|
+
|
|
277
|
+
// 可选 - 资源描述
|
|
278
|
+
resource?: string; // 资源 URL
|
|
279
|
+
description?: string; // 描述
|
|
280
|
+
mimeType?: string; // MIME 类型
|
|
281
|
+
maxTimeoutSeconds?: number; // 超时时间(秒)
|
|
282
|
+
|
|
283
|
+
// 可选 - 额外元数据
|
|
284
|
+
extra?: Record<string, unknown>;
|
|
285
|
+
|
|
286
|
+
// 可选 - 性能控制
|
|
287
|
+
autoDetect?: boolean; // false = 快速模式(需手动指定 paymentType)
|
|
288
|
+
}
|
|
289
|
+
```
|
|
188
290
|
|
|
189
|
-
|
|
190
|
-
resource?: "https://api.example.com/data",
|
|
191
|
-
description?: "Premium API access",
|
|
192
|
-
mimeType?: "application/json",
|
|
193
|
-
maxTimeoutSeconds?: 300,
|
|
291
|
+
**示例:**
|
|
194
292
|
|
|
195
|
-
|
|
196
|
-
|
|
293
|
+
```typescript
|
|
294
|
+
// 自动检测(默认)
|
|
295
|
+
const requirements = await server.createRequirements({
|
|
296
|
+
asset: "0x25d066c4C68C8A6332DfDB4230263608305Ca991",
|
|
297
|
+
maxAmountRequired: "1000000",
|
|
298
|
+
description: "Premium access",
|
|
299
|
+
});
|
|
197
300
|
|
|
198
|
-
|
|
199
|
-
|
|
301
|
+
// 快速模式(跳过检测)
|
|
302
|
+
const requirements = await server.createRequirements({
|
|
303
|
+
asset: "0x25d066c4C68C8A6332DfDB4230263608305Ca991",
|
|
304
|
+
maxAmountRequired: "1000000",
|
|
305
|
+
paymentType: "permit",
|
|
306
|
+
autoDetect: false, // <1ms
|
|
200
307
|
});
|
|
201
308
|
```
|
|
202
309
|
|
|
203
310
|
#### `process(paymentHeader, requirements): Promise<ProcessResult>`
|
|
204
311
|
|
|
205
|
-
|
|
312
|
+
完整的支付处理流程(解析 → 验证 → 结算)。
|
|
206
313
|
|
|
207
314
|
```typescript
|
|
208
315
|
const result = await server.process(
|
|
@@ -215,20 +322,22 @@ if (result.success) {
|
|
|
215
322
|
console.log("TxHash:", result.data.txHash);
|
|
216
323
|
} else {
|
|
217
324
|
console.log("Error:", result.response.error);
|
|
218
|
-
//
|
|
325
|
+
// 返回 402 状态码和 result.response
|
|
219
326
|
}
|
|
220
327
|
```
|
|
221
328
|
|
|
222
|
-
####
|
|
329
|
+
#### 分步处理(高级用法)
|
|
330
|
+
|
|
331
|
+
如需更细粒度的控制,可以分步处理:
|
|
223
332
|
|
|
224
333
|
```typescript
|
|
225
|
-
// 1.
|
|
334
|
+
// 1. 解析支付头
|
|
226
335
|
const parsed = server.parse(paymentHeader, requirements);
|
|
227
336
|
if (!parsed.success) {
|
|
228
337
|
return res.status(402).json(parsed.response402);
|
|
229
338
|
}
|
|
230
339
|
|
|
231
|
-
// 2.
|
|
340
|
+
// 2. 验证支付
|
|
232
341
|
const verified = await server.verify(parsed.data);
|
|
233
342
|
if (!verified.success) {
|
|
234
343
|
return res.status(402).json(
|
|
@@ -237,7 +346,7 @@ if (!verified.success) {
|
|
|
237
346
|
}
|
|
238
347
|
console.log("Payer:", verified.payer);
|
|
239
348
|
|
|
240
|
-
// 3.
|
|
349
|
+
// 3. 结算支付(可选 - 仅验证模式可跳过)
|
|
241
350
|
const settled = await server.settle(parsed.data);
|
|
242
351
|
if (!settled.success) {
|
|
243
352
|
return res.status(402).json(
|
|
@@ -247,53 +356,59 @@ if (!settled.success) {
|
|
|
247
356
|
console.log("TxHash:", settled.txHash);
|
|
248
357
|
```
|
|
249
358
|
|
|
250
|
-
###
|
|
359
|
+
### 工具方法
|
|
251
360
|
|
|
252
361
|
```typescript
|
|
253
|
-
//
|
|
362
|
+
// 生成 402 响应
|
|
254
363
|
const response402 = server.get402Response(requirements, error?);
|
|
255
364
|
|
|
256
|
-
//
|
|
257
|
-
await server.clearCache(tokenAddress?); //
|
|
365
|
+
// 清除 Token 缓存
|
|
366
|
+
await server.clearCache(tokenAddress?); // 指定地址或清除全部
|
|
258
367
|
|
|
259
|
-
//
|
|
368
|
+
// 获取缓存统计
|
|
260
369
|
const stats = server.getCacheStats();
|
|
261
370
|
console.log(stats.size, stats.keys);
|
|
262
371
|
|
|
263
|
-
//
|
|
372
|
+
// 获取底层实例(高级用法)
|
|
264
373
|
const facilitator = server.getFacilitator();
|
|
265
374
|
const detector = server.getDetector();
|
|
266
375
|
const client = server.getClient();
|
|
267
376
|
```
|
|
268
377
|
|
|
269
|
-
##
|
|
378
|
+
## 💡 使用示例
|
|
270
379
|
|
|
271
|
-
###
|
|
380
|
+
### 示例 1: 固定金额
|
|
272
381
|
|
|
273
382
|
```typescript
|
|
383
|
+
import express from "express";
|
|
274
384
|
import { X402Server } from "@wtflabs/x402-server";
|
|
275
385
|
import { Facilitator } from "@wtflabs/x402-facilitator";
|
|
276
386
|
|
|
277
|
-
|
|
387
|
+
const app = express();
|
|
388
|
+
|
|
389
|
+
// 创建 facilitator
|
|
278
390
|
const facilitator = new Facilitator({
|
|
279
391
|
recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
|
|
280
392
|
});
|
|
281
393
|
|
|
282
|
-
//
|
|
394
|
+
// 创建 server
|
|
283
395
|
const server = new X402Server({ client, facilitator });
|
|
284
396
|
|
|
285
|
-
//
|
|
397
|
+
// 预热缓存(可选)
|
|
286
398
|
await server.initialize(["0xUSDC"]);
|
|
287
399
|
|
|
288
|
-
//
|
|
400
|
+
// 固定要求
|
|
289
401
|
const requirements = await server.createRequirements({
|
|
290
402
|
asset: "0xUSDC",
|
|
291
|
-
maxAmountRequired: "
|
|
403
|
+
maxAmountRequired: "1000000",
|
|
292
404
|
description: "Access to premium API",
|
|
293
405
|
});
|
|
294
406
|
|
|
295
407
|
app.post("/premium-api", async (req, res) => {
|
|
296
|
-
const result = await server.process(
|
|
408
|
+
const result = await server.process(
|
|
409
|
+
req.headers["x-payment"] as string,
|
|
410
|
+
requirements
|
|
411
|
+
);
|
|
297
412
|
|
|
298
413
|
if (!result.success) {
|
|
299
414
|
return res.status(402).json(result.response);
|
|
@@ -303,57 +418,60 @@ app.post("/premium-api", async (req, res) => {
|
|
|
303
418
|
});
|
|
304
419
|
```
|
|
305
420
|
|
|
306
|
-
###
|
|
421
|
+
### 示例 2: 动态定价
|
|
307
422
|
|
|
308
423
|
```typescript
|
|
309
424
|
app.post("/api/compute", async (req, res) => {
|
|
310
425
|
const { complexity } = req.body;
|
|
311
426
|
|
|
312
|
-
//
|
|
427
|
+
// 根据复杂度计算价格
|
|
313
428
|
const price = calculatePrice(complexity);
|
|
314
429
|
|
|
315
|
-
//
|
|
430
|
+
// 动态创建要求
|
|
316
431
|
const requirements = await server.createRequirements({
|
|
317
432
|
asset: "0xUSDC",
|
|
318
433
|
maxAmountRequired: price,
|
|
319
|
-
description: `Compute task (${complexity})`,
|
|
434
|
+
description: `Compute task (complexity: ${complexity})`,
|
|
320
435
|
});
|
|
321
436
|
|
|
322
|
-
const result = await server.process(
|
|
437
|
+
const result = await server.process(
|
|
438
|
+
req.headers["x-payment"] as string,
|
|
439
|
+
requirements
|
|
440
|
+
);
|
|
323
441
|
|
|
324
442
|
if (!result.success) {
|
|
325
443
|
return res.status(402).json(result.response);
|
|
326
444
|
}
|
|
327
445
|
|
|
328
|
-
//
|
|
446
|
+
// 执行计算
|
|
329
447
|
const computeResult = await performComputation(complexity);
|
|
330
448
|
res.json({ result: computeResult, paid: price });
|
|
331
449
|
});
|
|
332
450
|
```
|
|
333
451
|
|
|
334
|
-
###
|
|
452
|
+
### 示例 3: 多 Token 支持
|
|
335
453
|
|
|
336
454
|
```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
455
|
const server = new X402Server({ client, facilitator });
|
|
347
456
|
|
|
348
|
-
//
|
|
457
|
+
// 预热多个 Token
|
|
349
458
|
await server.initialize(["0xUSDC", "0xDAI", "0xUSDT"]);
|
|
350
459
|
|
|
351
460
|
app.get("/premium-api", async (req, res) => {
|
|
352
|
-
//
|
|
461
|
+
// 返回多个支付选项
|
|
353
462
|
const accepts = await Promise.all([
|
|
354
|
-
server.createRequirements({
|
|
355
|
-
|
|
356
|
-
|
|
463
|
+
server.createRequirements({
|
|
464
|
+
asset: "0xUSDC",
|
|
465
|
+
maxAmountRequired: "1000000"
|
|
466
|
+
}),
|
|
467
|
+
server.createRequirements({
|
|
468
|
+
asset: "0xDAI",
|
|
469
|
+
maxAmountRequired: "1000000000000000000"
|
|
470
|
+
}),
|
|
471
|
+
server.createRequirements({
|
|
472
|
+
asset: "0xUSDT",
|
|
473
|
+
maxAmountRequired: "1000000"
|
|
474
|
+
}),
|
|
357
475
|
]);
|
|
358
476
|
|
|
359
477
|
res.status(402).json({
|
|
@@ -363,39 +481,51 @@ app.get("/premium-api", async (req, res) => {
|
|
|
363
481
|
});
|
|
364
482
|
|
|
365
483
|
app.post("/premium-api", async (req, res) => {
|
|
366
|
-
//
|
|
367
|
-
const parsed = server.parse(
|
|
484
|
+
// 用户使用选定的 Token 支付
|
|
485
|
+
const parsed = server.parse(
|
|
486
|
+
req.headers["x-payment"] as string,
|
|
487
|
+
accepts[0]
|
|
488
|
+
);
|
|
489
|
+
|
|
368
490
|
if (!parsed.success) {
|
|
369
491
|
return res.status(402).json(parsed.response402);
|
|
370
492
|
}
|
|
371
493
|
|
|
372
|
-
//
|
|
494
|
+
// 检测使用的 Token
|
|
373
495
|
const tokenUsed = parsed.data.payload.payload.authorization.token;
|
|
374
496
|
|
|
375
|
-
//
|
|
497
|
+
// 创建匹配的要求
|
|
376
498
|
const requirements = await server.createRequirements({
|
|
377
499
|
asset: tokenUsed,
|
|
378
|
-
maxAmountRequired: "
|
|
500
|
+
maxAmountRequired: "1000000",
|
|
379
501
|
});
|
|
380
502
|
|
|
381
|
-
const result = await server.process(
|
|
382
|
-
|
|
503
|
+
const result = await server.process(
|
|
504
|
+
req.headers["x-payment"] as string,
|
|
505
|
+
requirements
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
if (!result.success) {
|
|
509
|
+
return res.status(402).json(result.response);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
res.json({ data: "premium content" });
|
|
383
513
|
});
|
|
384
514
|
```
|
|
385
515
|
|
|
386
|
-
###
|
|
516
|
+
### 示例 4: 快速模式(跳过检测)
|
|
387
517
|
|
|
388
518
|
```typescript
|
|
389
|
-
//
|
|
519
|
+
// 为获得最大性能,跳过自动检测
|
|
390
520
|
const requirements = await server.createRequirements({
|
|
391
521
|
asset: "0xUSDC",
|
|
392
|
-
maxAmountRequired: "
|
|
393
|
-
paymentType: "permit",
|
|
394
|
-
autoDetect: false,
|
|
522
|
+
maxAmountRequired: "1000000",
|
|
523
|
+
paymentType: "permit", // 手动指定
|
|
524
|
+
autoDetect: false, // 跳过检测 (<1ms)
|
|
395
525
|
});
|
|
396
526
|
```
|
|
397
527
|
|
|
398
|
-
##
|
|
528
|
+
## 🔌 框架集成
|
|
399
529
|
|
|
400
530
|
### Express
|
|
401
531
|
|
|
@@ -408,27 +538,27 @@ import { bscTestnet } from "viem/chains";
|
|
|
408
538
|
|
|
409
539
|
const app = express();
|
|
410
540
|
|
|
411
|
-
// Create client
|
|
412
541
|
const client = createPublicClient({
|
|
413
542
|
chain: bscTestnet,
|
|
414
543
|
transport: http(),
|
|
415
544
|
});
|
|
416
545
|
|
|
417
|
-
// Create facilitator
|
|
418
546
|
const facilitator = new Facilitator({
|
|
419
547
|
recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
|
|
420
548
|
});
|
|
421
549
|
|
|
422
|
-
// Create server
|
|
423
550
|
const server = new X402Server({ client, facilitator });
|
|
424
551
|
|
|
425
552
|
app.post("/api/resource", async (req, res) => {
|
|
426
553
|
const requirements = await server.createRequirements({
|
|
427
554
|
asset: "0xUSDC",
|
|
428
|
-
maxAmountRequired: "
|
|
555
|
+
maxAmountRequired: "1000000",
|
|
429
556
|
});
|
|
430
557
|
|
|
431
|
-
const result = await server.process(
|
|
558
|
+
const result = await server.process(
|
|
559
|
+
req.headers["x-payment"] as string,
|
|
560
|
+
requirements
|
|
561
|
+
);
|
|
432
562
|
|
|
433
563
|
if (!result.success) {
|
|
434
564
|
return res.status(402).json(result.response);
|
|
@@ -449,27 +579,27 @@ import { bscTestnet } from "viem/chains";
|
|
|
449
579
|
|
|
450
580
|
const app = new Hono();
|
|
451
581
|
|
|
452
|
-
// Create client
|
|
453
582
|
const client = createPublicClient({
|
|
454
583
|
chain: bscTestnet,
|
|
455
584
|
transport: http(),
|
|
456
585
|
});
|
|
457
586
|
|
|
458
|
-
// Create facilitator
|
|
459
587
|
const facilitator = new Facilitator({
|
|
460
588
|
recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
|
|
461
589
|
});
|
|
462
590
|
|
|
463
|
-
// Create server
|
|
464
591
|
const server = new X402Server({ client, facilitator });
|
|
465
592
|
|
|
466
593
|
app.post("/api/resource", async (c) => {
|
|
467
594
|
const requirements = await server.createRequirements({
|
|
468
595
|
asset: "0xUSDC",
|
|
469
|
-
maxAmountRequired: "
|
|
596
|
+
maxAmountRequired: "1000000",
|
|
470
597
|
});
|
|
471
598
|
|
|
472
|
-
const result = await server.process(
|
|
599
|
+
const result = await server.process(
|
|
600
|
+
c.req.header("x-payment"),
|
|
601
|
+
requirements
|
|
602
|
+
);
|
|
473
603
|
|
|
474
604
|
if (!result.success) {
|
|
475
605
|
return c.json(result.response, 402);
|
|
@@ -479,7 +609,7 @@ app.post("/api/resource", async (c) => {
|
|
|
479
609
|
});
|
|
480
610
|
```
|
|
481
611
|
|
|
482
|
-
### Next.js
|
|
612
|
+
### Next.js App Router
|
|
483
613
|
|
|
484
614
|
```typescript
|
|
485
615
|
import { X402Server } from "@wtflabs/x402-server";
|
|
@@ -488,24 +618,21 @@ import { NextRequest } from "next/server";
|
|
|
488
618
|
import { createPublicClient, http } from "viem";
|
|
489
619
|
import { bscTestnet } from "viem/chains";
|
|
490
620
|
|
|
491
|
-
// Create client
|
|
492
621
|
const client = createPublicClient({
|
|
493
622
|
chain: bscTestnet,
|
|
494
623
|
transport: http(),
|
|
495
624
|
});
|
|
496
625
|
|
|
497
|
-
// Create facilitator
|
|
498
626
|
const facilitator = new Facilitator({
|
|
499
627
|
recipientAddress: "0x5D06b8145D908DDb7ca116664Fcf113ddaA4d6F3",
|
|
500
628
|
});
|
|
501
629
|
|
|
502
|
-
// Create server
|
|
503
630
|
const server = new X402Server({ client, facilitator });
|
|
504
631
|
|
|
505
632
|
export async function POST(req: NextRequest) {
|
|
506
633
|
const requirements = await server.createRequirements({
|
|
507
634
|
asset: "0xUSDC",
|
|
508
|
-
maxAmountRequired: "
|
|
635
|
+
maxAmountRequired: "1000000",
|
|
509
636
|
});
|
|
510
637
|
|
|
511
638
|
const result = await server.process(
|
|
@@ -521,110 +648,452 @@ export async function POST(req: NextRequest) {
|
|
|
521
648
|
}
|
|
522
649
|
```
|
|
523
650
|
|
|
524
|
-
##
|
|
651
|
+
## 🎯 中间件详细文档
|
|
525
652
|
|
|
526
|
-
|
|
527
|
-
|-----------|-----------|-------------|
|
|
528
|
-
| `createRequirements(autoDetect: true)` | 2-5s | <1ms |
|
|
529
|
-
| `createRequirements(autoDetect: false)` | <1ms | <1ms |
|
|
530
|
-
| `process()` | 2-5s + network | <1ms + network |
|
|
653
|
+
### Express 中间件
|
|
531
654
|
|
|
532
|
-
|
|
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
|
|
655
|
+
#### 基础用法
|
|
536
656
|
|
|
537
|
-
|
|
657
|
+
```typescript
|
|
658
|
+
import { createExpressMiddleware } from "@wtflabs/x402-server";
|
|
659
|
+
|
|
660
|
+
const middleware = createExpressMiddleware({
|
|
661
|
+
server,
|
|
662
|
+
getToken: (req) => req.body.token || "0xUSDC",
|
|
663
|
+
getAmount: (req) => calculatePrice(req.body),
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
app.post("/api", middleware, (req, res) => {
|
|
667
|
+
const { payer, txHash } = req.x402!;
|
|
668
|
+
res.json({ data: "resource", payer, txHash });
|
|
669
|
+
});
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
#### 高级配置
|
|
538
673
|
|
|
539
674
|
```typescript
|
|
540
|
-
const
|
|
675
|
+
const middleware = createExpressMiddleware({
|
|
676
|
+
server,
|
|
677
|
+
|
|
678
|
+
// 获取 token 地址
|
|
679
|
+
getToken: (req) => req.query.token as string || "0xUSDC",
|
|
680
|
+
|
|
681
|
+
// 获取金额
|
|
682
|
+
getAmount: (req) => {
|
|
683
|
+
const { complexity } = req.body;
|
|
684
|
+
return calculateDynamicPrice(complexity);
|
|
685
|
+
},
|
|
686
|
+
|
|
687
|
+
// 可选:额外配置
|
|
688
|
+
getConfig: (req) => ({
|
|
689
|
+
description: `API call for user ${req.user?.id}`,
|
|
690
|
+
resource: req.url,
|
|
691
|
+
}),
|
|
692
|
+
|
|
693
|
+
// 可选:自定义错误处理
|
|
694
|
+
onError: (error, req, res) => {
|
|
695
|
+
console.error("Payment error:", error);
|
|
696
|
+
res.status(500).json({ error: error.message });
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
// 可选:自定义 402 响应
|
|
700
|
+
on402: (req, res, response402) => {
|
|
701
|
+
console.log("Payment required for:", req.url);
|
|
702
|
+
res.status(402).json(response402);
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
// 可选:支付成功回调
|
|
706
|
+
onPaymentSuccess: async (req, payer, txHash) => {
|
|
707
|
+
await logPayment(payer, txHash);
|
|
708
|
+
console.log(`Payment received from ${payer}`);
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
```
|
|
541
712
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
713
|
+
#### 类型定义
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
import type {
|
|
717
|
+
ExpressRequest,
|
|
718
|
+
ExpressResponse,
|
|
719
|
+
ExpressNextFunction,
|
|
720
|
+
ExpressMiddleware
|
|
721
|
+
} from "@wtflabs/x402-server";
|
|
722
|
+
|
|
723
|
+
// ExpressRequest 接口
|
|
724
|
+
interface ExpressRequest {
|
|
725
|
+
headers: Record<string, string | string[] | undefined>;
|
|
726
|
+
body?: unknown;
|
|
727
|
+
params?: Record<string, string>;
|
|
728
|
+
query?: Record<string, string | string[] | undefined>;
|
|
729
|
+
x402?: {
|
|
730
|
+
payer: string;
|
|
731
|
+
txHash: string;
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// ExpressResponse 接口
|
|
736
|
+
interface ExpressResponse {
|
|
737
|
+
status(code: number): this;
|
|
738
|
+
json(body: unknown): this;
|
|
546
739
|
}
|
|
547
740
|
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
741
|
+
// 中间件类型
|
|
742
|
+
type ExpressMiddleware = (
|
|
743
|
+
req: ExpressRequest,
|
|
744
|
+
res: ExpressResponse,
|
|
745
|
+
next: ExpressNextFunction
|
|
746
|
+
) => void | Promise<void>;
|
|
551
747
|
```
|
|
552
748
|
|
|
553
|
-
|
|
749
|
+
### Hono 中间件
|
|
554
750
|
|
|
555
|
-
|
|
751
|
+
#### 基础用法
|
|
752
|
+
|
|
753
|
+
```typescript
|
|
754
|
+
import { createHonoMiddleware } from "@wtflabs/x402-server";
|
|
755
|
+
|
|
756
|
+
const middleware = createHonoMiddleware({
|
|
757
|
+
server,
|
|
758
|
+
getToken: (c) => c.req.query("token") || "0xUSDC",
|
|
759
|
+
getAmount: async (c) => {
|
|
760
|
+
const body = await c.req.json();
|
|
761
|
+
return calculatePrice(body.complexity);
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
app.post("/api", middleware, (c) => {
|
|
766
|
+
const x402 = c.get("x402") as { payer: string; txHash: string };
|
|
767
|
+
return c.json({ data: "resource", payer: x402.payer });
|
|
768
|
+
});
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
#### 高级配置
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
const middleware = createHonoMiddleware({
|
|
775
|
+
server,
|
|
776
|
+
|
|
777
|
+
// 获取 token 地址
|
|
778
|
+
getToken: (c) => c.req.query("token") || "0xUSDC",
|
|
779
|
+
|
|
780
|
+
// 获取金额
|
|
781
|
+
getAmount: async (c) => {
|
|
782
|
+
const body = await c.req.json<{ complexity: number }>();
|
|
783
|
+
return calculateDynamicPrice(body.complexity);
|
|
784
|
+
},
|
|
785
|
+
|
|
786
|
+
// 可选:额外配置
|
|
787
|
+
getConfig: async (c) => {
|
|
788
|
+
const body = await c.req.json();
|
|
789
|
+
return {
|
|
790
|
+
description: `API call with complexity ${body.complexity}`,
|
|
791
|
+
};
|
|
792
|
+
},
|
|
793
|
+
|
|
794
|
+
// 可选:自定义错误处理
|
|
795
|
+
onError: (error, c) => {
|
|
796
|
+
console.error("Payment error:", error);
|
|
797
|
+
return c.json({ error: error.message }, 500);
|
|
798
|
+
},
|
|
799
|
+
|
|
800
|
+
// 可选:自定义 402 响应
|
|
801
|
+
on402: (c, response402) => {
|
|
802
|
+
console.log("Payment required");
|
|
803
|
+
return c.json(response402, 402);
|
|
804
|
+
},
|
|
805
|
+
|
|
806
|
+
// 可选:支付成功回调
|
|
807
|
+
onPaymentSuccess: async (c, payer, txHash) => {
|
|
808
|
+
await logPayment(payer, txHash);
|
|
809
|
+
console.log(`Payment received from ${payer}`);
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
#### 类型定义
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
import type {
|
|
818
|
+
HonoContext,
|
|
819
|
+
HonoRequest,
|
|
820
|
+
HonoNext,
|
|
821
|
+
HonoMiddlewareHandler
|
|
822
|
+
} from "@wtflabs/x402-server";
|
|
823
|
+
|
|
824
|
+
// HonoRequest 接口
|
|
825
|
+
interface HonoRequest {
|
|
826
|
+
header(name: string): string | undefined;
|
|
827
|
+
json<T = unknown>(): Promise<T>;
|
|
828
|
+
query(name: string): string | undefined;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// HonoContext 接口
|
|
832
|
+
interface HonoContext {
|
|
833
|
+
req: HonoRequest;
|
|
834
|
+
json(body: unknown, status?: number): Response;
|
|
835
|
+
set(key: string, value: unknown): void;
|
|
836
|
+
get(key: string): unknown;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// 中间件类型
|
|
840
|
+
type HonoMiddlewareHandler = (
|
|
841
|
+
c: HonoContext,
|
|
842
|
+
next: HonoNext
|
|
843
|
+
) => Promise<Response | void>;
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
## 🎨 TypeScript 类型
|
|
847
|
+
|
|
848
|
+
### 完整类型导出
|
|
556
849
|
|
|
557
850
|
```typescript
|
|
558
851
|
import type {
|
|
852
|
+
// 配置
|
|
559
853
|
X402ServerConfig,
|
|
560
854
|
CreateRequirementsConfig,
|
|
855
|
+
|
|
856
|
+
// 数据结构
|
|
561
857
|
PaymentRequirements,
|
|
562
|
-
|
|
858
|
+
PaymentPayload,
|
|
859
|
+
Response402,
|
|
860
|
+
ParsedPayment,
|
|
861
|
+
|
|
862
|
+
// 结果类型
|
|
563
863
|
InitResult,
|
|
864
|
+
ProcessResult,
|
|
865
|
+
ParseResult,
|
|
866
|
+
VerifyResult,
|
|
867
|
+
SettleResult,
|
|
868
|
+
|
|
869
|
+
// 中间件类型
|
|
870
|
+
ExpressRequest,
|
|
871
|
+
ExpressResponse,
|
|
872
|
+
ExpressNextFunction,
|
|
873
|
+
ExpressMiddleware,
|
|
874
|
+
ExpressMiddlewareOptions,
|
|
875
|
+
|
|
876
|
+
HonoContext,
|
|
877
|
+
HonoRequest,
|
|
878
|
+
HonoNext,
|
|
879
|
+
HonoMiddlewareHandler,
|
|
880
|
+
HonoMiddlewareOptions,
|
|
881
|
+
|
|
882
|
+
// Facilitator 类型
|
|
883
|
+
WaitUntil,
|
|
564
884
|
} from "@wtflabs/x402-server";
|
|
565
885
|
```
|
|
566
886
|
|
|
567
|
-
|
|
887
|
+
### Zod Schema 导出
|
|
568
888
|
|
|
569
|
-
|
|
889
|
+
所有类型都有对应的 Zod schema,用于运行时验证:
|
|
570
890
|
|
|
571
891
|
```typescript
|
|
572
|
-
import {
|
|
892
|
+
import {
|
|
893
|
+
CreateRequirementsConfigSchema,
|
|
894
|
+
PaymentRequirementsSchema,
|
|
895
|
+
PaymentPayloadSchema,
|
|
896
|
+
Response402Schema,
|
|
897
|
+
InitResultSchema,
|
|
898
|
+
ProcessResultSchema,
|
|
899
|
+
ParseResultSchema,
|
|
900
|
+
VerifyResultSchema,
|
|
901
|
+
SettleResultSchema,
|
|
902
|
+
ParsedPaymentSchema,
|
|
903
|
+
} from "@wtflabs/x402-server";
|
|
573
904
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
getAmount: (req) => calculatePrice(req.body),
|
|
578
|
-
onPaymentSuccess: async (req, payer, txHash) => {
|
|
579
|
-
console.log(`Payment from ${payer}: ${txHash}`);
|
|
580
|
-
},
|
|
581
|
-
});
|
|
905
|
+
// 使用 schema 验证
|
|
906
|
+
const validated = CreateRequirementsConfigSchema.parse(config);
|
|
907
|
+
```
|
|
582
908
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
909
|
+
## ⚡ 性能优化
|
|
910
|
+
|
|
911
|
+
### 性能指标
|
|
912
|
+
|
|
913
|
+
| 操作 | 首次调用 | 缓存调用 |
|
|
914
|
+
|------|---------|---------|
|
|
915
|
+
| `createRequirements(autoDetect: true)` | 2-5s | <1ms |
|
|
916
|
+
| `createRequirements(autoDetect: false)` | <1ms | <1ms |
|
|
917
|
+
| `process()` | 2-5s + 网络 | <1ms + 网络 |
|
|
918
|
+
|
|
919
|
+
### 优化技巧
|
|
920
|
+
|
|
921
|
+
#### 1. 预热缓存
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
// 服务启动时预热
|
|
925
|
+
await server.initialize([
|
|
926
|
+
"0xUSDC",
|
|
927
|
+
"0xDAI",
|
|
928
|
+
"0xUSDT",
|
|
929
|
+
]);
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
#### 2. 快速模式
|
|
933
|
+
|
|
934
|
+
```typescript
|
|
935
|
+
// 跳过自动检测以获得最大性能
|
|
936
|
+
const requirements = await server.createRequirements({
|
|
937
|
+
asset: "0xUSDC",
|
|
938
|
+
maxAmountRequired: "1000000",
|
|
939
|
+
paymentType: "permit",
|
|
940
|
+
autoDetect: false, // <1ms
|
|
586
941
|
});
|
|
587
942
|
```
|
|
588
943
|
|
|
589
|
-
|
|
944
|
+
#### 3. 复用 Requirements
|
|
590
945
|
|
|
591
946
|
```typescript
|
|
592
|
-
|
|
947
|
+
// 对于固定金额的 API,可以复用 requirements
|
|
948
|
+
const cachedRequirements = await server.createRequirements({
|
|
949
|
+
asset: "0xUSDC",
|
|
950
|
+
maxAmountRequired: "1000000",
|
|
951
|
+
});
|
|
593
952
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
953
|
+
// 在多个请求中复用
|
|
954
|
+
app.post("/api", async (req, res) => {
|
|
955
|
+
const result = await server.process(
|
|
956
|
+
req.headers["x-payment"] as string,
|
|
957
|
+
cachedRequirements
|
|
958
|
+
);
|
|
959
|
+
// ...
|
|
601
960
|
});
|
|
961
|
+
```
|
|
602
962
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
963
|
+
#### 4. 后台初始化
|
|
964
|
+
|
|
965
|
+
```typescript
|
|
966
|
+
// 不阻塞服务启动
|
|
967
|
+
server.initialize([tokenAddress]).then(result => {
|
|
968
|
+
if (result.success) {
|
|
969
|
+
console.log("✅ Cache warmed up");
|
|
970
|
+
}
|
|
606
971
|
});
|
|
972
|
+
|
|
973
|
+
// 立即开始服务
|
|
974
|
+
app.listen(3000);
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
## ❌ 错误处理
|
|
978
|
+
|
|
979
|
+
### ProcessResult 类型
|
|
980
|
+
|
|
981
|
+
```typescript
|
|
982
|
+
type ProcessResult =
|
|
983
|
+
| {
|
|
984
|
+
success: true;
|
|
985
|
+
status: 200;
|
|
986
|
+
data: {
|
|
987
|
+
payer: string;
|
|
988
|
+
txHash: string;
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
| {
|
|
992
|
+
success: false;
|
|
993
|
+
status: 402;
|
|
994
|
+
response: Response402;
|
|
995
|
+
};
|
|
607
996
|
```
|
|
608
997
|
|
|
609
|
-
|
|
998
|
+
### 错误处理示例
|
|
610
999
|
|
|
611
|
-
|
|
1000
|
+
```typescript
|
|
1001
|
+
const result = await server.process(paymentHeader, requirements);
|
|
1002
|
+
|
|
1003
|
+
if (!result.success) {
|
|
1004
|
+
// 402 响应,包含错误详情
|
|
1005
|
+
console.log("Error:", result.response.error);
|
|
1006
|
+
console.log("Accepts:", result.response.accepts);
|
|
1007
|
+
return res.status(402).json(result.response);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// 成功
|
|
1011
|
+
console.log("Payer:", result.data.payer);
|
|
1012
|
+
console.log("TxHash:", result.data.txHash);
|
|
1013
|
+
```
|
|
612
1014
|
|
|
613
|
-
|
|
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
|
|
1015
|
+
### 常见错误
|
|
620
1016
|
|
|
621
|
-
|
|
1017
|
+
| 错误 | 原因 | 解决方案 |
|
|
1018
|
+
|------|------|---------|
|
|
1019
|
+
| `missing_payment_header` | 未提供 X-Payment header | 客户端需要发送支付头 |
|
|
1020
|
+
| `invalid_payment_header` | 支付头格式错误 | 检查 Base64 编码和 JSON 格式 |
|
|
1021
|
+
| `Verification failed` | 签名验证失败 | 检查签名和参数匹配 |
|
|
1022
|
+
| `Settlement failed` | 链上交易失败 | 检查余额、授权和网络状态 |
|
|
1023
|
+
|
|
1024
|
+
### Try-Catch 处理
|
|
1025
|
+
|
|
1026
|
+
```typescript
|
|
1027
|
+
try {
|
|
1028
|
+
const result = await server.process(paymentHeader, requirements);
|
|
1029
|
+
|
|
1030
|
+
if (!result.success) {
|
|
1031
|
+
return res.status(402).json(result.response);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
res.json({ data: "success" });
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
console.error("Unexpected error:", error);
|
|
1037
|
+
res.status(500).json({
|
|
1038
|
+
error: "Internal server error",
|
|
1039
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
## 🔍 调试
|
|
1045
|
+
|
|
1046
|
+
### 启用日志
|
|
1047
|
+
|
|
1048
|
+
```typescript
|
|
1049
|
+
// Facilitator 支持自定义 logger
|
|
1050
|
+
const facilitator = new Facilitator({
|
|
1051
|
+
recipientAddress: "0x...",
|
|
1052
|
+
logger: console, // 或自定义 logger
|
|
1053
|
+
});
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
### 缓存统计
|
|
1057
|
+
|
|
1058
|
+
```typescript
|
|
1059
|
+
const stats = server.getCacheStats();
|
|
1060
|
+
console.log("Cache size:", stats.size);
|
|
1061
|
+
console.log("Cached tokens:", stats.keys);
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
### 清除缓存
|
|
1065
|
+
|
|
1066
|
+
```typescript
|
|
1067
|
+
// 清除特定 token
|
|
1068
|
+
await server.clearCache("0xUSDC");
|
|
1069
|
+
|
|
1070
|
+
// 清除所有缓存
|
|
1071
|
+
await server.clearCache();
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
## 📖 相关资源
|
|
1075
|
+
|
|
1076
|
+
### 相关包
|
|
1077
|
+
|
|
1078
|
+
- [`@wtflabs/x402`](../x402) - 核心协议类型和工具
|
|
1079
|
+
- [`@wtflabs/x402-detector`](../x402-detector) - Token 检测库
|
|
1080
|
+
- [`@wtflabs/x402-facilitator`](../x402-facilitator) - 支付处理库
|
|
1081
|
+
- [`@wtflabs/x402-fetch`](../x402-fetch) - 客户端 SDK
|
|
1082
|
+
|
|
1083
|
+
### 文档
|
|
1084
|
+
|
|
1085
|
+
- [x402 协议规范](../../specs/x402-specification.md)
|
|
1086
|
+
- [Exact Scheme 文档](../../specs/schemes/exact/)
|
|
1087
|
+
- [HTTP Transport](../../specs/transports/http.md)
|
|
1088
|
+
|
|
1089
|
+
## 📄 许可证
|
|
622
1090
|
|
|
623
1091
|
Apache-2.0
|
|
624
1092
|
|
|
625
|
-
##
|
|
1093
|
+
## 🤝 贡献
|
|
1094
|
+
|
|
1095
|
+
欢迎提交 Issue 和 Pull Request!
|
|
1096
|
+
|
|
1097
|
+
---
|
|
626
1098
|
|
|
627
|
-
|
|
628
|
-
- `@wtflabs/x402-facilitator` - Payment processing (used internally)
|
|
629
|
-
- `@wtflabs/x402-fetch` - Client SDK
|
|
630
|
-
- `@wtflabs/x402` - Core protocol types
|
|
1099
|
+
**Made with ❤️ by WTFLabs**
|