@dexterai/x402 1.0.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.
@@ -0,0 +1,827 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/adapters/solana.ts
24
+ function isSolanaWallet(wallet) {
25
+ if (!wallet || typeof wallet !== "object") return false;
26
+ const w = wallet;
27
+ return "publicKey" in w && "signTransaction" in w && typeof w.signTransaction === "function";
28
+ }
29
+ function createSolanaAdapter(config) {
30
+ return new SolanaAdapter(config);
31
+ }
32
+ var import_web3, import_spl_token, SOLANA_MAINNET, SOLANA_DEVNET, SOLANA_TESTNET, DEFAULT_RPC_URLS, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS, SolanaAdapter;
33
+ var init_solana = __esm({
34
+ "src/adapters/solana.ts"() {
35
+ "use strict";
36
+ import_web3 = require("@solana/web3.js");
37
+ import_spl_token = require("@solana/spl-token");
38
+ SOLANA_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
39
+ SOLANA_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
40
+ SOLANA_TESTNET = "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";
41
+ DEFAULT_RPC_URLS = {
42
+ [SOLANA_MAINNET]: "https://api.mainnet-beta.solana.com",
43
+ [SOLANA_DEVNET]: "https://api.devnet.solana.com",
44
+ [SOLANA_TESTNET]: "https://api.testnet.solana.com"
45
+ };
46
+ DEFAULT_COMPUTE_UNIT_LIMIT = 12e3;
47
+ DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
48
+ SolanaAdapter = class {
49
+ name = "Solana";
50
+ networks = [SOLANA_MAINNET, SOLANA_DEVNET, SOLANA_TESTNET];
51
+ config;
52
+ log;
53
+ constructor(config = {}) {
54
+ this.config = config;
55
+ this.log = config.verbose ? console.log.bind(console, "[x402:solana]") : () => {
56
+ };
57
+ }
58
+ canHandle(network) {
59
+ if (this.networks.includes(network)) return true;
60
+ if (network === "solana") return true;
61
+ if (network === "solana-devnet") return true;
62
+ if (network === "solana-testnet") return true;
63
+ if (network.startsWith("solana:")) return true;
64
+ return false;
65
+ }
66
+ getDefaultRpcUrl(network) {
67
+ if (this.config.rpcUrls?.[network]) {
68
+ return this.config.rpcUrls[network];
69
+ }
70
+ if (DEFAULT_RPC_URLS[network]) {
71
+ return DEFAULT_RPC_URLS[network];
72
+ }
73
+ if (network === "solana") return DEFAULT_RPC_URLS[SOLANA_MAINNET];
74
+ if (network === "solana-devnet") return DEFAULT_RPC_URLS[SOLANA_DEVNET];
75
+ if (network === "solana-testnet") return DEFAULT_RPC_URLS[SOLANA_TESTNET];
76
+ return DEFAULT_RPC_URLS[SOLANA_MAINNET];
77
+ }
78
+ getAddress(wallet) {
79
+ if (!isSolanaWallet(wallet)) return null;
80
+ return wallet.publicKey?.toBase58() ?? null;
81
+ }
82
+ isConnected(wallet) {
83
+ if (!isSolanaWallet(wallet)) return false;
84
+ return wallet.publicKey !== null;
85
+ }
86
+ async getBalance(accept, wallet, rpcUrl) {
87
+ if (!isSolanaWallet(wallet) || !wallet.publicKey) {
88
+ return 0;
89
+ }
90
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
91
+ const connection = new import_web3.Connection(url, "confirmed");
92
+ const userPubkey = new import_web3.PublicKey(wallet.publicKey.toBase58());
93
+ const mintPubkey = new import_web3.PublicKey(accept.asset);
94
+ try {
95
+ const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
96
+ const programId = mintInfo?.owner.toBase58() === import_spl_token.TOKEN_2022_PROGRAM_ID.toBase58() ? import_spl_token.TOKEN_2022_PROGRAM_ID : import_spl_token.TOKEN_PROGRAM_ID;
97
+ const ata = await (0, import_spl_token.getAssociatedTokenAddress)(
98
+ mintPubkey,
99
+ userPubkey,
100
+ false,
101
+ programId
102
+ );
103
+ const account = await (0, import_spl_token.getAccount)(connection, ata, void 0, programId);
104
+ const decimals = accept.extra?.decimals ?? 6;
105
+ return Number(account.amount) / Math.pow(10, decimals);
106
+ } catch {
107
+ return 0;
108
+ }
109
+ }
110
+ async buildTransaction(accept, wallet, rpcUrl) {
111
+ if (!isSolanaWallet(wallet)) {
112
+ throw new Error("Invalid Solana wallet");
113
+ }
114
+ if (!wallet.publicKey) {
115
+ throw new Error("Wallet not connected");
116
+ }
117
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
118
+ const connection = new import_web3.Connection(url, "confirmed");
119
+ const userPubkey = new import_web3.PublicKey(wallet.publicKey.toBase58());
120
+ const { payTo, asset, amount, extra } = accept;
121
+ if (!extra?.feePayer) {
122
+ throw new Error("Missing feePayer in payment requirements");
123
+ }
124
+ if (typeof extra?.decimals !== "number") {
125
+ throw new Error("Missing decimals in payment requirements");
126
+ }
127
+ const feePayerPubkey = new import_web3.PublicKey(extra.feePayer);
128
+ const mintPubkey = new import_web3.PublicKey(asset);
129
+ const destinationPubkey = new import_web3.PublicKey(payTo);
130
+ this.log("Building transaction:", {
131
+ from: userPubkey.toBase58(),
132
+ to: payTo,
133
+ amount,
134
+ asset,
135
+ feePayer: extra.feePayer
136
+ });
137
+ const instructions = [];
138
+ instructions.push(
139
+ import_web3.ComputeBudgetProgram.setComputeUnitLimit({
140
+ units: DEFAULT_COMPUTE_UNIT_LIMIT
141
+ })
142
+ );
143
+ instructions.push(
144
+ import_web3.ComputeBudgetProgram.setComputeUnitPrice({
145
+ microLamports: DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS
146
+ })
147
+ );
148
+ const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
149
+ if (!mintInfo) {
150
+ throw new Error(`Token mint ${asset} not found`);
151
+ }
152
+ const programId = mintInfo.owner.toBase58() === import_spl_token.TOKEN_2022_PROGRAM_ID.toBase58() ? import_spl_token.TOKEN_2022_PROGRAM_ID : import_spl_token.TOKEN_PROGRAM_ID;
153
+ const mint = await (0, import_spl_token.getMint)(connection, mintPubkey, void 0, programId);
154
+ if (mint.decimals !== extra.decimals) {
155
+ this.log(
156
+ `Decimals mismatch: requirements say ${extra.decimals}, mint says ${mint.decimals}`
157
+ );
158
+ }
159
+ const sourceAta = await (0, import_spl_token.getAssociatedTokenAddress)(
160
+ mintPubkey,
161
+ userPubkey,
162
+ false,
163
+ programId
164
+ );
165
+ const destinationAta = await (0, import_spl_token.getAssociatedTokenAddress)(
166
+ mintPubkey,
167
+ destinationPubkey,
168
+ false,
169
+ programId
170
+ );
171
+ const sourceAtaInfo = await connection.getAccountInfo(sourceAta, "confirmed");
172
+ if (!sourceAtaInfo) {
173
+ throw new Error(
174
+ `No token account found for ${asset}. Please ensure you have USDC in your wallet.`
175
+ );
176
+ }
177
+ const destAtaInfo = await connection.getAccountInfo(destinationAta, "confirmed");
178
+ if (!destAtaInfo) {
179
+ throw new Error(
180
+ `Seller token account not found. The seller (${payTo}) must have a USDC account.`
181
+ );
182
+ }
183
+ const amountBigInt = BigInt(amount);
184
+ instructions.push(
185
+ (0, import_spl_token.createTransferCheckedInstruction)(
186
+ sourceAta,
187
+ mintPubkey,
188
+ destinationAta,
189
+ userPubkey,
190
+ amountBigInt,
191
+ mint.decimals,
192
+ [],
193
+ programId
194
+ )
195
+ );
196
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
197
+ const message = new import_web3.TransactionMessage({
198
+ payerKey: feePayerPubkey,
199
+ recentBlockhash: blockhash,
200
+ instructions
201
+ }).compileToV0Message();
202
+ const transaction = new import_web3.VersionedTransaction(message);
203
+ const signedTx = await wallet.signTransaction(transaction);
204
+ this.log("Transaction signed successfully");
205
+ return {
206
+ serialized: Buffer.from(signedTx.serialize()).toString("base64")
207
+ };
208
+ }
209
+ };
210
+ }
211
+ });
212
+
213
+ // src/adapters/evm.ts
214
+ function isEvmWallet(wallet) {
215
+ if (!wallet || typeof wallet !== "object") return false;
216
+ const w = wallet;
217
+ return "address" in w && typeof w.address === "string" && w.address.startsWith("0x");
218
+ }
219
+ function createEvmAdapter(config) {
220
+ return new EvmAdapter(config);
221
+ }
222
+ var BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE, CHAIN_IDS, DEFAULT_RPC_URLS2, USDC_ADDRESSES, EvmAdapter;
223
+ var init_evm = __esm({
224
+ "src/adapters/evm.ts"() {
225
+ "use strict";
226
+ BASE_MAINNET = "eip155:8453";
227
+ BASE_SEPOLIA = "eip155:84532";
228
+ ETHEREUM_MAINNET = "eip155:1";
229
+ ARBITRUM_ONE = "eip155:42161";
230
+ CHAIN_IDS = {
231
+ [BASE_MAINNET]: 8453,
232
+ [BASE_SEPOLIA]: 84532,
233
+ [ETHEREUM_MAINNET]: 1,
234
+ [ARBITRUM_ONE]: 42161
235
+ };
236
+ DEFAULT_RPC_URLS2 = {
237
+ [BASE_MAINNET]: "https://mainnet.base.org",
238
+ [BASE_SEPOLIA]: "https://sepolia.base.org",
239
+ [ETHEREUM_MAINNET]: "https://eth.llamarpc.com",
240
+ [ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc"
241
+ };
242
+ USDC_ADDRESSES = {
243
+ [BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
244
+ [ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
245
+ [ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
246
+ };
247
+ EvmAdapter = class {
248
+ name = "EVM";
249
+ networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
250
+ config;
251
+ log;
252
+ constructor(config = {}) {
253
+ this.config = config;
254
+ this.log = config.verbose ? console.log.bind(console, "[x402:evm]") : () => {
255
+ };
256
+ }
257
+ canHandle(network) {
258
+ if (this.networks.includes(network)) return true;
259
+ if (network === "base") return true;
260
+ if (network === "ethereum") return true;
261
+ if (network === "arbitrum") return true;
262
+ if (network.startsWith("eip155:")) return true;
263
+ return false;
264
+ }
265
+ getDefaultRpcUrl(network) {
266
+ if (this.config.rpcUrls?.[network]) {
267
+ return this.config.rpcUrls[network];
268
+ }
269
+ if (DEFAULT_RPC_URLS2[network]) {
270
+ return DEFAULT_RPC_URLS2[network];
271
+ }
272
+ if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
273
+ if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
274
+ if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
275
+ return DEFAULT_RPC_URLS2[BASE_MAINNET];
276
+ }
277
+ getAddress(wallet) {
278
+ if (!isEvmWallet(wallet)) return null;
279
+ return wallet.address;
280
+ }
281
+ isConnected(wallet) {
282
+ if (!isEvmWallet(wallet)) return false;
283
+ return !!wallet.address;
284
+ }
285
+ getChainId(network) {
286
+ if (CHAIN_IDS[network]) return CHAIN_IDS[network];
287
+ if (network.startsWith("eip155:")) {
288
+ const chainIdStr = network.split(":")[1];
289
+ return parseInt(chainIdStr, 10);
290
+ }
291
+ if (network === "base") return 8453;
292
+ if (network === "ethereum") return 1;
293
+ if (network === "arbitrum") return 42161;
294
+ return 8453;
295
+ }
296
+ async getBalance(accept, wallet, rpcUrl) {
297
+ if (!isEvmWallet(wallet) || !wallet.address) {
298
+ return 0;
299
+ }
300
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
301
+ try {
302
+ const data = this.encodeBalanceOf(wallet.address);
303
+ const response = await fetch(url, {
304
+ method: "POST",
305
+ headers: { "Content-Type": "application/json" },
306
+ body: JSON.stringify({
307
+ jsonrpc: "2.0",
308
+ id: 1,
309
+ method: "eth_call",
310
+ params: [
311
+ {
312
+ to: accept.asset,
313
+ data
314
+ },
315
+ "latest"
316
+ ]
317
+ })
318
+ });
319
+ const result = await response.json();
320
+ if (result.error || !result.result) {
321
+ return 0;
322
+ }
323
+ const balance = BigInt(result.result);
324
+ const decimals = accept.extra?.decimals ?? 6;
325
+ return Number(balance) / Math.pow(10, decimals);
326
+ } catch {
327
+ return 0;
328
+ }
329
+ }
330
+ encodeBalanceOf(address) {
331
+ const selector = "0x70a08231";
332
+ const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
333
+ return selector + paddedAddress;
334
+ }
335
+ async buildTransaction(accept, wallet, _rpcUrl) {
336
+ if (!isEvmWallet(wallet)) {
337
+ throw new Error("Invalid EVM wallet");
338
+ }
339
+ if (!wallet.address) {
340
+ throw new Error("Wallet not connected");
341
+ }
342
+ const { payTo, asset, amount, extra } = accept;
343
+ this.log("Building EVM transaction:", {
344
+ from: wallet.address,
345
+ to: payTo,
346
+ amount,
347
+ asset,
348
+ network: accept.network
349
+ });
350
+ const chainId = this.getChainId(accept.network);
351
+ const domain = {
352
+ name: extra?.name ?? "USD Coin",
353
+ version: extra?.version ?? "2",
354
+ chainId: BigInt(chainId),
355
+ verifyingContract: asset
356
+ };
357
+ const types = {
358
+ TransferWithAuthorization: [
359
+ { name: "from", type: "address" },
360
+ { name: "to", type: "address" },
361
+ { name: "value", type: "uint256" },
362
+ { name: "validAfter", type: "uint256" },
363
+ { name: "validBefore", type: "uint256" },
364
+ { name: "nonce", type: "bytes32" }
365
+ ]
366
+ };
367
+ const nonce = "0x" + [...Array(32)].map(() => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")).join("");
368
+ const now = Math.floor(Date.now() / 1e3);
369
+ const authorization = {
370
+ from: wallet.address,
371
+ to: payTo,
372
+ value: amount,
373
+ // string
374
+ validAfter: String(now - 600),
375
+ // 10 minutes before (matching upstream)
376
+ validBefore: String(now + (accept.maxTimeoutSeconds || 60)),
377
+ nonce
378
+ };
379
+ const message = {
380
+ from: wallet.address,
381
+ to: payTo,
382
+ value: BigInt(amount),
383
+ validAfter: BigInt(now - 600),
384
+ validBefore: BigInt(now + (accept.maxTimeoutSeconds || 60)),
385
+ nonce
386
+ };
387
+ if (!wallet.signTypedData) {
388
+ throw new Error("Wallet does not support signTypedData (EIP-712)");
389
+ }
390
+ const signature = await wallet.signTypedData({
391
+ domain,
392
+ types,
393
+ primaryType: "TransferWithAuthorization",
394
+ message
395
+ });
396
+ this.log("EIP-712 signature obtained");
397
+ const payload = {
398
+ authorization,
399
+ signature
400
+ };
401
+ return {
402
+ serialized: JSON.stringify(payload),
403
+ signature
404
+ };
405
+ }
406
+ };
407
+ }
408
+ });
409
+
410
+ // src/react/index.ts
411
+ var react_exports = {};
412
+ __export(react_exports, {
413
+ X402Error: () => X402Error,
414
+ useX402Payment: () => useX402Payment
415
+ });
416
+ module.exports = __toCommonJS(react_exports);
417
+
418
+ // src/react/useX402Payment.ts
419
+ var import_react = require("react");
420
+
421
+ // src/types.ts
422
+ var SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
423
+ var BASE_MAINNET_NETWORK = "eip155:8453";
424
+ var USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
425
+ var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
426
+ var X402Error = class _X402Error extends Error {
427
+ /** Error code for programmatic handling */
428
+ code;
429
+ /** Additional error details */
430
+ details;
431
+ constructor(code, message, details) {
432
+ super(message);
433
+ this.name = "X402Error";
434
+ this.code = code;
435
+ this.details = details;
436
+ Object.setPrototypeOf(this, _X402Error.prototype);
437
+ }
438
+ };
439
+
440
+ // src/adapters/index.ts
441
+ init_solana();
442
+ init_evm();
443
+
444
+ // src/client/x402-client.ts
445
+ function createX402Client(config) {
446
+ const {
447
+ adapters = [createSolanaAdapter({ verbose: config.verbose }), createEvmAdapter({ verbose: config.verbose })],
448
+ wallets: walletSet,
449
+ wallet: legacyWallet,
450
+ preferredNetwork,
451
+ rpcUrls = {},
452
+ maxAmountAtomic,
453
+ fetch: customFetch = globalThis.fetch,
454
+ verbose = false
455
+ } = config;
456
+ const log = verbose ? console.log.bind(console, "[x402]") : () => {
457
+ };
458
+ const wallets = walletSet || {};
459
+ if (legacyWallet && !wallets.solana && isSolanaWallet(legacyWallet)) {
460
+ wallets.solana = legacyWallet;
461
+ }
462
+ if (legacyWallet && !wallets.evm && isEvmWallet(legacyWallet)) {
463
+ wallets.evm = legacyWallet;
464
+ }
465
+ function findPaymentOption(accepts) {
466
+ const candidates = [];
467
+ for (const accept of accepts) {
468
+ const adapter = adapters.find((a) => a.canHandle(accept.network));
469
+ if (!adapter) continue;
470
+ let wallet;
471
+ if (adapter.name === "Solana") {
472
+ wallet = wallets.solana;
473
+ } else if (adapter.name === "EVM") {
474
+ wallet = wallets.evm;
475
+ }
476
+ if (wallet && adapter.isConnected(wallet)) {
477
+ candidates.push({ accept, adapter, wallet });
478
+ }
479
+ }
480
+ if (candidates.length === 0) {
481
+ return null;
482
+ }
483
+ if (preferredNetwork) {
484
+ const preferred = candidates.find((c) => c.accept.network === preferredNetwork);
485
+ if (preferred) return preferred;
486
+ }
487
+ return candidates[0];
488
+ }
489
+ function getRpcUrl(network, adapter) {
490
+ return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
491
+ }
492
+ async function x402Fetch(input, init) {
493
+ log("Making request:", input);
494
+ const response = await customFetch(input, init);
495
+ if (response.status !== 402) {
496
+ return response;
497
+ }
498
+ log("Received 402 Payment Required");
499
+ const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
500
+ if (!paymentRequiredHeader) {
501
+ throw new X402Error(
502
+ "missing_payment_required_header",
503
+ "Server returned 402 but no PAYMENT-REQUIRED header"
504
+ );
505
+ }
506
+ let requirements;
507
+ try {
508
+ const decoded = atob(paymentRequiredHeader);
509
+ requirements = JSON.parse(decoded);
510
+ } catch {
511
+ throw new X402Error(
512
+ "invalid_payment_required",
513
+ "Failed to decode PAYMENT-REQUIRED header"
514
+ );
515
+ }
516
+ log("Payment requirements:", requirements);
517
+ const match = findPaymentOption(requirements.accepts);
518
+ if (!match) {
519
+ const availableNetworks = requirements.accepts.map((a) => a.network).join(", ");
520
+ throw new X402Error(
521
+ "no_matching_payment_option",
522
+ `No connected wallet for any available network: ${availableNetworks}`
523
+ );
524
+ }
525
+ const { accept, adapter, wallet } = match;
526
+ log(`Using ${adapter.name} for ${accept.network}`);
527
+ if (adapter.name === "Solana" && !accept.extra?.feePayer) {
528
+ throw new X402Error(
529
+ "missing_fee_payer",
530
+ "Solana payment option missing feePayer in extra"
531
+ );
532
+ }
533
+ if (typeof accept.extra?.decimals !== "number") {
534
+ throw new X402Error(
535
+ "missing_decimals",
536
+ "Payment option missing decimals in extra"
537
+ );
538
+ }
539
+ if (maxAmountAtomic && BigInt(accept.amount) > BigInt(maxAmountAtomic)) {
540
+ throw new X402Error(
541
+ "amount_exceeds_max",
542
+ `Payment amount ${accept.amount} exceeds maximum ${maxAmountAtomic}`
543
+ );
544
+ }
545
+ log("Building transaction...");
546
+ const rpcUrl = getRpcUrl(accept.network, adapter);
547
+ const signedTx = await adapter.buildTransaction(accept, wallet, rpcUrl);
548
+ log("Transaction signed");
549
+ let payload;
550
+ if (adapter.name === "EVM") {
551
+ payload = JSON.parse(signedTx.serialized);
552
+ } else {
553
+ payload = { transaction: signedTx.serialized };
554
+ }
555
+ const paymentSignature = {
556
+ x402Version: 2,
557
+ resource: requirements.resource,
558
+ accepted: accept,
559
+ payload
560
+ };
561
+ const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
562
+ log("Retrying request with payment...");
563
+ const retryResponse = await customFetch(input, {
564
+ ...init,
565
+ headers: {
566
+ ...init?.headers || {},
567
+ "PAYMENT-SIGNATURE": paymentSignatureHeader
568
+ }
569
+ });
570
+ log("Retry response status:", retryResponse.status);
571
+ if (retryResponse.status === 402) {
572
+ let reason = "unknown";
573
+ try {
574
+ const body = await retryResponse.clone().json();
575
+ reason = String(body.error || body.message || JSON.stringify(body));
576
+ log("Rejection reason:", reason);
577
+ } catch {
578
+ }
579
+ throw new X402Error(
580
+ "payment_rejected",
581
+ `Payment was rejected by the server: ${reason}`
582
+ );
583
+ }
584
+ return retryResponse;
585
+ }
586
+ return {
587
+ fetch: x402Fetch
588
+ };
589
+ }
590
+
591
+ // src/utils.ts
592
+ function getChainFamily(network) {
593
+ if (network.startsWith("solana:") || network === "solana") {
594
+ return "solana";
595
+ }
596
+ if (network.startsWith("eip155:") || ["base", "ethereum", "arbitrum"].includes(network)) {
597
+ return "evm";
598
+ }
599
+ return "unknown";
600
+ }
601
+ function getChainName(network) {
602
+ const mapping = {
603
+ [SOLANA_MAINNET_NETWORK]: "Solana",
604
+ "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "Solana Devnet",
605
+ "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": "Solana Testnet",
606
+ "solana": "Solana",
607
+ [BASE_MAINNET_NETWORK]: "Base",
608
+ "eip155:84532": "Base Sepolia",
609
+ "eip155:1": "Ethereum",
610
+ "eip155:42161": "Arbitrum One",
611
+ "base": "Base",
612
+ "ethereum": "Ethereum",
613
+ "arbitrum": "Arbitrum"
614
+ };
615
+ return mapping[network] || network;
616
+ }
617
+ function getExplorerUrl(txSignature, network) {
618
+ const family = getChainFamily(network);
619
+ if (family === "solana") {
620
+ const isDevnet = network.includes("devnet") || network === "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
621
+ if (isDevnet) {
622
+ return `https://solscan.io/tx/${txSignature}?cluster=devnet`;
623
+ }
624
+ return `https://www.orbmarkets.io/tx/${txSignature}`;
625
+ }
626
+ if (family === "evm") {
627
+ let chainId = "8453";
628
+ if (network.startsWith("eip155:")) {
629
+ chainId = network.split(":")[1];
630
+ } else if (network === "ethereum") {
631
+ chainId = "1";
632
+ } else if (network === "arbitrum") {
633
+ chainId = "42161";
634
+ }
635
+ switch (chainId) {
636
+ case "8453":
637
+ return `https://basescan.org/tx/${txSignature}`;
638
+ case "84532":
639
+ return `https://sepolia.basescan.org/tx/${txSignature}`;
640
+ case "1":
641
+ return `https://etherscan.io/tx/${txSignature}`;
642
+ case "42161":
643
+ return `https://arbiscan.io/tx/${txSignature}`;
644
+ default:
645
+ return `https://basescan.org/tx/${txSignature}`;
646
+ }
647
+ }
648
+ return `https://solscan.io/tx/${txSignature}`;
649
+ }
650
+
651
+ // src/react/useX402Payment.ts
652
+ function useX402Payment(config) {
653
+ const {
654
+ wallets: walletSet,
655
+ wallet: legacyWallet,
656
+ preferredNetwork,
657
+ rpcUrls = {},
658
+ verbose = false
659
+ } = config;
660
+ const [isLoading, setIsLoading] = (0, import_react.useState)(false);
661
+ const [status, setStatus] = (0, import_react.useState)("idle");
662
+ const [error, setError] = (0, import_react.useState)(null);
663
+ const [transactionId, setTransactionId] = (0, import_react.useState)(null);
664
+ const [transactionNetwork, setTransactionNetwork] = (0, import_react.useState)(null);
665
+ const [balances, setBalances] = (0, import_react.useState)([]);
666
+ const log = (0, import_react.useCallback)((...args) => {
667
+ if (verbose) console.log("[useX402Payment]", ...args);
668
+ }, [verbose]);
669
+ const wallets = (0, import_react.useMemo)(() => {
670
+ const w = { ...walletSet };
671
+ if (legacyWallet && !w.solana && isSolanaWallet(legacyWallet)) {
672
+ w.solana = legacyWallet;
673
+ }
674
+ if (legacyWallet && !w.evm && isEvmWallet(legacyWallet)) {
675
+ w.evm = legacyWallet;
676
+ }
677
+ return w;
678
+ }, [walletSet, legacyWallet]);
679
+ const adapters = (0, import_react.useMemo)(() => [
680
+ createSolanaAdapter({ verbose, rpcUrls }),
681
+ createEvmAdapter({ verbose, rpcUrls })
682
+ ], [verbose, rpcUrls]);
683
+ const connectedChains = (0, import_react.useMemo)(() => ({
684
+ solana: wallets.solana ? isSolanaWallet(wallets.solana) && adapters[0].isConnected(wallets.solana) : false,
685
+ evm: wallets.evm ? isEvmWallet(wallets.evm) && adapters[1].isConnected(wallets.evm) : false
686
+ }), [wallets, adapters]);
687
+ const isAnyWalletConnected = connectedChains.solana || connectedChains.evm;
688
+ const refreshBalances = (0, import_react.useCallback)(async () => {
689
+ const newBalances = [];
690
+ if (connectedChains.solana && wallets.solana) {
691
+ try {
692
+ const solanaAdapter = adapters.find((a) => a.name === "Solana");
693
+ if (solanaAdapter) {
694
+ const accept = {
695
+ scheme: "exact",
696
+ network: SOLANA_MAINNET_NETWORK,
697
+ amount: "0",
698
+ asset: USDC_MINT,
699
+ payTo: "",
700
+ maxTimeoutSeconds: 60,
701
+ extra: { feePayer: "", decimals: 6 }
702
+ };
703
+ const balance = await solanaAdapter.getBalance(accept, wallets.solana);
704
+ newBalances.push({
705
+ network: SOLANA_MAINNET_NETWORK,
706
+ chainName: getChainName(SOLANA_MAINNET_NETWORK),
707
+ balance,
708
+ asset: "USDC"
709
+ });
710
+ }
711
+ } catch (e) {
712
+ log("Failed to fetch Solana balance:", e);
713
+ }
714
+ }
715
+ if (connectedChains.evm && wallets.evm) {
716
+ try {
717
+ const evmAdapter = adapters.find((a) => a.name === "EVM");
718
+ if (evmAdapter) {
719
+ const accept = {
720
+ scheme: "exact",
721
+ network: BASE_MAINNET_NETWORK,
722
+ amount: "0",
723
+ asset: USDC_BASE,
724
+ payTo: "",
725
+ maxTimeoutSeconds: 60,
726
+ extra: { feePayer: "", decimals: 6 }
727
+ };
728
+ const balance = await evmAdapter.getBalance(accept, wallets.evm);
729
+ newBalances.push({
730
+ network: BASE_MAINNET_NETWORK,
731
+ chainName: getChainName(BASE_MAINNET_NETWORK),
732
+ balance,
733
+ asset: "USDC"
734
+ });
735
+ }
736
+ } catch (e) {
737
+ log("Failed to fetch Base balance:", e);
738
+ }
739
+ }
740
+ setBalances(newBalances);
741
+ }, [connectedChains, wallets, adapters, log]);
742
+ (0, import_react.useEffect)(() => {
743
+ refreshBalances();
744
+ const interval = setInterval(refreshBalances, 3e4);
745
+ return () => clearInterval(interval);
746
+ }, [refreshBalances]);
747
+ const reset = (0, import_react.useCallback)(() => {
748
+ setIsLoading(false);
749
+ setStatus("idle");
750
+ setError(null);
751
+ setTransactionId(null);
752
+ setTransactionNetwork(null);
753
+ }, []);
754
+ const client = (0, import_react.useMemo)(() => createX402Client({
755
+ adapters,
756
+ wallets,
757
+ preferredNetwork,
758
+ rpcUrls,
759
+ verbose
760
+ }), [adapters, wallets, preferredNetwork, rpcUrls, verbose]);
761
+ const fetchWithPayment = (0, import_react.useCallback)(async (input, init) => {
762
+ setIsLoading(true);
763
+ setStatus("pending");
764
+ setError(null);
765
+ setTransactionId(null);
766
+ setTransactionNetwork(null);
767
+ if (!isAnyWalletConnected) {
768
+ const connError = new X402Error("wallet_not_connected", "No wallet connected");
769
+ setError(connError);
770
+ setStatus("error");
771
+ setIsLoading(false);
772
+ throw connError;
773
+ }
774
+ try {
775
+ const response = await client.fetch(input, init);
776
+ const paymentResponse = response.headers.get("PAYMENT-RESPONSE");
777
+ if (paymentResponse) {
778
+ try {
779
+ const decoded = JSON.parse(atob(paymentResponse));
780
+ if (decoded.transaction) {
781
+ setTransactionId(decoded.transaction);
782
+ }
783
+ if (decoded.network) {
784
+ setTransactionNetwork(decoded.network);
785
+ }
786
+ } catch {
787
+ log("Could not parse PAYMENT-RESPONSE header");
788
+ }
789
+ }
790
+ setStatus("success");
791
+ return response;
792
+ } catch (err) {
793
+ const error2 = err instanceof Error ? err : new Error(String(err));
794
+ setError(error2);
795
+ setStatus("error");
796
+ throw err;
797
+ } finally {
798
+ setIsLoading(false);
799
+ setTimeout(refreshBalances, 2e3);
800
+ }
801
+ }, [client, isAnyWalletConnected, log, refreshBalances]);
802
+ const transactionUrl = (0, import_react.useMemo)(() => {
803
+ if (!transactionId) return null;
804
+ const network = transactionNetwork || preferredNetwork || SOLANA_MAINNET_NETWORK;
805
+ return getExplorerUrl(transactionId, network);
806
+ }, [transactionId, transactionNetwork, preferredNetwork]);
807
+ return {
808
+ fetch: fetchWithPayment,
809
+ isLoading,
810
+ status,
811
+ error,
812
+ transactionId,
813
+ transactionNetwork,
814
+ transactionUrl,
815
+ balances,
816
+ connectedChains,
817
+ isAnyWalletConnected,
818
+ reset,
819
+ refreshBalances
820
+ };
821
+ }
822
+ // Annotate the CommonJS export names for ESM import in node:
823
+ 0 && (module.exports = {
824
+ X402Error,
825
+ useX402Payment
826
+ });
827
+ //# sourceMappingURL=index.cjs.map