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