@wtflabs/x402-server 0.0.1-beta.2
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 +313 -0
- package/dist/index.d.mts +221 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.js +548 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +518 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
// src/token-detection.ts
|
|
2
|
+
var EIP3009_SIGNATURES = ["0xe3ee160e", "0xcf092995"];
|
|
3
|
+
var EIP2612_PERMIT = "0xd505accf";
|
|
4
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
5
|
+
async function hasMethod(client, tokenAddress, methodSelector) {
|
|
6
|
+
try {
|
|
7
|
+
const code = await client.getBytecode({ address: tokenAddress });
|
|
8
|
+
if (!code) return false;
|
|
9
|
+
return code.toLowerCase().includes(methodSelector.slice(2).toLowerCase());
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error(`Error checking method ${methodSelector}:`, error);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function hasAnyMethod(client, tokenAddress, methodSelectors) {
|
|
16
|
+
try {
|
|
17
|
+
const code = await client.getBytecode({ address: tokenAddress });
|
|
18
|
+
if (!code) return false;
|
|
19
|
+
const codeLower = code.toLowerCase();
|
|
20
|
+
return methodSelectors.some(
|
|
21
|
+
(selector) => codeLower.includes(selector.slice(2).toLowerCase())
|
|
22
|
+
);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(`Error checking methods ${methodSelectors.join(", ")}:`, error);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function checkPermit2Support(client) {
|
|
29
|
+
try {
|
|
30
|
+
const permit2Code = await client.getBytecode({ address: PERMIT2_ADDRESS });
|
|
31
|
+
if (!permit2Code) return false;
|
|
32
|
+
return true;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("Error checking Permit2 support:", error);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function detectTokenPaymentMethods(tokenAddress, client) {
|
|
39
|
+
const address = tokenAddress.toLowerCase();
|
|
40
|
+
console.log(`\u{1F50D} Detecting payment methods for token ${address}...`);
|
|
41
|
+
const [hasEIP3009, hasPermit, hasPermit2Approval] = await Promise.all([
|
|
42
|
+
hasAnyMethod(client, address, EIP3009_SIGNATURES),
|
|
43
|
+
hasMethod(client, address, EIP2612_PERMIT),
|
|
44
|
+
checkPermit2Support(client)
|
|
45
|
+
]);
|
|
46
|
+
const supportedMethods = [];
|
|
47
|
+
if (hasEIP3009) {
|
|
48
|
+
supportedMethods.push("eip3009");
|
|
49
|
+
console.log(" \u2705 EIP-3009 (transferWithAuthorization) detected");
|
|
50
|
+
}
|
|
51
|
+
if (hasPermit) {
|
|
52
|
+
supportedMethods.push("permit");
|
|
53
|
+
console.log(" \u2705 EIP-2612 (permit) detected");
|
|
54
|
+
}
|
|
55
|
+
if (hasPermit2Approval) {
|
|
56
|
+
supportedMethods.push("permit2");
|
|
57
|
+
supportedMethods.push("permit2-witness");
|
|
58
|
+
console.log(" \u2705 Permit2 support available (universal)");
|
|
59
|
+
}
|
|
60
|
+
if (supportedMethods.length === 0) {
|
|
61
|
+
console.log(" \u26A0\uFE0F No advanced payment methods detected (standard ERC-20 only)");
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
address,
|
|
65
|
+
supportedMethods,
|
|
66
|
+
details: {
|
|
67
|
+
hasEIP3009,
|
|
68
|
+
hasPermit,
|
|
69
|
+
hasPermit2Approval
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function getRecommendedPaymentMethod(capabilities) {
|
|
74
|
+
const { supportedMethods } = capabilities;
|
|
75
|
+
if (supportedMethods.includes("eip3009")) return "eip3009";
|
|
76
|
+
if (supportedMethods.includes("permit")) return "permit";
|
|
77
|
+
if (supportedMethods.includes("permit2") || supportedMethods.includes("permit2-witness")) {
|
|
78
|
+
return "permit2";
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
async function getTokenInfo(tokenAddress, client) {
|
|
83
|
+
const address = tokenAddress.toLowerCase();
|
|
84
|
+
const erc20ABI = [
|
|
85
|
+
{
|
|
86
|
+
inputs: [],
|
|
87
|
+
name: "name",
|
|
88
|
+
outputs: [{ name: "", type: "string" }],
|
|
89
|
+
stateMutability: "view",
|
|
90
|
+
type: "function"
|
|
91
|
+
}
|
|
92
|
+
];
|
|
93
|
+
const eip712DomainABI = [
|
|
94
|
+
{
|
|
95
|
+
inputs: [],
|
|
96
|
+
name: "eip712Domain",
|
|
97
|
+
outputs: [
|
|
98
|
+
{ name: "fields", type: "bytes1" },
|
|
99
|
+
{ name: "name", type: "string" },
|
|
100
|
+
{ name: "version", type: "string" },
|
|
101
|
+
{ name: "chainId", type: "uint256" },
|
|
102
|
+
{ name: "verifyingContract", type: "address" },
|
|
103
|
+
{ name: "salt", type: "bytes32" },
|
|
104
|
+
{ name: "extensions", type: "uint256[]" }
|
|
105
|
+
],
|
|
106
|
+
stateMutability: "view",
|
|
107
|
+
type: "function"
|
|
108
|
+
}
|
|
109
|
+
];
|
|
110
|
+
const versionABI = [
|
|
111
|
+
{
|
|
112
|
+
inputs: [],
|
|
113
|
+
name: "version",
|
|
114
|
+
outputs: [{ name: "", type: "string" }],
|
|
115
|
+
stateMutability: "view",
|
|
116
|
+
type: "function"
|
|
117
|
+
}
|
|
118
|
+
];
|
|
119
|
+
try {
|
|
120
|
+
const name = await client.readContract({
|
|
121
|
+
address,
|
|
122
|
+
abi: erc20ABI,
|
|
123
|
+
functionName: "name"
|
|
124
|
+
});
|
|
125
|
+
let version = "1";
|
|
126
|
+
try {
|
|
127
|
+
const result = await client.readContract({
|
|
128
|
+
address,
|
|
129
|
+
abi: eip712DomainABI,
|
|
130
|
+
functionName: "eip712Domain"
|
|
131
|
+
});
|
|
132
|
+
version = result[2];
|
|
133
|
+
} catch {
|
|
134
|
+
try {
|
|
135
|
+
version = await client.readContract({
|
|
136
|
+
address,
|
|
137
|
+
abi: versionABI,
|
|
138
|
+
functionName: "version"
|
|
139
|
+
});
|
|
140
|
+
} catch {
|
|
141
|
+
console.log(` \u2139\uFE0F Using default version "1" for token ${address}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
name,
|
|
146
|
+
version
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(`Error getting token info for ${address}:`, error);
|
|
150
|
+
throw new Error(`Failed to get token info: ${error}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/server.ts
|
|
155
|
+
var X402Server = class {
|
|
156
|
+
constructor(config) {
|
|
157
|
+
this.initialized = false;
|
|
158
|
+
this.facilitator = config.facilitator;
|
|
159
|
+
this.schema = config.schema;
|
|
160
|
+
this.client = config.client;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 初始化服务器
|
|
164
|
+
* 初始化和校验 schema 等数据,对 schema 增加 facilitator 数据 extra: {relayer}
|
|
165
|
+
* 并获取 token 的 name 和 version 信息
|
|
166
|
+
*/
|
|
167
|
+
async initialize() {
|
|
168
|
+
try {
|
|
169
|
+
this.schema.verify();
|
|
170
|
+
const schemaAsset = this.schema.get("asset");
|
|
171
|
+
if (schemaAsset) {
|
|
172
|
+
try {
|
|
173
|
+
const tokenInfo = await getTokenInfo(schemaAsset, this.client);
|
|
174
|
+
this.schema.setExtra({
|
|
175
|
+
relayer: this.facilitator.relayer,
|
|
176
|
+
name: tokenInfo.name,
|
|
177
|
+
version: tokenInfo.version
|
|
178
|
+
});
|
|
179
|
+
console.log(`\u2705 Token info retrieved: ${tokenInfo.name} v${tokenInfo.version}`);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.warn(`\u26A0\uFE0F Failed to get token info, signatures may fail:`, error);
|
|
182
|
+
this.schema.setExtra({
|
|
183
|
+
relayer: this.facilitator.relayer
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
this.schema.setExtra({
|
|
188
|
+
relayer: this.facilitator.relayer
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const verifyResult = await this._verify();
|
|
192
|
+
if (!verifyResult.success) {
|
|
193
|
+
return {
|
|
194
|
+
success: false,
|
|
195
|
+
error: verifyResult.errors?.[0] || "Verification failed"
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
this.initialized = true;
|
|
199
|
+
return { success: true };
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
error: error instanceof Error ? error.message : "Initialization failed"
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 验证配置
|
|
209
|
+
* 1. 验证 client network 是否和 schema 的 network 匹配
|
|
210
|
+
* 2. 验证 facilitator recipientAddress 和 schema payTo 是否一致
|
|
211
|
+
*/
|
|
212
|
+
async _verify() {
|
|
213
|
+
const errors = [];
|
|
214
|
+
try {
|
|
215
|
+
const schemaAsset = this.schema.get("asset");
|
|
216
|
+
if (schemaAsset) {
|
|
217
|
+
const tokenCapabilities = await detectTokenPaymentMethods(
|
|
218
|
+
schemaAsset,
|
|
219
|
+
this.client
|
|
220
|
+
);
|
|
221
|
+
const currentPaymentType = this.schema.get("paymentType");
|
|
222
|
+
if (!currentPaymentType) {
|
|
223
|
+
const recommendedMethod = getRecommendedPaymentMethod(tokenCapabilities);
|
|
224
|
+
if (recommendedMethod) {
|
|
225
|
+
this.schema.set("paymentType", recommendedMethod);
|
|
226
|
+
} else {
|
|
227
|
+
errors.push(
|
|
228
|
+
`Token ${schemaAsset} does not support any advanced payment methods (permit, eip3009, permit2). Please specify paymentType manually.`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
if (!tokenCapabilities.supportedMethods.includes(currentPaymentType)) {
|
|
233
|
+
errors.push(
|
|
234
|
+
`Token ${schemaAsset} does not support the specified payment method "${currentPaymentType}". Supported methods: ${tokenCapabilities.supportedMethods.join(", ")}`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (tokenCapabilities.supportedMethods.length === 0) {
|
|
239
|
+
errors.push(
|
|
240
|
+
`Token ${schemaAsset} does not support any advanced payment methods (permit, eip3009, permit2)`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const clientChainId = this.client.chain?.id;
|
|
245
|
+
const schemaNetwork = this.schema.get("network");
|
|
246
|
+
if (clientChainId && schemaAsset) {
|
|
247
|
+
const facilitatorSupported = await this.facilitator.supported({
|
|
248
|
+
chainId: clientChainId,
|
|
249
|
+
tokenAddress: schemaAsset
|
|
250
|
+
});
|
|
251
|
+
const isSupportedByFacilitator = facilitatorSupported.kinds.some(
|
|
252
|
+
(kind) => {
|
|
253
|
+
const networkMatches = kind.network === schemaNetwork;
|
|
254
|
+
const assetsInKind = kind.extra?.assets || [];
|
|
255
|
+
const assetMatches = assetsInKind.some(
|
|
256
|
+
(asset) => asset.address.toLowerCase() === schemaAsset.toLowerCase()
|
|
257
|
+
);
|
|
258
|
+
return networkMatches && assetMatches;
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
if (!isSupportedByFacilitator) {
|
|
262
|
+
errors.push(
|
|
263
|
+
`Facilitator does not support token ${schemaAsset} on network ${schemaNetwork} (chainId: ${clientChainId})`
|
|
264
|
+
);
|
|
265
|
+
} else {
|
|
266
|
+
}
|
|
267
|
+
const chainSupported = facilitatorSupported.kinds.some(
|
|
268
|
+
(kind) => kind.network === schemaNetwork
|
|
269
|
+
);
|
|
270
|
+
if (!chainSupported) {
|
|
271
|
+
errors.push(
|
|
272
|
+
`Facilitator does not support network ${schemaNetwork} (chainId: ${clientChainId})`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (clientChainId) {
|
|
277
|
+
const networkValid = this.validateNetwork(
|
|
278
|
+
schemaNetwork,
|
|
279
|
+
clientChainId
|
|
280
|
+
);
|
|
281
|
+
if (!networkValid) {
|
|
282
|
+
errors.push(
|
|
283
|
+
`Network mismatch: client chainId ${clientChainId} does not match schema network ${schemaNetwork}`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const schemaPayTo = this.schema.get("payTo");
|
|
288
|
+
const facilitatorRecipientAddress = this.facilitator.recipientAddress;
|
|
289
|
+
if (schemaPayTo.toLowerCase() !== facilitatorRecipientAddress.toLowerCase()) {
|
|
290
|
+
errors.push(
|
|
291
|
+
`Address mismatch: schema payTo ${schemaPayTo} does not match facilitator recipientAddress ${facilitatorRecipientAddress}`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
success: errors.length === 0,
|
|
296
|
+
errors: errors.length > 0 ? errors : void 0
|
|
297
|
+
};
|
|
298
|
+
} catch (error) {
|
|
299
|
+
errors.push(
|
|
300
|
+
error instanceof Error ? error.message : "Unknown verification error"
|
|
301
|
+
);
|
|
302
|
+
return {
|
|
303
|
+
success: false,
|
|
304
|
+
errors
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 结算支付
|
|
310
|
+
* @param paymentPayload 支付负载
|
|
311
|
+
* @param paymentRequirements 支付要求
|
|
312
|
+
*/
|
|
313
|
+
async settle(paymentPayload, paymentRequirements) {
|
|
314
|
+
if (!this.initialized) {
|
|
315
|
+
return {
|
|
316
|
+
success: false,
|
|
317
|
+
error: "Server not initialized. Please call initialize() first."
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const result = await this.facilitator.settle(
|
|
322
|
+
paymentPayload,
|
|
323
|
+
paymentRequirements
|
|
324
|
+
);
|
|
325
|
+
if (!result.success) {
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
error: result.error || result.errorMessage
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
success: true,
|
|
333
|
+
transactionHash: result.transactionHash
|
|
334
|
+
};
|
|
335
|
+
} catch (error) {
|
|
336
|
+
return {
|
|
337
|
+
success: false,
|
|
338
|
+
error: error instanceof Error ? error.message : "Settlement failed"
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* 验证支付负载
|
|
344
|
+
* @param paymentPayload 支付负载
|
|
345
|
+
* @param paymentRequirements 支付要求
|
|
346
|
+
*/
|
|
347
|
+
async verifyPayment(paymentPayload, paymentRequirements) {
|
|
348
|
+
if (!this.initialized) {
|
|
349
|
+
return {
|
|
350
|
+
success: false,
|
|
351
|
+
error: "Server not initialized. Please call initialize() first."
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const result = await this.facilitator.verify(
|
|
356
|
+
paymentPayload,
|
|
357
|
+
paymentRequirements
|
|
358
|
+
);
|
|
359
|
+
return {
|
|
360
|
+
success: result.success,
|
|
361
|
+
data: result.payer,
|
|
362
|
+
error: result.error || result.errorMessage
|
|
363
|
+
};
|
|
364
|
+
} catch (error) {
|
|
365
|
+
return {
|
|
366
|
+
success: false,
|
|
367
|
+
error: error instanceof Error ? error.message : "Payment verification failed"
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* 获取 facilitator
|
|
373
|
+
*/
|
|
374
|
+
getFacilitator() {
|
|
375
|
+
return this.facilitator;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* 获取 schema
|
|
379
|
+
*/
|
|
380
|
+
getSchema() {
|
|
381
|
+
return this.schema;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* 获取 client
|
|
385
|
+
*/
|
|
386
|
+
getClient() {
|
|
387
|
+
return this.client;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* 检查是否已初始化
|
|
391
|
+
*/
|
|
392
|
+
isInitialized() {
|
|
393
|
+
return this.initialized;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* 验证网络是否匹配
|
|
397
|
+
* @param schemaNetwork schema 中的 network
|
|
398
|
+
* @param clientChainId client 的 chainId
|
|
399
|
+
* @returns 是否匹配
|
|
400
|
+
*/
|
|
401
|
+
validateNetwork(schemaNetwork, clientChainId) {
|
|
402
|
+
if (schemaNetwork.startsWith("eip155:")) {
|
|
403
|
+
const chainId = parseInt(schemaNetwork.split(":")[1] || "0");
|
|
404
|
+
return chainId === clientChainId;
|
|
405
|
+
}
|
|
406
|
+
const networkMap = {
|
|
407
|
+
"ethereum": 1,
|
|
408
|
+
"goerli": 5,
|
|
409
|
+
"sepolia": 11155111,
|
|
410
|
+
"base": 8453,
|
|
411
|
+
"base-sepolia": 84532,
|
|
412
|
+
"bsc": 56,
|
|
413
|
+
"bsc-testnet": 97,
|
|
414
|
+
"polygon": 137,
|
|
415
|
+
"polygon-mumbai": 80001,
|
|
416
|
+
"arbitrum": 42161,
|
|
417
|
+
"arbitrum-goerli": 421613,
|
|
418
|
+
"optimism": 10,
|
|
419
|
+
"optimism-goerli": 420,
|
|
420
|
+
"avalanche": 43114,
|
|
421
|
+
"avalanche-fuji": 43113
|
|
422
|
+
};
|
|
423
|
+
const expectedChainId = networkMap[schemaNetwork];
|
|
424
|
+
if (expectedChainId !== void 0) {
|
|
425
|
+
return expectedChainId === clientChainId;
|
|
426
|
+
}
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* 解析支付 header
|
|
431
|
+
* @param paymentHeaderBase64 Base64 编码的支付 header
|
|
432
|
+
* @returns 解析结果,成功时返回 paymentPayload 和 paymentRequirements,失败时返回服务端的支付要求
|
|
433
|
+
*/
|
|
434
|
+
parsePaymentHeader(paymentHeaderBase64) {
|
|
435
|
+
const paymentRequirements = this.schema.toJSON();
|
|
436
|
+
if (!paymentHeaderBase64) {
|
|
437
|
+
return {
|
|
438
|
+
success: false,
|
|
439
|
+
data: paymentRequirements,
|
|
440
|
+
error: "No X-PAYMENT header"
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
let paymentPayload;
|
|
444
|
+
try {
|
|
445
|
+
const paymentHeaderJson = Buffer.from(
|
|
446
|
+
paymentHeaderBase64,
|
|
447
|
+
"base64"
|
|
448
|
+
).toString("utf-8");
|
|
449
|
+
paymentPayload = JSON.parse(paymentHeaderJson);
|
|
450
|
+
} catch (err) {
|
|
451
|
+
return {
|
|
452
|
+
success: false,
|
|
453
|
+
data: paymentRequirements,
|
|
454
|
+
error: "Invalid payment header format"
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const validationError = this.validatePaymentPayload(
|
|
458
|
+
paymentPayload,
|
|
459
|
+
paymentRequirements
|
|
460
|
+
);
|
|
461
|
+
if (validationError) {
|
|
462
|
+
return {
|
|
463
|
+
success: false,
|
|
464
|
+
data: paymentRequirements,
|
|
465
|
+
error: validationError
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
success: true,
|
|
470
|
+
data: {
|
|
471
|
+
paymentPayload,
|
|
472
|
+
paymentRequirements
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* 验证客户端的支付数据是否与服务端要求一致
|
|
478
|
+
* @param paymentPayload 客户端的支付负载
|
|
479
|
+
* @param paymentRequirements 服务端的支付要求
|
|
480
|
+
* @returns 错误信息,如果验证通过则返回 null
|
|
481
|
+
*/
|
|
482
|
+
validatePaymentPayload(paymentPayload, paymentRequirements) {
|
|
483
|
+
if (paymentPayload.scheme !== paymentRequirements.scheme) {
|
|
484
|
+
return `Scheme mismatch: expected '${paymentRequirements.scheme}', got '${paymentPayload.scheme}'`;
|
|
485
|
+
}
|
|
486
|
+
if (paymentPayload.network !== paymentRequirements.network) {
|
|
487
|
+
return `Network mismatch: expected '${paymentRequirements.network}', got '${paymentPayload.network}'`;
|
|
488
|
+
}
|
|
489
|
+
if (paymentPayload.payload) {
|
|
490
|
+
const authorization = paymentPayload.payload.authorization;
|
|
491
|
+
if (authorization?.value) {
|
|
492
|
+
const paymentAmount = BigInt(authorization.value);
|
|
493
|
+
const maxAmount = BigInt(paymentRequirements.maxAmountRequired);
|
|
494
|
+
if (paymentAmount !== maxAmount) {
|
|
495
|
+
return `Payment amount error ${paymentAmount} !== ${maxAmount}`;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (authorization?.to) {
|
|
499
|
+
const expectedPayTo = paymentRequirements.payTo.toLowerCase();
|
|
500
|
+
const actualPayTo = authorization.to.toLowerCase();
|
|
501
|
+
if (actualPayTo !== expectedPayTo && actualPayTo !== paymentRequirements.extra?.relayer?.toLowerCase()) {
|
|
502
|
+
return `PayTo address mismatch: expected '${expectedPayTo}', got '${actualPayTo}'`;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const authorizationType = paymentPayload.payload.authorizationType;
|
|
506
|
+
if (authorizationType === "permit" || authorizationType === "permit2" || authorizationType === "eip3009") {
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
export {
|
|
513
|
+
X402Server,
|
|
514
|
+
detectTokenPaymentMethods,
|
|
515
|
+
getRecommendedPaymentMethod,
|
|
516
|
+
getTokenInfo
|
|
517
|
+
};
|
|
518
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/token-detection.ts","../src/server.ts"],"sourcesContent":["import type { Address, PublicClient } from \"viem\";\n\n/**\n * Token 信息\n */\nexport interface TokenInfo {\n name: string;\n version: string;\n}\n\n/**\n * 支持的支付方式\n */\nexport type PaymentMethod = \"eip3009\" | \"permit\" | \"permit2\" | \"permit2-witness\";\n\n/**\n * 检测结果\n */\nexport interface TokenPaymentCapabilities {\n address: string;\n supportedMethods: PaymentMethod[];\n details: {\n hasEIP3009: boolean;\n hasPermit: boolean;\n hasPermit2Approval: boolean;\n };\n}\n\n/**\n * EIP-3009 方法签名\n * - transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)\n * \n * 支持多个方法签名变体,以兼容不同的实现:\n * - 0xe3ee160e: 标准 EIP-3009 实现\n * - 0xcf092995: 某些代币的替代实现\n */\nconst EIP3009_SIGNATURES = [\"0xe3ee160e\", \"0xcf092995\"] as const;\n\n/**\n * EIP-2612 Permit 方法签名\n * - permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\n */\nconst EIP2612_PERMIT = \"0xd505accf\" as const;\n\n/**\n * Uniswap Permit2 合约地址(所有链相同)\n */\nconst PERMIT2_ADDRESS = \"0x000000000022D473030F116dDEE9F6B43aC78BA3\" as const;\n\n/**\n * 检查合约是否支持某个方法(通过字节码检查)\n */\nasync function hasMethod(\n client: PublicClient,\n tokenAddress: Address,\n methodSelector: string\n): Promise<boolean> {\n try {\n // 尝试获取合约代码\n const code = await client.getBytecode({ address: tokenAddress });\n if (!code) return false;\n\n // 检查字节码中是否包含方法选择器\n return code.toLowerCase().includes(methodSelector.slice(2).toLowerCase());\n } catch (error) {\n console.error(`Error checking method ${methodSelector}:`, error);\n return false;\n }\n}\n\n/**\n * 检查合约是否支持多个方法签名中的任意一个\n */\nasync function hasAnyMethod(\n client: PublicClient,\n tokenAddress: Address,\n methodSelectors: readonly string[]\n): Promise<boolean> {\n try {\n // 尝试获取合约代码\n const code = await client.getBytecode({ address: tokenAddress });\n if (!code) return false;\n\n const codeLower = code.toLowerCase();\n\n // 检查是否包含任何一个方法选择器\n return methodSelectors.some(selector =>\n codeLower.includes(selector.slice(2).toLowerCase())\n );\n } catch (error) {\n console.error(`Error checking methods ${methodSelectors.join(\", \")}:`, error);\n return false;\n }\n}\n\n/**\n * 检查 Permit2 合约是否在该链上部署\n */\nasync function checkPermit2Support(client: PublicClient): Promise<boolean> {\n try {\n // 检查 Permit2 合约是否在该链上部署\n const permit2Code = await client.getBytecode({ address: PERMIT2_ADDRESS });\n if (!permit2Code) return false;\n\n // 如果 Permit2 存在,理论上任何 ERC-20 都可以使用它\n return true;\n } catch (error) {\n console.error(\"Error checking Permit2 support:\", error);\n return false;\n }\n}\n\n/**\n * 检测代币支持的支付方式\n * @param tokenAddress 代币地址\n * @param client viem PublicClient\n * @returns 检测结果\n */\nexport async function detectTokenPaymentMethods(\n tokenAddress: string,\n client: PublicClient\n): Promise<TokenPaymentCapabilities> {\n const address = tokenAddress.toLowerCase() as Address;\n\n console.log(`🔍 Detecting payment methods for token ${address}...`);\n\n // 并行检测所有方法\n const [hasEIP3009, hasPermit, hasPermit2Approval] = await Promise.all([\n hasAnyMethod(client, address, EIP3009_SIGNATURES),\n hasMethod(client, address, EIP2612_PERMIT),\n checkPermit2Support(client),\n ]);\n\n // 构建支持的方法列表\n const supportedMethods: PaymentMethod[] = [];\n\n if (hasEIP3009) {\n supportedMethods.push(\"eip3009\");\n console.log(\" ✅ EIP-3009 (transferWithAuthorization) detected\");\n }\n\n if (hasPermit) {\n supportedMethods.push(\"permit\");\n console.log(\" ✅ EIP-2612 (permit) detected\");\n }\n\n if (hasPermit2Approval) {\n supportedMethods.push(\"permit2\");\n supportedMethods.push(\"permit2-witness\");\n console.log(\" ✅ Permit2 support available (universal)\");\n }\n\n if (supportedMethods.length === 0) {\n console.log(\" ⚠️ No advanced payment methods detected (standard ERC-20 only)\");\n }\n\n return {\n address,\n supportedMethods,\n details: {\n hasEIP3009,\n hasPermit,\n hasPermit2Approval,\n },\n };\n}\n\n/**\n * 获取推荐的支付方式(仅返回 schema 支持的类型)\n * 按优先级排序:eip3009 > permit > permit2\n * 注意:permit2-witness 会被映射为 permit2,因为它们在 schema 中是同一种支付类型\n */\nexport function getRecommendedPaymentMethod(\n capabilities: TokenPaymentCapabilities\n): \"eip3009\" | \"permit2\" | \"permit\" | null {\n const { supportedMethods } = capabilities;\n\n if (supportedMethods.includes(\"eip3009\")) return \"eip3009\";\n if (supportedMethods.includes(\"permit\")) return \"permit\";\n // permit2 和 permit2-witness 都映射为 permit2(schema 只支持 permit2)\n if (supportedMethods.includes(\"permit2\") || supportedMethods.includes(\"permit2-witness\")) {\n return \"permit2\";\n }\n\n return null;\n}\n\n/**\n * 获取 token 的 name 和 version 信息(用于 EIP-712 签名)\n * @param tokenAddress 代币地址\n * @param client viem PublicClient\n * @returns Token 的 name 和 version\n */\nexport async function getTokenInfo(\n tokenAddress: string,\n client: PublicClient\n): Promise<TokenInfo> {\n const address = tokenAddress.toLowerCase() as Address;\n\n // ERC-20 标准 ABI\n const erc20ABI = [\n {\n inputs: [],\n name: \"name\",\n outputs: [{ name: \"\", type: \"string\" }],\n stateMutability: \"view\",\n type: \"function\",\n },\n ] as const;\n\n // EIP-5267 eip712Domain ABI(OpenZeppelin v5+)\n const eip712DomainABI = [\n {\n inputs: [],\n name: \"eip712Domain\",\n outputs: [\n { name: \"fields\", type: \"bytes1\" },\n { name: \"name\", type: \"string\" },\n { name: \"version\", type: \"string\" },\n { name: \"chainId\", type: \"uint256\" },\n { name: \"verifyingContract\", type: \"address\" },\n { name: \"salt\", type: \"bytes32\" },\n { name: \"extensions\", type: \"uint256[]\" },\n ],\n stateMutability: \"view\",\n type: \"function\",\n },\n ] as const;\n\n // version() ABI(OpenZeppelin v4)\n const versionABI = [\n {\n inputs: [],\n name: \"version\",\n outputs: [{ name: \"\", type: \"string\" }],\n stateMutability: \"view\",\n type: \"function\",\n },\n ] as const;\n\n try {\n // 获取 token name\n const name = await client.readContract({\n address,\n abi: erc20ABI,\n functionName: \"name\",\n });\n\n // 尝试获取 version,优先使用 EIP-5267\n let version = \"1\"; // 默认版本\n try {\n const result = await client.readContract({\n address,\n abi: eip712DomainABI,\n functionName: \"eip712Domain\",\n });\n // eip712Domain 返回 [fields, name, version, chainId, verifyingContract, salt, extensions]\n version = result[2] as string; // version 是第 3 个元素(索引 2)\n } catch {\n // 回退到 version() 函数(OpenZeppelin v4)\n try {\n version = await client.readContract({\n address,\n abi: versionABI,\n functionName: \"version\",\n });\n } catch {\n // 如果两种方法都不可用,使用默认值 \"1\"\n console.log(` ℹ️ Using default version \"1\" for token ${address}`);\n }\n }\n\n return {\n name: name as string,\n version: version as string,\n };\n } catch (error) {\n console.error(`Error getting token info for ${address}:`, error);\n throw new Error(`Failed to get token info: ${error}`);\n }\n}\n\n","import type { Facilitator } from \"@wtflabs/x402-facilitator\";\nimport type { X402PaymentSchema, X402PaymentSchemaWithExtra } from \"@wtflabs/x402-schema\";\nimport type { PublicClient } from \"viem\";\nimport type {\n InitializeResult,\n SettleResult,\n VerifyResult,\n X402ServerConfig,\n} from \"./types\";\nimport { PaymentPayload, PaymentRequirements } from \"@wtflabs/x402/types\";\nimport { detectTokenPaymentMethods, getRecommendedPaymentMethod, getTokenInfo, type PaymentMethod } from \"./token-detection\";\n\n/**\n * X402Server 类\n * 集成 facilitator, schema 和 client,提供完整的服务端支付处理\n *\n * @example\n * ```typescript\n * import { X402Server } from \"@wtflabs/x402-server\";\n * import { Facilitator } from \"@wtflabs/x402-facilitator\";\n * import { X402PaymentSchema } from \"@wtflabs/x402-schema\";\n * import { createPublicClient, http } from \"viem\";\n * import { bscTestnet } from \"viem/chains\";\n *\n * const facilitator = new Facilitator({\n * recipientAddress: \"0x1234...\",\n * });\n *\n * const schema = new X402PaymentSchema({\n * scheme: \"exact\",\n * network: \"bsc-testnet\",\n * maxAmountRequired: \"100000\",\n * resource: \"http://localhost:3000/protected-resource\",\n * description: \"Access to protected resource\",\n * mimeType: \"application/json\",\n * payTo: \"0x1234...\",\n * maxTimeoutSeconds: 3600,\n * asset: \"0x5678...\",\n * });\n *\n * const client = createPublicClient({\n * chain: bscTestnet,\n * transport: http(),\n * });\n *\n * const server = new X402Server({\n * facilitator,\n * schema,\n * client,\n * });\n *\n * // 初始化和校验\n * await server.initialize();\n *\n * // 验证\n * const verifyResult = await server.verify();\n *\n * // 结算\n * const settleResult = await server.settle(paymentPayload, paymentRequirements);\n * ```\n */\nexport class X402Server {\n private facilitator: Facilitator;\n private schema: X402PaymentSchema;\n private client: PublicClient;\n private initialized: boolean = false;\n\n constructor(config: X402ServerConfig) {\n this.facilitator = config.facilitator;\n this.schema = config.schema;\n this.client = config.client;\n }\n\n /**\n * 初始化服务器\n * 初始化和校验 schema 等数据,对 schema 增加 facilitator 数据 extra: {relayer}\n * 并获取 token 的 name 和 version 信息\n */\n async initialize(): Promise<InitializeResult> {\n try {\n // 验证 schema\n this.schema.verify();\n\n // 获取 token 信息(name 和 version)并添加到 schema extra 中\n const schemaAsset = this.schema.get(\"asset\");\n if (schemaAsset) {\n try {\n const tokenInfo = await getTokenInfo(schemaAsset, this.client);\n this.schema.setExtra({\n relayer: this.facilitator.relayer,\n name: tokenInfo.name,\n version: tokenInfo.version,\n });\n console.log(`✅ Token info retrieved: ${tokenInfo.name} v${tokenInfo.version}`);\n } catch (error) {\n console.warn(`⚠️ Failed to get token info, signatures may fail:`, error);\n // 仍然设置 relayer,但没有 name 和 version\n this.schema.setExtra({\n relayer: this.facilitator.relayer,\n });\n }\n } else {\n // 如果没有 asset,只设置 relayer\n this.schema.setExtra({\n relayer: this.facilitator.relayer,\n });\n }\n\n // TODO 将_verify中的内容内置到initialize中\n const verifyResult = await this._verify();\n if (!verifyResult.success) {\n return {\n success: false,\n error: verifyResult.errors?.[0] || \"Verification failed\",\n };\n }\n\n this.initialized = true;\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Initialization failed\",\n };\n }\n }\n\n /**\n * 验证配置\n * 1. 验证 client network 是否和 schema 的 network 匹配\n * 2. 验证 facilitator recipientAddress 和 schema payTo 是否一致\n */\n async _verify(): Promise<VerifyResult> {\n\n const errors: string[] = [];\n\n try {\n // 1. 检测 token 对 permit 和 eip3009 的支持\n const schemaAsset = this.schema.get(\"asset\");\n if (schemaAsset) {\n const tokenCapabilities = await detectTokenPaymentMethods(\n schemaAsset,\n this.client\n );\n\n // 如果 schema 中不存在 paymentType,则根据 tokenCapabilities 自动确定\n // 优先级:eip3009 > permit2 > permit\n const currentPaymentType = this.schema.get(\"paymentType\");\n if (!currentPaymentType) {\n const recommendedMethod = getRecommendedPaymentMethod(tokenCapabilities);\n if (recommendedMethod) {\n this.schema.set(\"paymentType\", recommendedMethod);\n // console.log(`✅ Auto-selected payment method: ${recommendedMethod}`);\n } else {\n errors.push(\n `Token ${schemaAsset} does not support any advanced payment methods (permit, eip3009, permit2). Please specify paymentType manually.`\n );\n }\n } else {\n // 验证 schema 中指定的 paymentType 是否被 token 支持\n if (!tokenCapabilities.supportedMethods.includes(currentPaymentType)) {\n errors.push(\n `Token ${schemaAsset} does not support the specified payment method \"${currentPaymentType}\". Supported methods: ${tokenCapabilities.supportedMethods.join(\", \")}`\n );\n }\n }\n\n // 如果 token 不支持任何高级支付方法,给出警告\n if (tokenCapabilities.supportedMethods.length === 0) {\n errors.push(\n `Token ${schemaAsset} does not support any advanced payment methods (permit, eip3009, permit2)`\n );\n }\n }\n\n // 2. 检查 facilitator 的 /supported 是否包含此代币和链\n const clientChainId = this.client.chain?.id;\n const schemaNetwork = this.schema.get(\"network\");\n\n if (clientChainId && schemaAsset) {\n const facilitatorSupported = await this.facilitator.supported({\n chainId: clientChainId,\n tokenAddress: schemaAsset,\n });\n\n // console.log(\n // `Checking facilitator support for chainId ${clientChainId} and token ${schemaAsset}`\n // );\n\n // 检查 facilitator 是否支持当前的链和代币组合\n const isSupportedByFacilitator = facilitatorSupported.kinds.some(\n (kind) => {\n // 检查网络匹配\n const networkMatches = kind.network === schemaNetwork;\n\n // 检查资产匹配\n const assetsInKind = (kind.extra as any)?.assets || [];\n const assetMatches = assetsInKind.some(\n (asset: any) =>\n asset.address.toLowerCase() === schemaAsset.toLowerCase()\n );\n\n return networkMatches && assetMatches;\n }\n );\n\n if (!isSupportedByFacilitator) {\n errors.push(\n `Facilitator does not support token ${schemaAsset} on network ${schemaNetwork} (chainId: ${clientChainId})`\n );\n } else {\n // console.log(`✅ Facilitator supports this configuration`);\n }\n\n // 3. 检查当前配置的链是否在 /supported 中\n const chainSupported = facilitatorSupported.kinds.some(\n (kind) => kind.network === schemaNetwork\n );\n\n if (!chainSupported) {\n errors.push(\n `Facilitator does not support network ${schemaNetwork} (chainId: ${clientChainId})`\n );\n }\n }\n\n // 4. 验证 network 匹配\n // 简化的网络验证逻辑\n // 实际应用中可能需要更复杂的网络匹配逻辑\n if (clientChainId) {\n // 检查 schema network 是否包含 chainId\n const networkValid = this.validateNetwork(\n schemaNetwork,\n clientChainId,\n );\n if (!networkValid) {\n errors.push(\n `Network mismatch: client chainId ${clientChainId} does not match schema network ${schemaNetwork}`,\n );\n }\n }\n\n // 2. 验证 payTo 和 recipientAddress 匹配\n const schemaPayTo = this.schema.get(\"payTo\");\n const facilitatorRecipientAddress =\n this.facilitator.recipientAddress;\n\n if (\n schemaPayTo.toLowerCase() !==\n facilitatorRecipientAddress.toLowerCase()\n ) {\n errors.push(\n `Address mismatch: schema payTo ${schemaPayTo} does not match facilitator recipientAddress ${facilitatorRecipientAddress}`,\n );\n }\n\n // 3. 调用 facilitator verify(如果有支付负载的话)\n // 这里暂时只做配置验证,实际支付验证在处理支付时进行\n\n return {\n success: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n };\n } catch (error) {\n errors.push(\n error instanceof Error\n ? error.message\n : \"Unknown verification error\",\n );\n return {\n success: false,\n errors,\n };\n }\n }\n\n /**\n * 结算支付\n * @param paymentPayload 支付负载\n * @param paymentRequirements 支付要求\n */\n async settle(\n paymentPayload: any,\n paymentRequirements: any,\n ): Promise<SettleResult> {\n if (!this.initialized) {\n return {\n success: false,\n error:\n \"Server not initialized. Please call initialize() first.\",\n };\n }\n\n try {\n // 调用 facilitator 进行结算\n const result = await this.facilitator.settle(\n paymentPayload,\n paymentRequirements,\n );\n\n if (!result.success) {\n return {\n success: false,\n error: result.error || result.errorMessage,\n };\n }\n\n return {\n success: true,\n transactionHash: result.transactionHash,\n };\n } catch (error) {\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Settlement failed\",\n };\n }\n }\n\n /**\n * 验证支付负载\n * @param paymentPayload 支付负载\n * @param paymentRequirements 支付要求\n */\n async verifyPayment(\n paymentPayload: any,\n paymentRequirements: any,\n ): Promise<{\n success: boolean;\n data?: string;\n error?: string;\n }> {\n if (!this.initialized) {\n return {\n success: false,\n error:\n \"Server not initialized. Please call initialize() first.\",\n };\n }\n\n try {\n const result = await this.facilitator.verify(\n paymentPayload,\n paymentRequirements,\n );\n\n return {\n success: result.success,\n data: result.payer,\n error: result.error || result.errorMessage,\n };\n } catch (error) {\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Payment verification failed\",\n };\n }\n }\n\n /**\n * 获取 facilitator\n */\n getFacilitator(): Facilitator {\n return this.facilitator;\n }\n\n /**\n * 获取 schema\n */\n getSchema(): X402PaymentSchema {\n return this.schema;\n }\n\n /**\n * 获取 client\n */\n getClient(): PublicClient {\n return this.client;\n }\n\n /**\n * 检查是否已初始化\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n /**\n * 验证网络是否匹配\n * @param schemaNetwork schema 中的 network\n * @param clientChainId client 的 chainId\n * @returns 是否匹配\n */\n private validateNetwork(\n schemaNetwork: string,\n clientChainId: number,\n ): boolean {\n // 如果是 eip155: 格式\n if (schemaNetwork.startsWith(\"eip155:\")) {\n const chainId = parseInt(schemaNetwork.split(\":\")[1] || \"0\");\n return chainId === clientChainId;\n }\n\n // 常见网络名称映射\n const networkMap: Record<string, number> = {\n \"ethereum\": 1,\n \"goerli\": 5,\n \"sepolia\": 11155111,\n \"base\": 8453,\n \"base-sepolia\": 84532,\n \"bsc\": 56,\n \"bsc-testnet\": 97,\n \"polygon\": 137,\n \"polygon-mumbai\": 80001,\n \"arbitrum\": 42161,\n \"arbitrum-goerli\": 421613,\n \"optimism\": 10,\n \"optimism-goerli\": 420,\n \"avalanche\": 43114,\n \"avalanche-fuji\": 43113,\n };\n\n const expectedChainId = networkMap[schemaNetwork];\n if (expectedChainId !== undefined) {\n return expectedChainId === clientChainId;\n }\n\n // 如果无法匹配,返回 true(宽松验证)\n return true;\n }\n\n /**\n * 解析支付 header\n * @param paymentHeaderBase64 Base64 编码的支付 header\n * @returns 解析结果,成功时返回 paymentPayload 和 paymentRequirements,失败时返回服务端的支付要求\n */\n parsePaymentHeader(\n paymentHeaderBase64: string,\n ):\n | {\n success: true;\n data: {\n paymentPayload: PaymentPayload;\n paymentRequirements: PaymentRequirements;\n };\n }\n | { success: false; data: PaymentRequirements; error: string } {\n // 获取服务端的支付要求(从 schema 转换)\n const paymentRequirements = this.schema.toJSON() as PaymentRequirements;\n\n // 检查是否有 payment header\n if (!paymentHeaderBase64) {\n return {\n success: false,\n data: paymentRequirements,\n error: \"No X-PAYMENT header\",\n };\n }\n\n // 解码 payment header\n let paymentPayload: PaymentPayload;\n try {\n const paymentHeaderJson = Buffer.from(\n paymentHeaderBase64,\n \"base64\",\n ).toString(\"utf-8\");\n paymentPayload = JSON.parse(paymentHeaderJson) as PaymentPayload;\n } catch (err) {\n return {\n success: false,\n data: paymentRequirements,\n error: \"Invalid payment header format\",\n };\n }\n\n // 验证支付数据与服务端 schema 是否一致\n const validationError = this.validatePaymentPayload(\n paymentPayload,\n paymentRequirements,\n );\n if (validationError) {\n return {\n success: false,\n data: paymentRequirements,\n error: validationError,\n };\n }\n\n // 返回成功结果\n return {\n success: true,\n data: {\n paymentPayload,\n paymentRequirements,\n },\n };\n }\n\n /**\n * 验证客户端的支付数据是否与服务端要求一致\n * @param paymentPayload 客户端的支付负载\n * @param paymentRequirements 服务端的支付要求\n * @returns 错误信息,如果验证通过则返回 null\n */\n private validatePaymentPayload(\n paymentPayload: PaymentPayload,\n paymentRequirements: PaymentRequirements,\n ): string | null {\n // 1. 验证 scheme\n if (paymentPayload.scheme !== paymentRequirements.scheme) {\n return `Scheme mismatch: expected '${paymentRequirements.scheme}', got '${paymentPayload.scheme}'`;\n }\n\n // 2. 验证 network\n if (paymentPayload.network !== paymentRequirements.network) {\n return `Network mismatch: expected '${paymentRequirements.network}', got '${paymentPayload.network}'`;\n }\n\n // 3. 验证支付金额(从 payload 中提取)\n if (paymentPayload.payload) {\n const authorization = (paymentPayload.payload as any).authorization;\n if (authorization?.value) {\n const paymentAmount = BigInt(authorization.value);\n const maxAmount = BigInt(paymentRequirements.maxAmountRequired);\n\n if (paymentAmount !== maxAmount) {\n return `Payment amount error ${paymentAmount} !== ${maxAmount}`;\n }\n }\n\n // 4. 验证 payTo 地址\n if (authorization?.to) {\n const expectedPayTo = paymentRequirements.payTo.toLowerCase();\n const actualPayTo = authorization.to.toLowerCase();\n\n if (actualPayTo !== expectedPayTo && actualPayTo !== paymentRequirements.extra?.relayer?.toLowerCase()) {\n return `PayTo address mismatch: expected '${expectedPayTo}', got '${actualPayTo}'`;\n }\n }\n\n // 5. 验证 asset 地址(如果是 permit 或 permit2)\n const authorizationType = (paymentPayload.payload as any)\n .authorizationType;\n if (\n authorizationType === \"permit\" ||\n authorizationType === \"permit2\" ||\n authorizationType === \"eip3009\"\n ) {\n // 对于 permit/permit2,asset 通常在 schema 中,需要与实际调用的合约匹配\n // 这里可以添加更多验证逻辑\n }\n }\n\n // 验证通过\n return null;\n }\n}\n\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wtflabs/x402-server",
|
|
3
|
+
"version": "0.0.1-beta.2",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"module": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"x402",
|
|
9
|
+
"payment",
|
|
10
|
+
"server"
|
|
11
|
+
],
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"description": "X402 Payment Server integration",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^22.13.4",
|
|
16
|
+
"tsup": "^8.4.0",
|
|
17
|
+
"tsx": "^4.19.2",
|
|
18
|
+
"typescript": "^5.7.3",
|
|
19
|
+
"vitest": "^3.0.5"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@wtflabs/x402": "^0.0.1-beta.4",
|
|
23
|
+
"viem": "^2.21.26",
|
|
24
|
+
"@wtflabs/x402-schema": "0.0.1-beta.2",
|
|
25
|
+
"@wtflabs/x402-facilitator": "0.0.1-beta.2"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": {
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
31
|
+
"default": "./dist/index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"require": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"watch": "tsc --watch",
|
|
47
|
+
"format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"",
|
|
48
|
+
"format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"",
|
|
49
|
+
"lint": "eslint . --ext .ts --fix",
|
|
50
|
+
"lint:check": "eslint . --ext .ts"
|
|
51
|
+
}
|
|
52
|
+
}
|