@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/dist/index.mjs ADDED
@@ -0,0 +1,602 @@
1
+ // src/token-detection.ts
2
+ var EIP3009_SIGNATURES = ["0xe3ee160e", "0xcf092995"];
3
+ var EIP2612_PERMIT = "0xd505accf";
4
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
5
+ var EIP1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
6
+ var EIP1822_IMPLEMENTATION_SLOT = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3";
7
+ async function getImplementationAddress(client, proxyAddress) {
8
+ try {
9
+ try {
10
+ const implSlotData = await client.getStorageAt({
11
+ address: proxyAddress,
12
+ slot: EIP1967_IMPLEMENTATION_SLOT
13
+ });
14
+ if (implSlotData && implSlotData !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
15
+ const implAddress = `0x${implSlotData.slice(-40)}`;
16
+ if (implAddress !== "0x0000000000000000000000000000000000000000") {
17
+ console.log(` \u{1F4E6} Detected EIP-1967 proxy, implementation: ${implAddress}`);
18
+ return implAddress;
19
+ }
20
+ }
21
+ } catch {
22
+ }
23
+ try {
24
+ const uupsSlotData = await client.getStorageAt({
25
+ address: proxyAddress,
26
+ slot: EIP1822_IMPLEMENTATION_SLOT
27
+ });
28
+ if (uupsSlotData && uupsSlotData !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
29
+ const implAddress = `0x${uupsSlotData.slice(-40)}`;
30
+ if (implAddress !== "0x0000000000000000000000000000000000000000") {
31
+ console.log(` \u{1F4E6} Detected EIP-1822 UUPS proxy, implementation: ${implAddress}`);
32
+ return implAddress;
33
+ }
34
+ }
35
+ } catch {
36
+ }
37
+ try {
38
+ const implABI = [
39
+ {
40
+ inputs: [],
41
+ name: "implementation",
42
+ outputs: [{ name: "", type: "address" }],
43
+ stateMutability: "view",
44
+ type: "function"
45
+ }
46
+ ];
47
+ const implAddress = await client.readContract({
48
+ address: proxyAddress,
49
+ abi: implABI,
50
+ functionName: "implementation"
51
+ });
52
+ if (implAddress && implAddress !== "0x0000000000000000000000000000000000000000") {
53
+ console.log(` \u{1F4E6} Detected proxy via implementation(), implementation: ${implAddress}`);
54
+ return implAddress;
55
+ }
56
+ } catch {
57
+ }
58
+ return null;
59
+ } catch (error) {
60
+ console.error("Error detecting proxy implementation:", error);
61
+ return null;
62
+ }
63
+ }
64
+ async function hasMethod(client, tokenAddress, methodSelector) {
65
+ try {
66
+ const code = await client.getBytecode({ address: tokenAddress });
67
+ if (!code) return false;
68
+ const hasMethodInProxy = code.toLowerCase().includes(methodSelector.slice(2).toLowerCase());
69
+ if (hasMethodInProxy) {
70
+ return true;
71
+ }
72
+ const implAddress = await getImplementationAddress(client, tokenAddress);
73
+ if (implAddress) {
74
+ const implCode = await client.getBytecode({ address: implAddress });
75
+ if (implCode) {
76
+ const hasMethodInImpl = implCode.toLowerCase().includes(methodSelector.slice(2).toLowerCase());
77
+ if (hasMethodInImpl) {
78
+ console.log(` \u2705 Method ${methodSelector} found in implementation contract`);
79
+ }
80
+ return hasMethodInImpl;
81
+ }
82
+ }
83
+ return false;
84
+ } catch (error) {
85
+ console.error(`Error checking method ${methodSelector}:`, error);
86
+ return false;
87
+ }
88
+ }
89
+ async function hasAnyMethod(client, tokenAddress, methodSelectors) {
90
+ try {
91
+ const code = await client.getBytecode({ address: tokenAddress });
92
+ if (!code) return false;
93
+ const codeLower = code.toLowerCase();
94
+ const hasMethodInProxy = methodSelectors.some(
95
+ (selector) => codeLower.includes(selector.slice(2).toLowerCase())
96
+ );
97
+ if (hasMethodInProxy) {
98
+ return true;
99
+ }
100
+ const implAddress = await getImplementationAddress(client, tokenAddress);
101
+ if (implAddress) {
102
+ const implCode = await client.getBytecode({ address: implAddress });
103
+ if (implCode) {
104
+ const implCodeLower = implCode.toLowerCase();
105
+ const hasMethodInImpl = methodSelectors.some(
106
+ (selector) => implCodeLower.includes(selector.slice(2).toLowerCase())
107
+ );
108
+ if (hasMethodInImpl) {
109
+ console.log(` \u2705 Method(s) found in implementation contract`);
110
+ }
111
+ return hasMethodInImpl;
112
+ }
113
+ }
114
+ return false;
115
+ } catch (error) {
116
+ console.error(`Error checking methods ${methodSelectors.join(", ")}:`, error);
117
+ return false;
118
+ }
119
+ }
120
+ async function checkPermit2Support(client) {
121
+ try {
122
+ const permit2Code = await client.getBytecode({ address: PERMIT2_ADDRESS });
123
+ if (!permit2Code) return false;
124
+ return true;
125
+ } catch (error) {
126
+ console.error("Error checking Permit2 support:", error);
127
+ return false;
128
+ }
129
+ }
130
+ async function detectTokenPaymentMethods(tokenAddress, client) {
131
+ const address = tokenAddress.toLowerCase();
132
+ console.log(`\u{1F50D} Detecting payment methods for token ${address}...`);
133
+ const [hasEIP3009, hasPermit, hasPermit2Approval] = await Promise.all([
134
+ hasAnyMethod(client, address, EIP3009_SIGNATURES),
135
+ hasMethod(client, address, EIP2612_PERMIT),
136
+ checkPermit2Support(client)
137
+ ]);
138
+ const supportedMethods = [];
139
+ if (hasEIP3009) {
140
+ supportedMethods.push("eip3009");
141
+ console.log(" \u2705 EIP-3009 (transferWithAuthorization) detected");
142
+ }
143
+ if (hasPermit) {
144
+ supportedMethods.push("permit");
145
+ console.log(" \u2705 EIP-2612 (permit) detected");
146
+ }
147
+ if (hasPermit2Approval) {
148
+ supportedMethods.push("permit2");
149
+ supportedMethods.push("permit2-witness");
150
+ console.log(" \u2705 Permit2 support available (universal)");
151
+ }
152
+ if (supportedMethods.length === 0) {
153
+ console.log(" \u26A0\uFE0F No advanced payment methods detected (standard ERC-20 only)");
154
+ }
155
+ return {
156
+ address,
157
+ supportedMethods,
158
+ details: {
159
+ hasEIP3009,
160
+ hasPermit,
161
+ hasPermit2Approval
162
+ }
163
+ };
164
+ }
165
+ function getRecommendedPaymentMethod(capabilities) {
166
+ const { supportedMethods } = capabilities;
167
+ if (supportedMethods.includes("eip3009")) return "eip3009";
168
+ if (supportedMethods.includes("permit")) return "permit";
169
+ if (supportedMethods.includes("permit2") || supportedMethods.includes("permit2-witness")) {
170
+ return "permit2";
171
+ }
172
+ return null;
173
+ }
174
+ async function getTokenInfo(tokenAddress, client) {
175
+ const address = tokenAddress.toLowerCase();
176
+ const erc20ABI = [
177
+ {
178
+ inputs: [],
179
+ name: "name",
180
+ outputs: [{ name: "", type: "string" }],
181
+ stateMutability: "view",
182
+ type: "function"
183
+ }
184
+ ];
185
+ const eip712DomainABI = [
186
+ {
187
+ inputs: [],
188
+ name: "eip712Domain",
189
+ outputs: [
190
+ { name: "fields", type: "bytes1" },
191
+ { name: "name", type: "string" },
192
+ { name: "version", type: "string" },
193
+ { name: "chainId", type: "uint256" },
194
+ { name: "verifyingContract", type: "address" },
195
+ { name: "salt", type: "bytes32" },
196
+ { name: "extensions", type: "uint256[]" }
197
+ ],
198
+ stateMutability: "view",
199
+ type: "function"
200
+ }
201
+ ];
202
+ const versionABI = [
203
+ {
204
+ inputs: [],
205
+ name: "version",
206
+ outputs: [{ name: "", type: "string" }],
207
+ stateMutability: "view",
208
+ type: "function"
209
+ }
210
+ ];
211
+ try {
212
+ const implAddress = await getImplementationAddress(client, address);
213
+ if (implAddress) {
214
+ console.log(` \u{1F4E6} Reading token info from proxy, actual calls will be delegated to implementation`);
215
+ }
216
+ const name = await client.readContract({
217
+ address,
218
+ abi: erc20ABI,
219
+ functionName: "name"
220
+ });
221
+ let version = "1";
222
+ try {
223
+ const result = await client.readContract({
224
+ address,
225
+ abi: eip712DomainABI,
226
+ functionName: "eip712Domain"
227
+ });
228
+ version = result[2];
229
+ } catch {
230
+ try {
231
+ version = await client.readContract({
232
+ address,
233
+ abi: versionABI,
234
+ functionName: "version"
235
+ });
236
+ } catch {
237
+ console.log(` \u2139\uFE0F Using default version "1" for token ${address}`);
238
+ }
239
+ }
240
+ return {
241
+ name,
242
+ version
243
+ };
244
+ } catch (error) {
245
+ console.error(`Error getting token info for ${address}:`, error);
246
+ throw new Error(`Failed to get token info: ${error}`);
247
+ }
248
+ }
249
+
250
+ // src/server.ts
251
+ var X402Server = class {
252
+ constructor(config) {
253
+ this.initialized = false;
254
+ this.facilitator = config.facilitator;
255
+ this.schema = config.schema;
256
+ this.client = config.client;
257
+ this._initialize();
258
+ }
259
+ async _initialize() {
260
+ try {
261
+ this.schema.verify();
262
+ const schemaAsset = this.schema.get("asset");
263
+ if (schemaAsset) {
264
+ try {
265
+ const tokenInfo = await getTokenInfo(schemaAsset, this.client);
266
+ this.schema.setExtra({
267
+ name: tokenInfo.name,
268
+ version: tokenInfo.version
269
+ });
270
+ console.log(`\u2705 Token info retrieved: ${tokenInfo.name} v${tokenInfo.version}`);
271
+ } catch (error) {
272
+ console.warn(`\u26A0\uFE0F Failed to get token info, signatures may fail:`, error);
273
+ }
274
+ }
275
+ const verifyResult = await this._verify();
276
+ if (!verifyResult.success) {
277
+ return {
278
+ success: false,
279
+ error: verifyResult.errors?.[0] || "Verification failed"
280
+ };
281
+ }
282
+ this.initialized = true;
283
+ return { success: true };
284
+ } catch (error) {
285
+ return {
286
+ success: false,
287
+ error: error instanceof Error ? error.message : "Initialization failed"
288
+ };
289
+ }
290
+ }
291
+ /**
292
+ * 验证配置
293
+ * 1. 验证 client network 是否和 schema 的 network 匹配
294
+ * 2. 验证 facilitator recipientAddress 和 schema payTo 是否一致
295
+ */
296
+ async _verify() {
297
+ const errors = [];
298
+ try {
299
+ const schemaAsset = this.schema.get("asset");
300
+ if (schemaAsset) {
301
+ const tokenCapabilities = await detectTokenPaymentMethods(
302
+ schemaAsset,
303
+ this.client
304
+ );
305
+ const currentPaymentType = this.schema.get("paymentType");
306
+ if (!currentPaymentType) {
307
+ const recommendedMethod = getRecommendedPaymentMethod(tokenCapabilities);
308
+ if (recommendedMethod) {
309
+ this.schema.set("paymentType", recommendedMethod);
310
+ } else {
311
+ errors.push(
312
+ `Token ${schemaAsset} does not support any advanced payment methods (permit, eip3009, permit2). Please specify paymentType manually.`
313
+ );
314
+ }
315
+ } else {
316
+ if (!tokenCapabilities.supportedMethods.includes(currentPaymentType)) {
317
+ errors.push(
318
+ `Token ${schemaAsset} does not support the specified payment method "${currentPaymentType}". Supported methods: ${tokenCapabilities.supportedMethods.join(", ")}`
319
+ );
320
+ }
321
+ }
322
+ if (tokenCapabilities.supportedMethods.length === 0) {
323
+ errors.push(
324
+ `Token ${schemaAsset} does not support any advanced payment methods (permit, eip3009, permit2)`
325
+ );
326
+ }
327
+ }
328
+ const clientChainId = this.client.chain?.id;
329
+ const schemaNetwork = this.schema.get("network");
330
+ if (clientChainId && schemaAsset) {
331
+ const facilitatorSupported = await this.facilitator.supported({
332
+ chainId: clientChainId,
333
+ tokenAddress: schemaAsset
334
+ });
335
+ const isSupportedByFacilitator = facilitatorSupported.kinds.some(
336
+ (kind) => {
337
+ const networkMatches = kind.network === schemaNetwork;
338
+ const assetsInKind = kind.extra?.assets || [];
339
+ const assetMatches = assetsInKind.some(
340
+ (asset) => asset.address.toLowerCase() === schemaAsset.toLowerCase()
341
+ );
342
+ return networkMatches && assetMatches;
343
+ }
344
+ );
345
+ if (!isSupportedByFacilitator) {
346
+ errors.push(
347
+ `Facilitator does not support token ${schemaAsset} on network ${schemaNetwork} (chainId: ${clientChainId})`
348
+ );
349
+ } else {
350
+ }
351
+ const chainSupported = facilitatorSupported.kinds.some(
352
+ (kind) => kind.network === schemaNetwork
353
+ );
354
+ if (!chainSupported) {
355
+ errors.push(
356
+ `Facilitator does not support network ${schemaNetwork} (chainId: ${clientChainId})`
357
+ );
358
+ }
359
+ }
360
+ if (clientChainId) {
361
+ const networkValid = this.validateNetwork(
362
+ schemaNetwork,
363
+ clientChainId
364
+ );
365
+ if (!networkValid) {
366
+ errors.push(
367
+ `Network mismatch: client chainId ${clientChainId} does not match schema network ${schemaNetwork}`
368
+ );
369
+ }
370
+ }
371
+ const schemaPayTo = this.schema.get("payTo");
372
+ const facilitatorRecipientAddress = this.facilitator.recipientAddress;
373
+ if (schemaPayTo.toLowerCase() !== facilitatorRecipientAddress.toLowerCase()) {
374
+ errors.push(
375
+ `Address mismatch: schema payTo ${schemaPayTo} does not match facilitator recipientAddress ${facilitatorRecipientAddress}`
376
+ );
377
+ }
378
+ return {
379
+ success: errors.length === 0,
380
+ errors: errors.length > 0 ? errors : void 0
381
+ };
382
+ } catch (error) {
383
+ errors.push(
384
+ error instanceof Error ? error.message : "Unknown verification error"
385
+ );
386
+ return {
387
+ success: false,
388
+ errors
389
+ };
390
+ }
391
+ }
392
+ /**
393
+ * 结算支付
394
+ * @param paymentPayload 支付负载
395
+ * @param paymentRequirements 支付要求
396
+ */
397
+ async settle(paymentPayload, paymentRequirements) {
398
+ if (!this.initialized) {
399
+ return {
400
+ success: false,
401
+ error: "Server not initialized. Please call initialize() first."
402
+ };
403
+ }
404
+ try {
405
+ const result = await this.facilitator.settle(
406
+ paymentPayload,
407
+ paymentRequirements
408
+ );
409
+ if (!result.success) {
410
+ return {
411
+ success: false,
412
+ error: result.error || result.errorMessage
413
+ };
414
+ }
415
+ return {
416
+ success: true,
417
+ transaction: result.transaction
418
+ };
419
+ } catch (error) {
420
+ return {
421
+ success: false,
422
+ error: error instanceof Error ? error.message : "Settlement failed"
423
+ };
424
+ }
425
+ }
426
+ /**
427
+ * 验证支付负载
428
+ * @param paymentPayload 支付负载
429
+ * @param paymentRequirements 支付要求
430
+ */
431
+ async verify(paymentPayload, paymentRequirements) {
432
+ if (!this.initialized) {
433
+ return {
434
+ success: false,
435
+ error: "Server not initialized. Please call initialize() first."
436
+ };
437
+ }
438
+ try {
439
+ const result = await this.facilitator.verify(
440
+ paymentPayload,
441
+ paymentRequirements
442
+ );
443
+ return {
444
+ success: result.success,
445
+ data: result.payer,
446
+ error: result.error || result.errorMessage
447
+ };
448
+ } catch (error) {
449
+ return {
450
+ success: false,
451
+ error: error instanceof Error ? error.message : "Payment verification failed"
452
+ };
453
+ }
454
+ }
455
+ /**
456
+ * 获取 facilitator
457
+ */
458
+ getFacilitator() {
459
+ return this.facilitator;
460
+ }
461
+ /**
462
+ * 获取 schema
463
+ */
464
+ getSchema() {
465
+ return this.schema;
466
+ }
467
+ /**
468
+ * 获取 client
469
+ */
470
+ getClient() {
471
+ return this.client;
472
+ }
473
+ /**
474
+ * 检查是否已初始化
475
+ */
476
+ isInitialized() {
477
+ return this.initialized;
478
+ }
479
+ /**
480
+ * 验证网络是否匹配
481
+ * @param schemaNetwork schema 中的 network
482
+ * @param clientChainId client 的 chainId
483
+ * @returns 是否匹配
484
+ */
485
+ validateNetwork(schemaNetwork, clientChainId) {
486
+ if (schemaNetwork.startsWith("eip155:")) {
487
+ const chainId = parseInt(schemaNetwork.split(":")[1] || "0");
488
+ return chainId === clientChainId;
489
+ }
490
+ const networkMap = {
491
+ "ethereum": 1,
492
+ "goerli": 5,
493
+ "sepolia": 11155111,
494
+ "base": 8453,
495
+ "base-sepolia": 84532,
496
+ "bsc": 56,
497
+ "bsc-testnet": 97,
498
+ "polygon": 137,
499
+ "polygon-mumbai": 80001,
500
+ "arbitrum": 42161,
501
+ "arbitrum-goerli": 421613,
502
+ "optimism": 10,
503
+ "optimism-goerli": 420,
504
+ "avalanche": 43114,
505
+ "avalanche-fuji": 43113
506
+ };
507
+ const expectedChainId = networkMap[schemaNetwork];
508
+ if (expectedChainId !== void 0) {
509
+ return expectedChainId === clientChainId;
510
+ }
511
+ return true;
512
+ }
513
+ /**
514
+ * 解析支付 header
515
+ * @param paymentHeaderBase64 Base64 编码的支付 header
516
+ * @returns 解析结果,成功时返回 paymentPayload 和 paymentRequirements,失败时返回服务端的支付要求
517
+ */
518
+ parsePaymentHeader(paymentHeaderBase64) {
519
+ const paymentRequirements = this.schema.toJSON();
520
+ if (!paymentHeaderBase64) {
521
+ return {
522
+ success: false,
523
+ data: paymentRequirements,
524
+ error: "No X-PAYMENT header"
525
+ };
526
+ }
527
+ let paymentPayload;
528
+ try {
529
+ const paymentHeaderJson = Buffer.from(
530
+ paymentHeaderBase64,
531
+ "base64"
532
+ ).toString("utf-8");
533
+ paymentPayload = JSON.parse(paymentHeaderJson);
534
+ } catch (err) {
535
+ return {
536
+ success: false,
537
+ data: paymentRequirements,
538
+ error: "Invalid payment header format"
539
+ };
540
+ }
541
+ const validationError = this.validatePaymentPayload(
542
+ paymentPayload,
543
+ paymentRequirements
544
+ );
545
+ if (validationError) {
546
+ return {
547
+ success: false,
548
+ data: paymentRequirements,
549
+ error: validationError
550
+ };
551
+ }
552
+ return {
553
+ success: true,
554
+ data: {
555
+ paymentPayload,
556
+ paymentRequirements
557
+ }
558
+ };
559
+ }
560
+ /**
561
+ * 验证客户端的支付数据是否与服务端要求一致
562
+ * @param paymentPayload 客户端的支付负载
563
+ * @param paymentRequirements 服务端的支付要求
564
+ * @returns 错误信息,如果验证通过则返回 null
565
+ */
566
+ validatePaymentPayload(paymentPayload, paymentRequirements) {
567
+ if (paymentPayload.scheme !== paymentRequirements.scheme) {
568
+ return `Scheme mismatch: expected '${paymentRequirements.scheme}', got '${paymentPayload.scheme}'`;
569
+ }
570
+ if (paymentPayload.network !== paymentRequirements.network) {
571
+ return `Network mismatch: expected '${paymentRequirements.network}', got '${paymentPayload.network}'`;
572
+ }
573
+ if (paymentPayload.payload) {
574
+ const authorization = paymentPayload.payload.authorization;
575
+ if (authorization?.value) {
576
+ const paymentAmount = BigInt(authorization.value);
577
+ const maxAmount = BigInt(paymentRequirements.maxAmountRequired);
578
+ if (paymentAmount !== maxAmount) {
579
+ return `Payment amount error ${paymentAmount} !== ${maxAmount}`;
580
+ }
581
+ }
582
+ if (authorization?.to) {
583
+ const expectedPayTo = paymentRequirements.payTo.toLowerCase();
584
+ const actualPayTo = authorization.to.toLowerCase();
585
+ if (actualPayTo !== expectedPayTo) {
586
+ return `PayTo address mismatch: expected '${expectedPayTo}', got '${actualPayTo}'`;
587
+ }
588
+ }
589
+ const authorizationType = paymentPayload.payload.authorizationType;
590
+ if (authorizationType === "permit" || authorizationType === "permit2" || authorizationType === "eip3009") {
591
+ }
592
+ }
593
+ return null;
594
+ }
595
+ };
596
+ export {
597
+ X402Server,
598
+ detectTokenPaymentMethods,
599
+ getRecommendedPaymentMethod,
600
+ getTokenInfo
601
+ };
602
+ //# sourceMappingURL=index.mjs.map