@wazabiai/x402 0.1.0

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.js ADDED
@@ -0,0 +1,750 @@
1
+ import { z } from 'zod';
2
+ import axios from 'axios';
3
+ import { createWalletClient, http, verifyTypedData } from 'viem';
4
+ import { privateKeyToAccount } from 'viem/accounts';
5
+ import { bsc } from 'viem/chains';
6
+
7
+ /* @wazabiai/x402 - x402 v2 Payment Protocol SDK */
8
+
9
+ var X402_VERSION = "2.0.0";
10
+ var X402_DOMAIN_NAME = "x402";
11
+ var X402_HEADERS = {
12
+ PAYMENT_REQUIRED: "x-payment-required",
13
+ PAYMENT_SIGNATURE: "x-payment-signature",
14
+ PAYMENT_PAYLOAD: "x-payment-payload"
15
+ };
16
+ var PaymentRequirementSchema = z.object({
17
+ /** Payment amount in smallest token unit (wei/satoshi) as string */
18
+ amount: z.string().regex(/^\d+$/, "Amount must be a numeric string"),
19
+ /** Token contract address */
20
+ token: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid token address"),
21
+ /** CAIP-2 network identifier */
22
+ network_id: z.string().regex(/^eip155:\d+$/, "Invalid CAIP-2 network ID"),
23
+ /** Recipient address for payment */
24
+ pay_to: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid recipient address"),
25
+ /** Optional: Payment description */
26
+ description: z.string().optional(),
27
+ /** Optional: Resource identifier being accessed */
28
+ resource: z.string().optional(),
29
+ /** Optional: Expiration timestamp (Unix epoch seconds) */
30
+ expires_at: z.number().int().positive().optional(),
31
+ /** Optional: Unique nonce to prevent replay attacks */
32
+ nonce: z.string().optional(),
33
+ /** Protocol version */
34
+ version: z.string().optional()
35
+ });
36
+ var PaymentPayloadSchema = z.object({
37
+ /** Payment amount in smallest token unit */
38
+ amount: z.string().regex(/^\d+$/, "Amount must be a numeric string"),
39
+ /** Token contract address */
40
+ token: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid token address"),
41
+ /** Chain ID (numeric) */
42
+ chainId: z.number().int().positive(),
43
+ /** Recipient address */
44
+ payTo: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid recipient address"),
45
+ /** Payer address (signer) */
46
+ payer: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid payer address"),
47
+ /** Unix timestamp when payment expires */
48
+ deadline: z.number().int().positive(),
49
+ /** Unique nonce to prevent replay attacks */
50
+ nonce: z.string(),
51
+ /** Optional: Resource being accessed */
52
+ resource: z.string().optional()
53
+ });
54
+ var PAYMENT_TYPES = {
55
+ Payment: [
56
+ { name: "amount", type: "uint256" },
57
+ { name: "token", type: "address" },
58
+ { name: "chainId", type: "uint256" },
59
+ { name: "payTo", type: "address" },
60
+ { name: "payer", type: "address" },
61
+ { name: "deadline", type: "uint256" },
62
+ { name: "nonce", type: "string" },
63
+ { name: "resource", type: "string" }
64
+ ]
65
+ };
66
+ var SignedPaymentSchema = z.object({
67
+ payload: PaymentPayloadSchema,
68
+ signature: z.string().regex(/^0x[a-fA-F0-9]+$/, "Invalid signature format"),
69
+ signer: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid signer address")
70
+ });
71
+ var X402Error = class _X402Error extends Error {
72
+ constructor(message, code, details) {
73
+ super(message);
74
+ this.code = code;
75
+ this.details = details;
76
+ this.name = "X402Error";
77
+ Object.setPrototypeOf(this, _X402Error.prototype);
78
+ }
79
+ };
80
+ var PaymentRequiredError = class _PaymentRequiredError extends X402Error {
81
+ constructor(requirement, message = "Payment required") {
82
+ super(message, "PAYMENT_REQUIRED", { requirement });
83
+ this.requirement = requirement;
84
+ this.name = "PaymentRequiredError";
85
+ Object.setPrototypeOf(this, _PaymentRequiredError.prototype);
86
+ }
87
+ };
88
+ var PaymentVerificationError = class _PaymentVerificationError extends X402Error {
89
+ constructor(message, details) {
90
+ super(message, "PAYMENT_VERIFICATION_FAILED", details);
91
+ this.details = details;
92
+ this.name = "PaymentVerificationError";
93
+ Object.setPrototypeOf(this, _PaymentVerificationError.prototype);
94
+ }
95
+ };
96
+ var UnsupportedNetworkError = class _UnsupportedNetworkError extends X402Error {
97
+ constructor(networkId, supportedNetworks) {
98
+ super(
99
+ `Network ${networkId} is not supported. Supported networks: ${supportedNetworks.join(", ")}`,
100
+ "UNSUPPORTED_NETWORK",
101
+ { networkId, supportedNetworks }
102
+ );
103
+ this.name = "UnsupportedNetworkError";
104
+ Object.setPrototypeOf(this, _UnsupportedNetworkError.prototype);
105
+ }
106
+ };
107
+ var PaymentExpiredError = class _PaymentExpiredError extends X402Error {
108
+ constructor(deadline) {
109
+ super(
110
+ `Payment has expired. Deadline: ${new Date(deadline * 1e3).toISOString()}`,
111
+ "PAYMENT_EXPIRED",
112
+ { deadline }
113
+ );
114
+ this.name = "PaymentExpiredError";
115
+ Object.setPrototypeOf(this, _PaymentExpiredError.prototype);
116
+ }
117
+ };
118
+ function extractChainId(caipId) {
119
+ const match = caipId.match(/^eip155:(\d+)$/);
120
+ if (!match?.[1]) {
121
+ throw new X402Error(`Invalid CAIP-2 identifier: ${caipId}`, "INVALID_CAIP_ID");
122
+ }
123
+ return parseInt(match[1], 10);
124
+ }
125
+ function createCaipId(chainId) {
126
+ return `eip155:${chainId}`;
127
+ }
128
+ function generateNonce() {
129
+ const bytes = new Uint8Array(16);
130
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
131
+ crypto.getRandomValues(bytes);
132
+ } else {
133
+ for (let i = 0; i < bytes.length; i++) {
134
+ bytes[i] = Math.floor(Math.random() * 256);
135
+ }
136
+ }
137
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
138
+ }
139
+ function calculateDeadline(durationSeconds = 300) {
140
+ return Math.floor(Date.now() / 1e3) + durationSeconds;
141
+ }
142
+
143
+ // src/chains/bnb.ts
144
+ var BSC_CHAIN_ID = 56;
145
+ var BSC_CAIP_ID = "eip155:56";
146
+ var BSC_RPC_URLS = [
147
+ "https://bsc-dataseed.binance.org",
148
+ "https://bsc-dataseed1.binance.org",
149
+ "https://bsc-dataseed2.binance.org",
150
+ "https://bsc-dataseed3.binance.org",
151
+ "https://bsc-dataseed4.binance.org",
152
+ "https://bsc-dataseed1.defibit.io",
153
+ "https://bsc-dataseed1.ninicoin.io"
154
+ ];
155
+ var BSC_DEFAULT_RPC = BSC_RPC_URLS[0];
156
+ var BSC_BLOCK_EXPLORER = "https://bscscan.com";
157
+ var BSC_USDT = {
158
+ address: "0x55d398326f99059fF775485246999027B3197955",
159
+ symbol: "USDT",
160
+ decimals: 18,
161
+ name: "Tether USD"
162
+ };
163
+ var BSC_USDC = {
164
+ address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
165
+ symbol: "USDC",
166
+ decimals: 18,
167
+ name: "USD Coin"
168
+ };
169
+ var BSC_BUSD = {
170
+ address: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56",
171
+ symbol: "BUSD",
172
+ decimals: 18,
173
+ name: "Binance USD"
174
+ };
175
+ var BSC_WBNB = {
176
+ address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
177
+ symbol: "BNB",
178
+ decimals: 18,
179
+ name: "Wrapped BNB"
180
+ };
181
+ var BSC_DEFAULT_TOKEN = BSC_USDT;
182
+ var BSC_TOKENS = {
183
+ USDT: BSC_USDT,
184
+ USDC: BSC_USDC,
185
+ BUSD: BSC_BUSD,
186
+ WBNB: BSC_WBNB
187
+ };
188
+ var BSC_TOKEN_BY_ADDRESS = {
189
+ [BSC_USDT.address.toLowerCase()]: BSC_USDT,
190
+ [BSC_USDC.address.toLowerCase()]: BSC_USDC,
191
+ [BSC_BUSD.address.toLowerCase()]: BSC_BUSD,
192
+ [BSC_WBNB.address.toLowerCase()]: BSC_WBNB
193
+ };
194
+ var BSC_NETWORK_CONFIG = {
195
+ caipId: BSC_CAIP_ID,
196
+ chainId: BSC_CHAIN_ID,
197
+ name: "BNB Smart Chain",
198
+ rpcUrl: BSC_DEFAULT_RPC,
199
+ nativeCurrency: {
200
+ name: "BNB",
201
+ symbol: "BNB",
202
+ decimals: 18
203
+ },
204
+ blockExplorer: BSC_BLOCK_EXPLORER,
205
+ tokens: BSC_TOKENS
206
+ };
207
+ function getTokenByAddress(address) {
208
+ return BSC_TOKEN_BY_ADDRESS[address.toLowerCase()];
209
+ }
210
+ function getTokenBySymbol(symbol) {
211
+ const upperSymbol = symbol.toUpperCase();
212
+ return BSC_TOKENS[upperSymbol];
213
+ }
214
+ function isTokenSupported(address) {
215
+ return address.toLowerCase() in BSC_TOKEN_BY_ADDRESS;
216
+ }
217
+ function formatTokenAmount(amount, tokenAddress) {
218
+ const token = getTokenByAddress(tokenAddress);
219
+ const decimals = token?.decimals ?? 18;
220
+ const amountBigInt = typeof amount === "string" ? BigInt(amount) : amount;
221
+ const divisor = BigInt(10 ** decimals);
222
+ const wholePart = amountBigInt / divisor;
223
+ const fractionalPart = amountBigInt % divisor;
224
+ const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
225
+ const trimmedFractional = fractionalStr.replace(/0+$/, "").padEnd(2, "0");
226
+ return `${wholePart}.${trimmedFractional}`;
227
+ }
228
+ function parseTokenAmount(amount, tokenAddress) {
229
+ const token = getTokenByAddress(tokenAddress);
230
+ const decimals = token?.decimals ?? 18;
231
+ const [whole, fractional = ""] = amount.split(".");
232
+ const paddedFractional = fractional.padEnd(decimals, "0").slice(0, decimals);
233
+ return BigInt(whole + paddedFractional);
234
+ }
235
+ function getTxUrl(txHash) {
236
+ return `${BSC_BLOCK_EXPLORER}/tx/${txHash}`;
237
+ }
238
+ function getAddressUrl(address) {
239
+ return `${BSC_BLOCK_EXPLORER}/address/${address}`;
240
+ }
241
+ function getTokenUrl(tokenAddress) {
242
+ return `${BSC_BLOCK_EXPLORER}/token/${tokenAddress}`;
243
+ }
244
+
245
+ // src/chains/index.ts
246
+ var SUPPORTED_NETWORKS = {
247
+ [BSC_CAIP_ID]: BSC_NETWORK_CONFIG
248
+ };
249
+ function getNetworkConfig(caipId) {
250
+ return SUPPORTED_NETWORKS[caipId];
251
+ }
252
+ function isNetworkSupported(caipId) {
253
+ return caipId in SUPPORTED_NETWORKS;
254
+ }
255
+ function getSupportedNetworkIds() {
256
+ return Object.keys(SUPPORTED_NETWORKS);
257
+ }
258
+ var X402Client = class {
259
+ axiosInstance;
260
+ walletClient = null;
261
+ account = null;
262
+ config;
263
+ constructor(config = {}) {
264
+ this.config = {
265
+ supportedNetworks: [BSC_CAIP_ID],
266
+ defaultDeadline: 300,
267
+ // 5 minutes
268
+ autoRetry: true,
269
+ maxRetries: 1,
270
+ ...config
271
+ };
272
+ this.axiosInstance = axios.create({
273
+ timeout: 3e4,
274
+ validateStatus: (status) => status < 500,
275
+ // Don't throw on 402
276
+ ...config.axiosConfig
277
+ });
278
+ if (config.privateKey) {
279
+ const normalizedKey = config.privateKey.startsWith("0x") ? config.privateKey : `0x${config.privateKey}`;
280
+ this.account = privateKeyToAccount(normalizedKey);
281
+ this.walletClient = createWalletClient({
282
+ account: this.account,
283
+ chain: bsc,
284
+ transport: http(config.rpcUrl ?? BSC_DEFAULT_RPC)
285
+ });
286
+ }
287
+ }
288
+ /**
289
+ * Get the signer address if available
290
+ */
291
+ get signerAddress() {
292
+ return this.account?.address ?? null;
293
+ }
294
+ /**
295
+ * Check if the client can sign payments
296
+ */
297
+ get canSign() {
298
+ return this.account !== null;
299
+ }
300
+ /**
301
+ * Make an HTTP request with automatic 402 payment handling
302
+ */
303
+ async fetch(url, options = {}) {
304
+ let lastError = null;
305
+ let attempts = 0;
306
+ while (attempts <= this.config.maxRetries) {
307
+ try {
308
+ const response = await this.axiosInstance.request({
309
+ url,
310
+ ...options
311
+ });
312
+ if (response.status === 402) {
313
+ const requirement = this.parsePaymentRequirement(response);
314
+ if (this.config.onPaymentRequired) {
315
+ await this.config.onPaymentRequired(requirement);
316
+ }
317
+ if (!this.config.autoRetry || attempts >= this.config.maxRetries) {
318
+ throw new PaymentRequiredError(requirement);
319
+ }
320
+ if (!this.canSign) {
321
+ throw new PaymentRequiredError(
322
+ requirement,
323
+ "Payment required but no signer configured. Provide a privateKey in client config."
324
+ );
325
+ }
326
+ if (!this.config.supportedNetworks.includes(requirement.network_id)) {
327
+ throw new UnsupportedNetworkError(
328
+ requirement.network_id,
329
+ this.config.supportedNetworks
330
+ );
331
+ }
332
+ const signedPayment = await this.signPayment(requirement);
333
+ if (this.config.onPaymentSigned) {
334
+ await this.config.onPaymentSigned(signedPayment);
335
+ }
336
+ const retryOptions = this.addPaymentHeaders(options, signedPayment);
337
+ attempts++;
338
+ options = retryOptions;
339
+ continue;
340
+ }
341
+ return response;
342
+ } catch (error) {
343
+ if (error instanceof PaymentRequiredError || error instanceof UnsupportedNetworkError) {
344
+ throw error;
345
+ }
346
+ lastError = error;
347
+ attempts++;
348
+ }
349
+ }
350
+ throw lastError ?? new Error("Request failed after all retries");
351
+ }
352
+ /**
353
+ * Convenience methods for common HTTP verbs
354
+ */
355
+ async get(url, options) {
356
+ return this.fetch(url, { ...options, method: "GET" });
357
+ }
358
+ async post(url, data, options) {
359
+ return this.fetch(url, { ...options, method: "POST", data });
360
+ }
361
+ async put(url, data, options) {
362
+ return this.fetch(url, { ...options, method: "PUT", data });
363
+ }
364
+ async delete(url, options) {
365
+ return this.fetch(url, { ...options, method: "DELETE" });
366
+ }
367
+ /**
368
+ * Sign a payment requirement and return the signed payment
369
+ */
370
+ async signPayment(requirement) {
371
+ if (!this.walletClient || !this.account) {
372
+ throw new PaymentVerificationError(
373
+ "Cannot sign payment: no wallet configured"
374
+ );
375
+ }
376
+ const chainId = extractChainId(requirement.network_id);
377
+ const nonce = requirement.nonce ?? generateNonce();
378
+ const deadline = requirement.expires_at ?? calculateDeadline(this.config.defaultDeadline);
379
+ const payload = {
380
+ amount: requirement.amount,
381
+ token: requirement.token,
382
+ chainId,
383
+ payTo: requirement.pay_to,
384
+ payer: this.account.address,
385
+ deadline,
386
+ nonce,
387
+ resource: requirement.resource ?? ""
388
+ };
389
+ const domain = {
390
+ name: X402_DOMAIN_NAME,
391
+ version: X402_VERSION,
392
+ chainId
393
+ };
394
+ const signature = await this.walletClient.signTypedData({
395
+ account: this.account,
396
+ domain,
397
+ types: PAYMENT_TYPES,
398
+ primaryType: "Payment",
399
+ message: {
400
+ amount: BigInt(payload.amount),
401
+ token: payload.token,
402
+ chainId: BigInt(payload.chainId),
403
+ payTo: payload.payTo,
404
+ payer: payload.payer,
405
+ deadline: BigInt(payload.deadline),
406
+ nonce: payload.nonce,
407
+ resource: payload.resource ?? ""
408
+ }
409
+ });
410
+ return {
411
+ payload,
412
+ signature,
413
+ signer: this.account.address
414
+ };
415
+ }
416
+ /**
417
+ * Parse payment requirement from 402 response
418
+ */
419
+ parsePaymentRequirement(response) {
420
+ const headerValue = response.headers[X402_HEADERS.PAYMENT_REQUIRED] || response.headers[X402_HEADERS.PAYMENT_REQUIRED.toLowerCase()];
421
+ let requirementData;
422
+ if (headerValue) {
423
+ try {
424
+ requirementData = JSON.parse(
425
+ typeof headerValue === "string" ? headerValue : String(headerValue)
426
+ );
427
+ } catch {
428
+ throw new PaymentVerificationError(
429
+ "Invalid payment requirement header: failed to parse JSON"
430
+ );
431
+ }
432
+ } else if (response.data && typeof response.data === "object") {
433
+ requirementData = response.data;
434
+ } else {
435
+ throw new PaymentVerificationError(
436
+ "No payment requirement found in response headers or body"
437
+ );
438
+ }
439
+ const result = PaymentRequirementSchema.safeParse(requirementData);
440
+ if (!result.success) {
441
+ throw new PaymentVerificationError(
442
+ `Invalid payment requirement: ${result.error.message}`,
443
+ { errors: result.error.errors }
444
+ );
445
+ }
446
+ return result.data;
447
+ }
448
+ /**
449
+ * Add payment headers to request options
450
+ */
451
+ addPaymentHeaders(options, payment) {
452
+ const headers = {
453
+ ...options.headers,
454
+ [X402_HEADERS.PAYMENT_SIGNATURE]: payment.signature,
455
+ [X402_HEADERS.PAYMENT_PAYLOAD]: JSON.stringify(payment.payload)
456
+ };
457
+ return {
458
+ ...options,
459
+ headers
460
+ };
461
+ }
462
+ };
463
+ function createX402Client(config) {
464
+ return new X402Client(config);
465
+ }
466
+ function createX402ClientFromEnv(config) {
467
+ const privateKey = process.env["X402_PRIVATE_KEY"];
468
+ if (!privateKey) {
469
+ console.warn("X402_PRIVATE_KEY not found in environment. Client will be read-only.");
470
+ }
471
+ return new X402Client({
472
+ ...config,
473
+ privateKey
474
+ });
475
+ }
476
+ async function verifyPaymentSignatureLocal(payment, expectedRecipient, chainId) {
477
+ try {
478
+ const { payload, signature, signer } = payment;
479
+ const now = Math.floor(Date.now() / 1e3);
480
+ if (payload.deadline < now) {
481
+ return {
482
+ valid: false,
483
+ error: `Payment expired at ${new Date(payload.deadline * 1e3).toISOString()}`
484
+ };
485
+ }
486
+ if (payload.chainId !== chainId) {
487
+ return {
488
+ valid: false,
489
+ error: `Chain ID mismatch: expected ${chainId}, got ${payload.chainId}`
490
+ };
491
+ }
492
+ if (payload.payTo.toLowerCase() !== expectedRecipient.toLowerCase()) {
493
+ return {
494
+ valid: false,
495
+ error: `Recipient mismatch: expected ${expectedRecipient}, got ${payload.payTo}`
496
+ };
497
+ }
498
+ const domain = {
499
+ name: X402_DOMAIN_NAME,
500
+ version: X402_VERSION,
501
+ chainId
502
+ };
503
+ const isValid = await verifyTypedData({
504
+ address: signer,
505
+ domain,
506
+ types: PAYMENT_TYPES,
507
+ primaryType: "Payment",
508
+ message: {
509
+ amount: BigInt(payload.amount),
510
+ token: payload.token,
511
+ chainId: BigInt(payload.chainId),
512
+ payTo: payload.payTo,
513
+ payer: payload.payer,
514
+ deadline: BigInt(payload.deadline),
515
+ nonce: payload.nonce,
516
+ resource: payload.resource ?? ""
517
+ },
518
+ signature
519
+ });
520
+ if (!isValid) {
521
+ return {
522
+ valid: false,
523
+ error: "Signature verification failed"
524
+ };
525
+ }
526
+ return {
527
+ valid: true,
528
+ signer,
529
+ payload
530
+ };
531
+ } catch (error) {
532
+ return {
533
+ valid: false,
534
+ error: error instanceof Error ? error.message : "Unknown verification error"
535
+ };
536
+ }
537
+ }
538
+ async function verifyPaymentWithFacilitator(payment, facilitatorUrl, networkId) {
539
+ try {
540
+ const request = {
541
+ signature: payment.signature,
542
+ payload: payment.payload,
543
+ networkId
544
+ };
545
+ const response = await axios.post(
546
+ `${facilitatorUrl}/verify`,
547
+ request,
548
+ {
549
+ timeout: 1e4,
550
+ headers: { "Content-Type": "application/json" }
551
+ }
552
+ );
553
+ if (response.data.valid) {
554
+ return {
555
+ valid: true,
556
+ signer: response.data.signer,
557
+ payload: payment.payload
558
+ };
559
+ }
560
+ return {
561
+ valid: false,
562
+ error: response.data.error ?? "Facilitator rejected payment"
563
+ };
564
+ } catch (error) {
565
+ return {
566
+ valid: false,
567
+ error: error instanceof Error ? `Facilitator error: ${error.message}` : "Facilitator verification failed"
568
+ };
569
+ }
570
+ }
571
+ function x402Middleware(config) {
572
+ const {
573
+ recipientAddress,
574
+ amount,
575
+ tokenAddress = BSC_USDT.address,
576
+ facilitatorUrl,
577
+ description,
578
+ networkId = BSC_CAIP_ID,
579
+ deadlineDuration = 300,
580
+ nonceGenerator = generateNonce,
581
+ verifyPayment: customVerify,
582
+ excludeRoutes = [],
583
+ onError
584
+ } = config;
585
+ const chainId = extractChainId(networkId);
586
+ return async (req, res, next) => {
587
+ try {
588
+ const path = req.path;
589
+ if (excludeRoutes.some((route) => path.startsWith(route))) {
590
+ next();
591
+ return;
592
+ }
593
+ const signatureHeader = req.headers[X402_HEADERS.PAYMENT_SIGNATURE] || req.headers[X402_HEADERS.PAYMENT_SIGNATURE.toLowerCase()];
594
+ const payloadHeader = req.headers[X402_HEADERS.PAYMENT_PAYLOAD] || req.headers[X402_HEADERS.PAYMENT_PAYLOAD.toLowerCase()];
595
+ if (!signatureHeader) {
596
+ const requirement = {
597
+ amount,
598
+ token: tokenAddress,
599
+ network_id: networkId,
600
+ pay_to: recipientAddress,
601
+ description,
602
+ resource: req.originalUrl,
603
+ expires_at: calculateDeadline(deadlineDuration),
604
+ nonce: nonceGenerator(),
605
+ version: X402_VERSION
606
+ };
607
+ res.status(402);
608
+ res.setHeader(X402_HEADERS.PAYMENT_REQUIRED, JSON.stringify(requirement));
609
+ res.json({
610
+ error: "Payment Required",
611
+ requirement
612
+ });
613
+ return;
614
+ }
615
+ let payload;
616
+ try {
617
+ if (!payloadHeader) {
618
+ throw new Error("Missing payment payload header");
619
+ }
620
+ const parsedPayload = JSON.parse(
621
+ typeof payloadHeader === "string" ? payloadHeader : String(payloadHeader)
622
+ );
623
+ const result = PaymentPayloadSchema.safeParse(parsedPayload);
624
+ if (!result.success) {
625
+ throw new Error(`Invalid payload: ${result.error.message}`);
626
+ }
627
+ payload = result.data;
628
+ } catch (error) {
629
+ res.status(400).json({
630
+ error: "Invalid Payment",
631
+ message: error instanceof Error ? error.message : "Failed to parse payment payload"
632
+ });
633
+ return;
634
+ }
635
+ const signedPayment = {
636
+ payload,
637
+ signature: typeof signatureHeader === "string" ? signatureHeader : String(signatureHeader),
638
+ signer: payload.payer
639
+ };
640
+ let verificationResult;
641
+ if (facilitatorUrl) {
642
+ verificationResult = await verifyPaymentWithFacilitator(
643
+ signedPayment,
644
+ facilitatorUrl,
645
+ networkId
646
+ );
647
+ } else {
648
+ verificationResult = await verifyPaymentSignatureLocal(
649
+ signedPayment,
650
+ recipientAddress,
651
+ chainId
652
+ );
653
+ }
654
+ if (!verificationResult.valid) {
655
+ res.status(402).json({
656
+ error: "Payment Verification Failed",
657
+ message: verificationResult.error
658
+ });
659
+ return;
660
+ }
661
+ if (customVerify) {
662
+ const customValid = await customVerify(signedPayment, req);
663
+ if (!customValid) {
664
+ res.status(402).json({
665
+ error: "Payment Rejected",
666
+ message: "Custom verification failed"
667
+ });
668
+ return;
669
+ }
670
+ }
671
+ if (BigInt(payload.amount) < BigInt(amount)) {
672
+ res.status(402).json({
673
+ error: "Insufficient Payment",
674
+ message: `Expected ${amount}, received ${payload.amount}`
675
+ });
676
+ return;
677
+ }
678
+ req.x402 = {
679
+ payment: signedPayment,
680
+ verified: true,
681
+ signer: verificationResult.signer
682
+ };
683
+ next();
684
+ } catch (error) {
685
+ if (onError && error instanceof Error) {
686
+ onError(error, req, res);
687
+ return;
688
+ }
689
+ console.error("[x402] Middleware error:", error);
690
+ res.status(500).json({
691
+ error: "Internal Server Error",
692
+ message: "Payment processing failed"
693
+ });
694
+ }
695
+ };
696
+ }
697
+ function createPaymentRequirement(config) {
698
+ const {
699
+ recipientAddress,
700
+ amount,
701
+ tokenAddress = BSC_USDT.address,
702
+ description,
703
+ networkId = BSC_CAIP_ID,
704
+ resource,
705
+ deadline,
706
+ nonce
707
+ } = config;
708
+ return {
709
+ amount,
710
+ token: tokenAddress,
711
+ network_id: networkId,
712
+ pay_to: recipientAddress,
713
+ description,
714
+ resource,
715
+ expires_at: deadline ?? calculateDeadline(300),
716
+ nonce: nonce ?? generateNonce(),
717
+ version: X402_VERSION
718
+ };
719
+ }
720
+ async function verifyPayment(payment, recipientAddress, networkId = BSC_CAIP_ID, facilitatorUrl) {
721
+ const chainId = extractChainId(networkId);
722
+ if (facilitatorUrl) {
723
+ return verifyPaymentWithFacilitator(payment, facilitatorUrl, networkId);
724
+ }
725
+ return verifyPaymentSignatureLocal(payment, recipientAddress, chainId);
726
+ }
727
+ function parsePaymentFromRequest(req) {
728
+ try {
729
+ const signatureHeader = req.headers[X402_HEADERS.PAYMENT_SIGNATURE] || req.headers[X402_HEADERS.PAYMENT_SIGNATURE.toLowerCase()];
730
+ const payloadHeader = req.headers[X402_HEADERS.PAYMENT_PAYLOAD] || req.headers[X402_HEADERS.PAYMENT_PAYLOAD.toLowerCase()];
731
+ if (!signatureHeader || !payloadHeader) {
732
+ return null;
733
+ }
734
+ const payload = JSON.parse(
735
+ typeof payloadHeader === "string" ? payloadHeader : String(payloadHeader)
736
+ );
737
+ const result = SignedPaymentSchema.safeParse({
738
+ payload,
739
+ signature: typeof signatureHeader === "string" ? signatureHeader : String(signatureHeader),
740
+ signer: payload.payer
741
+ });
742
+ return result.success ? result.data : null;
743
+ } catch {
744
+ return null;
745
+ }
746
+ }
747
+
748
+ export { BSC_BLOCK_EXPLORER, BSC_BUSD, BSC_CAIP_ID, BSC_CHAIN_ID, BSC_DEFAULT_RPC, BSC_DEFAULT_TOKEN, BSC_NETWORK_CONFIG, BSC_RPC_URLS, BSC_TOKENS, BSC_TOKEN_BY_ADDRESS, BSC_USDC, BSC_USDT, BSC_WBNB, PAYMENT_TYPES, PaymentExpiredError, PaymentPayloadSchema, PaymentRequiredError, PaymentRequirementSchema, PaymentVerificationError, SUPPORTED_NETWORKS, SignedPaymentSchema, UnsupportedNetworkError, X402Client, X402Error, X402_DOMAIN_NAME, X402_HEADERS, X402_VERSION, calculateDeadline, createCaipId, createPaymentRequirement, createX402Client, createX402ClientFromEnv, extractChainId, formatTokenAmount, generateNonce, getAddressUrl, getNetworkConfig, getSupportedNetworkIds, getTokenByAddress, getTokenBySymbol, getTokenUrl, getTxUrl, isNetworkSupported, isTokenSupported, parsePaymentFromRequest, parseTokenAmount, verifyPayment, x402Middleware };
749
+ //# sourceMappingURL=index.js.map
750
+ //# sourceMappingURL=index.js.map