@wtflabs/x402-server 0.0.1-beta.6 → 0.0.1-beta.9

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 DELETED
@@ -1,610 +0,0 @@
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
- relayer: this.facilitator.relayer,
268
- name: tokenInfo.name,
269
- version: tokenInfo.version
270
- });
271
- console.log(`\u2705 Token info retrieved: ${tokenInfo.name} v${tokenInfo.version}`);
272
- } catch (error) {
273
- console.warn(`\u26A0\uFE0F Failed to get token info, signatures may fail:`, error);
274
- this.schema.setExtra({
275
- relayer: this.facilitator.relayer
276
- });
277
- }
278
- } else {
279
- this.schema.setExtra({
280
- relayer: this.facilitator.relayer
281
- });
282
- }
283
- const verifyResult = await this._verify();
284
- if (!verifyResult.success) {
285
- return {
286
- success: false,
287
- error: verifyResult.errors?.[0] || "Verification failed"
288
- };
289
- }
290
- this.initialized = true;
291
- return { success: true };
292
- } catch (error) {
293
- return {
294
- success: false,
295
- error: error instanceof Error ? error.message : "Initialization failed"
296
- };
297
- }
298
- }
299
- /**
300
- * 验证配置
301
- * 1. 验证 client network 是否和 schema 的 network 匹配
302
- * 2. 验证 facilitator recipientAddress 和 schema payTo 是否一致
303
- */
304
- async _verify() {
305
- const errors = [];
306
- try {
307
- const schemaAsset = this.schema.get("asset");
308
- if (schemaAsset) {
309
- const tokenCapabilities = await detectTokenPaymentMethods(
310
- schemaAsset,
311
- this.client
312
- );
313
- const currentPaymentType = this.schema.get("paymentType");
314
- if (!currentPaymentType) {
315
- const recommendedMethod = getRecommendedPaymentMethod(tokenCapabilities);
316
- if (recommendedMethod) {
317
- this.schema.set("paymentType", recommendedMethod);
318
- } else {
319
- errors.push(
320
- `Token ${schemaAsset} does not support any advanced payment methods (permit, eip3009, permit2). Please specify paymentType manually.`
321
- );
322
- }
323
- } else {
324
- if (!tokenCapabilities.supportedMethods.includes(currentPaymentType)) {
325
- errors.push(
326
- `Token ${schemaAsset} does not support the specified payment method "${currentPaymentType}". Supported methods: ${tokenCapabilities.supportedMethods.join(", ")}`
327
- );
328
- }
329
- }
330
- if (tokenCapabilities.supportedMethods.length === 0) {
331
- errors.push(
332
- `Token ${schemaAsset} does not support any advanced payment methods (permit, eip3009, permit2)`
333
- );
334
- }
335
- }
336
- const clientChainId = this.client.chain?.id;
337
- const schemaNetwork = this.schema.get("network");
338
- if (clientChainId && schemaAsset) {
339
- const facilitatorSupported = await this.facilitator.supported({
340
- chainId: clientChainId,
341
- tokenAddress: schemaAsset
342
- });
343
- const isSupportedByFacilitator = facilitatorSupported.kinds.some(
344
- (kind) => {
345
- const networkMatches = kind.network === schemaNetwork;
346
- const assetsInKind = kind.extra?.assets || [];
347
- const assetMatches = assetsInKind.some(
348
- (asset) => asset.address.toLowerCase() === schemaAsset.toLowerCase()
349
- );
350
- return networkMatches && assetMatches;
351
- }
352
- );
353
- if (!isSupportedByFacilitator) {
354
- errors.push(
355
- `Facilitator does not support token ${schemaAsset} on network ${schemaNetwork} (chainId: ${clientChainId})`
356
- );
357
- } else {
358
- }
359
- const chainSupported = facilitatorSupported.kinds.some(
360
- (kind) => kind.network === schemaNetwork
361
- );
362
- if (!chainSupported) {
363
- errors.push(
364
- `Facilitator does not support network ${schemaNetwork} (chainId: ${clientChainId})`
365
- );
366
- }
367
- }
368
- if (clientChainId) {
369
- const networkValid = this.validateNetwork(
370
- schemaNetwork,
371
- clientChainId
372
- );
373
- if (!networkValid) {
374
- errors.push(
375
- `Network mismatch: client chainId ${clientChainId} does not match schema network ${schemaNetwork}`
376
- );
377
- }
378
- }
379
- const schemaPayTo = this.schema.get("payTo");
380
- const facilitatorRecipientAddress = this.facilitator.recipientAddress;
381
- if (schemaPayTo.toLowerCase() !== facilitatorRecipientAddress.toLowerCase()) {
382
- errors.push(
383
- `Address mismatch: schema payTo ${schemaPayTo} does not match facilitator recipientAddress ${facilitatorRecipientAddress}`
384
- );
385
- }
386
- return {
387
- success: errors.length === 0,
388
- errors: errors.length > 0 ? errors : void 0
389
- };
390
- } catch (error) {
391
- errors.push(
392
- error instanceof Error ? error.message : "Unknown verification error"
393
- );
394
- return {
395
- success: false,
396
- errors
397
- };
398
- }
399
- }
400
- /**
401
- * 结算支付
402
- * @param paymentPayload 支付负载
403
- * @param paymentRequirements 支付要求
404
- */
405
- async settle(paymentPayload, paymentRequirements) {
406
- if (!this.initialized) {
407
- return {
408
- success: false,
409
- error: "Server not initialized. Please call initialize() first."
410
- };
411
- }
412
- try {
413
- const result = await this.facilitator.settle(
414
- paymentPayload,
415
- paymentRequirements
416
- );
417
- if (!result.success) {
418
- return {
419
- success: false,
420
- error: result.error || result.errorMessage
421
- };
422
- }
423
- return {
424
- success: true,
425
- transaction: result.transaction
426
- };
427
- } catch (error) {
428
- return {
429
- success: false,
430
- error: error instanceof Error ? error.message : "Settlement failed"
431
- };
432
- }
433
- }
434
- /**
435
- * 验证支付负载
436
- * @param paymentPayload 支付负载
437
- * @param paymentRequirements 支付要求
438
- */
439
- async verify(paymentPayload, paymentRequirements) {
440
- if (!this.initialized) {
441
- return {
442
- success: false,
443
- error: "Server not initialized. Please call initialize() first."
444
- };
445
- }
446
- try {
447
- const result = await this.facilitator.verify(
448
- paymentPayload,
449
- paymentRequirements
450
- );
451
- return {
452
- success: result.success,
453
- data: result.payer,
454
- error: result.error || result.errorMessage
455
- };
456
- } catch (error) {
457
- return {
458
- success: false,
459
- error: error instanceof Error ? error.message : "Payment verification failed"
460
- };
461
- }
462
- }
463
- /**
464
- * 获取 facilitator
465
- */
466
- getFacilitator() {
467
- return this.facilitator;
468
- }
469
- /**
470
- * 获取 schema
471
- */
472
- getSchema() {
473
- return this.schema;
474
- }
475
- /**
476
- * 获取 client
477
- */
478
- getClient() {
479
- return this.client;
480
- }
481
- /**
482
- * 检查是否已初始化
483
- */
484
- isInitialized() {
485
- return this.initialized;
486
- }
487
- /**
488
- * 验证网络是否匹配
489
- * @param schemaNetwork schema 中的 network
490
- * @param clientChainId client 的 chainId
491
- * @returns 是否匹配
492
- */
493
- validateNetwork(schemaNetwork, clientChainId) {
494
- if (schemaNetwork.startsWith("eip155:")) {
495
- const chainId = parseInt(schemaNetwork.split(":")[1] || "0");
496
- return chainId === clientChainId;
497
- }
498
- const networkMap = {
499
- "ethereum": 1,
500
- "goerli": 5,
501
- "sepolia": 11155111,
502
- "base": 8453,
503
- "base-sepolia": 84532,
504
- "bsc": 56,
505
- "bsc-testnet": 97,
506
- "polygon": 137,
507
- "polygon-mumbai": 80001,
508
- "arbitrum": 42161,
509
- "arbitrum-goerli": 421613,
510
- "optimism": 10,
511
- "optimism-goerli": 420,
512
- "avalanche": 43114,
513
- "avalanche-fuji": 43113
514
- };
515
- const expectedChainId = networkMap[schemaNetwork];
516
- if (expectedChainId !== void 0) {
517
- return expectedChainId === clientChainId;
518
- }
519
- return true;
520
- }
521
- /**
522
- * 解析支付 header
523
- * @param paymentHeaderBase64 Base64 编码的支付 header
524
- * @returns 解析结果,成功时返回 paymentPayload 和 paymentRequirements,失败时返回服务端的支付要求
525
- */
526
- parsePaymentHeader(paymentHeaderBase64) {
527
- const paymentRequirements = this.schema.toJSON();
528
- if (!paymentHeaderBase64) {
529
- return {
530
- success: false,
531
- data: paymentRequirements,
532
- error: "No X-PAYMENT header"
533
- };
534
- }
535
- let paymentPayload;
536
- try {
537
- const paymentHeaderJson = Buffer.from(
538
- paymentHeaderBase64,
539
- "base64"
540
- ).toString("utf-8");
541
- paymentPayload = JSON.parse(paymentHeaderJson);
542
- } catch (err) {
543
- return {
544
- success: false,
545
- data: paymentRequirements,
546
- error: "Invalid payment header format"
547
- };
548
- }
549
- const validationError = this.validatePaymentPayload(
550
- paymentPayload,
551
- paymentRequirements
552
- );
553
- if (validationError) {
554
- return {
555
- success: false,
556
- data: paymentRequirements,
557
- error: validationError
558
- };
559
- }
560
- return {
561
- success: true,
562
- data: {
563
- paymentPayload,
564
- paymentRequirements
565
- }
566
- };
567
- }
568
- /**
569
- * 验证客户端的支付数据是否与服务端要求一致
570
- * @param paymentPayload 客户端的支付负载
571
- * @param paymentRequirements 服务端的支付要求
572
- * @returns 错误信息,如果验证通过则返回 null
573
- */
574
- validatePaymentPayload(paymentPayload, paymentRequirements) {
575
- if (paymentPayload.scheme !== paymentRequirements.scheme) {
576
- return `Scheme mismatch: expected '${paymentRequirements.scheme}', got '${paymentPayload.scheme}'`;
577
- }
578
- if (paymentPayload.network !== paymentRequirements.network) {
579
- return `Network mismatch: expected '${paymentRequirements.network}', got '${paymentPayload.network}'`;
580
- }
581
- if (paymentPayload.payload) {
582
- const authorization = paymentPayload.payload.authorization;
583
- if (authorization?.value) {
584
- const paymentAmount = BigInt(authorization.value);
585
- const maxAmount = BigInt(paymentRequirements.maxAmountRequired);
586
- if (paymentAmount !== maxAmount) {
587
- return `Payment amount error ${paymentAmount} !== ${maxAmount}`;
588
- }
589
- }
590
- if (authorization?.to) {
591
- const expectedPayTo = paymentRequirements.payTo.toLowerCase();
592
- const actualPayTo = authorization.to.toLowerCase();
593
- if (actualPayTo !== expectedPayTo && actualPayTo !== paymentRequirements.extra?.relayer?.toLowerCase()) {
594
- return `PayTo address mismatch: expected '${expectedPayTo}', got '${actualPayTo}'`;
595
- }
596
- }
597
- const authorizationType = paymentPayload.payload.authorizationType;
598
- if (authorizationType === "permit" || authorizationType === "permit2" || authorizationType === "eip3009") {
599
- }
600
- }
601
- return null;
602
- }
603
- };
604
- export {
605
- X402Server,
606
- detectTokenPaymentMethods,
607
- getRecommendedPaymentMethod,
608
- getTokenInfo
609
- };
610
- //# sourceMappingURL=index.mjs.map