@alleyboss/micropay-solana-x402-paywall 3.2.1 → 3.3.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 CHANGED
@@ -4,7 +4,7 @@ export * from '@x402/core/client';
4
4
  export * from '@x402/svm';
5
5
  import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from '@x402/core/http';
6
6
  import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
7
- import { useState, useCallback, useEffect } from 'react';
7
+ import { useState, useCallback, useEffect, useRef } from 'react';
8
8
  import bs58 from 'bs58';
9
9
  import { SignJWT, jwtVerify } from 'jose';
10
10
 
@@ -41,8 +41,8 @@ function buildSolanaPayUrl(params) {
41
41
  }
42
42
  return url.toString();
43
43
  }
44
- function createPaymentFlow(config2) {
45
- const { network, recipientWallet, amount, asset = "native", memo } = config2;
44
+ function createPaymentFlow(config) {
45
+ const { network, recipientWallet, amount, asset = "native", memo } = config;
46
46
  let decimals = 9;
47
47
  let mintAddress;
48
48
  if (asset === "usdc") {
@@ -58,7 +58,7 @@ function createPaymentFlow(config2) {
58
58
  const naturalAmount = Number(amount) / Math.pow(10, decimals);
59
59
  return {
60
60
  /** Get the payment configuration */
61
- getConfig: () => ({ ...config2 }),
61
+ getConfig: () => ({ ...config }),
62
62
  /** Get amount in natural display units (e.g., 0.01 SOL) */
63
63
  getDisplayAmount: () => naturalAmount,
64
64
  /** Get amount formatted with symbol */
@@ -246,10 +246,265 @@ function usePaywallResource({
246
246
  unlock
247
247
  };
248
248
  }
249
+
250
+ // src/pricing/utils.ts
251
+ function lamportsToSol(lamports) {
252
+ return Number(lamports) / 1e9;
253
+ }
254
+
255
+ // src/pricing/index.ts
256
+ var priceCache = null;
257
+ var currentConfig = {};
258
+ function configurePricing(newConfig) {
259
+ currentConfig = { ...currentConfig, ...newConfig };
260
+ }
261
+ var PROVIDERS = [
262
+ {
263
+ name: "coincap",
264
+ url: "https://api.coincap.io/v2/assets/solana",
265
+ parse: (data) => parseFloat(data.data?.priceUsd || "0")
266
+ },
267
+ {
268
+ name: "binance",
269
+ url: "https://api.binance.com/api/v3/ticker/price?symbol=SOLUSDT",
270
+ parse: (data) => parseFloat(data.price || "0")
271
+ },
272
+ {
273
+ name: "coingecko",
274
+ url: "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
275
+ parse: (data) => data.solana?.usd || 0
276
+ },
277
+ {
278
+ name: "kraken",
279
+ url: "https://api.kraken.com/0/public/Ticker?pair=SOLUSD",
280
+ parse: (data) => parseFloat(data.result?.SOLUSD?.c?.[0] || "0")
281
+ }
282
+ ];
283
+ async function fetchFromProvider(provider, timeout) {
284
+ const controller = new AbortController();
285
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
286
+ try {
287
+ const response = await fetch(provider.url, {
288
+ headers: { "Accept": "application/json" },
289
+ signal: controller.signal
290
+ });
291
+ if (!response.ok) {
292
+ throw new Error(`HTTP ${response.status}`);
293
+ }
294
+ const data = await response.json();
295
+ const price = provider.parse(data);
296
+ if (!price || price <= 0) {
297
+ throw new Error("Invalid price");
298
+ }
299
+ return { price, source: provider.name };
300
+ } finally {
301
+ clearTimeout(timeoutId);
302
+ }
303
+ }
304
+ async function fetchPriceParallel(timeout) {
305
+ const promises = PROVIDERS.map(
306
+ (provider) => fetchFromProvider(provider, timeout).catch(() => null)
307
+ );
308
+ const results = await Promise.all(promises);
309
+ const validResult = results.find((r) => r !== null);
310
+ if (validResult) {
311
+ return validResult;
312
+ }
313
+ throw new Error("All providers failed");
314
+ }
315
+ async function fetchPriceSequential(timeout) {
316
+ for (const provider of PROVIDERS) {
317
+ try {
318
+ return await fetchFromProvider(provider, timeout);
319
+ } catch {
320
+ continue;
321
+ }
322
+ }
323
+ throw new Error("All providers failed");
324
+ }
325
+ async function getSolPrice() {
326
+ const cacheTTL = currentConfig.cacheTTL ?? 6e4;
327
+ const timeout = currentConfig.timeout ?? 3e3;
328
+ const useParallel = currentConfig.parallelFetch ?? true;
329
+ const now = Date.now();
330
+ if (priceCache && now - priceCache.timestamp < cacheTTL) {
331
+ return priceCache.data;
332
+ }
333
+ if (currentConfig.customProvider) {
334
+ try {
335
+ const price = await currentConfig.customProvider();
336
+ if (price > 0) {
337
+ const data = {
338
+ solPrice: price,
339
+ fetchedAt: /* @__PURE__ */ new Date(),
340
+ source: "custom"
341
+ };
342
+ priceCache = { data, timestamp: now };
343
+ return data;
344
+ }
345
+ } catch {
346
+ }
347
+ }
348
+ try {
349
+ const result = useParallel ? await fetchPriceParallel(timeout) : await fetchPriceSequential(timeout);
350
+ const data = {
351
+ solPrice: result.price,
352
+ fetchedAt: /* @__PURE__ */ new Date(),
353
+ source: result.source
354
+ };
355
+ priceCache = { data, timestamp: now };
356
+ return data;
357
+ } catch {
358
+ if (priceCache) {
359
+ return {
360
+ ...priceCache.data,
361
+ source: `${priceCache.data.source} (stale)`
362
+ };
363
+ }
364
+ throw new Error(
365
+ "Failed to fetch SOL price from all providers. Configure a custom provider or ensure network connectivity."
366
+ );
367
+ }
368
+ }
369
+ async function lamportsToUsd(lamports) {
370
+ const { solPrice } = await getSolPrice();
371
+ const sol = Number(lamports) / 1e9;
372
+ return sol * solPrice;
373
+ }
374
+ async function usdToLamports(usd) {
375
+ const { solPrice } = await getSolPrice();
376
+ const sol = usd / solPrice;
377
+ return BigInt(Math.floor(sol * 1e9));
378
+ }
379
+ async function formatPriceDisplay(lamports) {
380
+ const { solPrice } = await getSolPrice();
381
+ const sol = Number(lamports) / 1e9;
382
+ const usd = sol * solPrice;
383
+ return `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`;
384
+ }
385
+ function formatPriceSync(lamports, solPrice) {
386
+ const sol = Number(lamports) / 1e9;
387
+ const usd = sol * solPrice;
388
+ return {
389
+ sol,
390
+ usd,
391
+ formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`
392
+ };
393
+ }
394
+ function clearPriceCache() {
395
+ priceCache = null;
396
+ }
397
+ function getProviders() {
398
+ return PROVIDERS.map((p) => ({ name: p.name, url: p.url }));
399
+ }
400
+
401
+ // src/client/hooks.ts
402
+ function usePricing(refreshIntervalMs = 6e4) {
403
+ const [priceData, setPriceData] = useState(null);
404
+ const [isLoading, setIsLoading] = useState(true);
405
+ const [error, setError] = useState(null);
406
+ const intervalRef = useRef(null);
407
+ const fetchPrice = useCallback(async () => {
408
+ try {
409
+ setIsLoading(true);
410
+ setError(null);
411
+ const data = await getSolPrice();
412
+ setPriceData(data);
413
+ } catch (err) {
414
+ setError(err instanceof Error ? err.message : "Failed to fetch price");
415
+ } finally {
416
+ setIsLoading(false);
417
+ }
418
+ }, []);
419
+ useEffect(() => {
420
+ fetchPrice();
421
+ if (refreshIntervalMs > 0) {
422
+ intervalRef.current = setInterval(fetchPrice, refreshIntervalMs);
423
+ }
424
+ return () => {
425
+ if (intervalRef.current) {
426
+ clearInterval(intervalRef.current);
427
+ }
428
+ };
429
+ }, [fetchPrice, refreshIntervalMs]);
430
+ return {
431
+ solPrice: priceData?.solPrice ?? null,
432
+ source: priceData?.source ?? null,
433
+ fetchedAt: priceData?.fetchedAt ?? null,
434
+ isLoading,
435
+ error,
436
+ refresh: fetchPrice
437
+ };
438
+ }
439
+ function useLamportsToUsd(lamports) {
440
+ const { solPrice, isLoading } = usePricing();
441
+ if (isLoading || !solPrice || lamports === null) {
442
+ return { usd: null, formatted: null, isLoading };
443
+ }
444
+ const lamportsBigInt = typeof lamports === "number" ? BigInt(lamports) : lamports;
445
+ const sol = Number(lamportsBigInt) / 1e9;
446
+ const usd = sol * solPrice;
447
+ return {
448
+ usd,
449
+ formatted: `$${usd.toFixed(2)}`,
450
+ isLoading: false
451
+ };
452
+ }
453
+ function useMicropay() {
454
+ const [status, setStatus] = useState("idle");
455
+ const [error, setError] = useState(null);
456
+ const [signature, setSignature] = useState(null);
457
+ const reset = useCallback(() => {
458
+ setStatus("idle");
459
+ setError(null);
460
+ setSignature(null);
461
+ }, []);
462
+ const pay = useCallback(async (_options) => {
463
+ setStatus("pending");
464
+ setError(null);
465
+ try {
466
+ throw new Error(
467
+ "useMicropay requires implementation of onSign/onSend callbacks. See documentation for wallet adapter integration."
468
+ );
469
+ } catch (err) {
470
+ const errorMessage = err instanceof Error ? err.message : "Payment failed";
471
+ setError(errorMessage);
472
+ setStatus("error");
473
+ return { success: false, error: errorMessage };
474
+ }
475
+ }, []);
476
+ return {
477
+ status,
478
+ error,
479
+ signature,
480
+ pay,
481
+ reset
482
+ };
483
+ }
484
+ function useFormatPrice(lamports) {
485
+ const { solPrice, isLoading } = usePricing();
486
+ if (isLoading || !solPrice || lamports === null) {
487
+ return {
488
+ sol: null,
489
+ usd: null,
490
+ formatted: "Loading...",
491
+ isLoading
492
+ };
493
+ }
494
+ const lamportsBigInt = typeof lamports === "number" ? BigInt(lamports) : lamports;
495
+ const sol = Number(lamportsBigInt) / 1e9;
496
+ const usd = sol * solPrice;
497
+ return {
498
+ sol,
499
+ usd,
500
+ formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`,
501
+ isLoading: false
502
+ };
503
+ }
249
504
  var DEFAULT_COMPUTE_UNITS = 2e5;
250
505
  var DEFAULT_MICRO_LAMPORTS = 1e3;
251
- function createPriorityFeeInstructions(config2 = {}) {
252
- const { enabled = false, microLamports, computeUnits } = config2;
506
+ function createPriorityFeeInstructions(config = {}) {
507
+ const { enabled = false, microLamports, computeUnits } = config;
253
508
  if (!enabled) {
254
509
  return [];
255
510
  }
@@ -260,14 +515,14 @@ function createPriorityFeeInstructions(config2 = {}) {
260
515
  instructions.push(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: price }));
261
516
  return instructions;
262
517
  }
263
- async function buildVersionedTransaction(config2) {
518
+ async function buildVersionedTransaction(config) {
264
519
  const {
265
520
  connection,
266
521
  payer,
267
522
  instructions,
268
523
  priorityFee,
269
524
  recentBlockhash
270
- } = config2;
525
+ } = config;
271
526
  const priorityIxs = createPriorityFeeInstructions(priorityFee);
272
527
  const allInstructions = [...priorityIxs, ...instructions];
273
528
  let blockhash;
@@ -383,9 +638,9 @@ async function getAgentBalance(connection, agentKeypair) {
383
638
  balanceSol: balance / LAMPORTS_PER_SOL
384
639
  };
385
640
  }
386
- async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports) {
641
+ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports, feeBufferLamports = 5000000n) {
387
642
  const { balance } = await getAgentBalance(connection, agentKeypair);
388
- const totalRequired = requiredLamports + 10000n;
643
+ const totalRequired = requiredLamports + feeBufferLamports;
389
644
  return {
390
645
  sufficient: balance >= totalRequired,
391
646
  balance,
@@ -434,20 +689,20 @@ function validateWalletAddress(address) {
434
689
  const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
435
690
  return base58Regex.test(address);
436
691
  }
437
- async function createCreditSession(walletAddress, purchaseId, config2) {
692
+ async function createCreditSession(walletAddress, purchaseId, config) {
438
693
  if (!validateWalletAddress(walletAddress)) {
439
694
  throw new Error("Invalid wallet address format");
440
695
  }
441
- if (config2.initialCredits <= 0 || config2.initialCredits > MAX_CREDITS) {
696
+ if (config.initialCredits <= 0 || config.initialCredits > MAX_CREDITS) {
442
697
  throw new Error(`Credits must be between 1 and ${MAX_CREDITS}`);
443
698
  }
444
- if (!config2.durationHours || config2.durationHours <= 0 || config2.durationHours > 8760) {
699
+ if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 8760) {
445
700
  throw new Error("Session duration must be between 1 and 8760 hours (1 year)");
446
701
  }
447
702
  const sessionId = uuidv4();
448
703
  const now = Math.floor(Date.now() / 1e3);
449
- const expiresAt = now + config2.durationHours * 3600;
450
- const bundleExpiry = config2.bundleExpiryHours ? now + config2.bundleExpiryHours * 3600 : expiresAt;
704
+ const expiresAt = now + config.durationHours * 3600;
705
+ const bundleExpiry = config.bundleExpiryHours ? now + config.bundleExpiryHours * 3600 : expiresAt;
451
706
  const session = {
452
707
  id: sessionId,
453
708
  walletAddress,
@@ -455,22 +710,22 @@ async function createCreditSession(walletAddress, purchaseId, config2) {
455
710
  siteWideUnlock: false,
456
711
  createdAt: now,
457
712
  expiresAt,
458
- credits: config2.initialCredits,
713
+ credits: config.initialCredits,
459
714
  bundleExpiry,
460
- bundleType: config2.bundleType
715
+ bundleType: config.bundleType
461
716
  };
462
717
  const payload = {
463
718
  sub: walletAddress,
464
719
  sid: sessionId,
465
720
  articles: session.unlockedArticles,
466
721
  siteWide: false,
467
- credits: config2.initialCredits,
722
+ credits: config.initialCredits,
468
723
  bundleExpiry,
469
- bundleType: config2.bundleType,
724
+ bundleType: config.bundleType,
470
725
  iat: now,
471
726
  exp: expiresAt
472
727
  };
473
- const token = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(`${config2.durationHours}h`).sign(getSecretKey(config2.secret));
728
+ const token = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(`${config.durationHours}h`).sign(getSecretKey(config.secret));
474
729
  return { token, session };
475
730
  }
476
731
  async function validateCreditSession(token, secret) {
@@ -588,139 +843,4 @@ async function getRemainingCredits(token, secret) {
588
843
  };
589
844
  }
590
845
 
591
- // src/pricing/utils.ts
592
- function lamportsToSol(lamports) {
593
- return Number(lamports) / 1e9;
594
- }
595
-
596
- // src/pricing/index.ts
597
- var cachedPrice = null;
598
- var config = {};
599
- var lastProviderIndex = -1;
600
- function configurePricing(newConfig) {
601
- config = { ...config, ...newConfig };
602
- cachedPrice = null;
603
- }
604
- var PROVIDERS = [
605
- {
606
- name: "coincap",
607
- url: "https://api.coincap.io/v2/assets/solana",
608
- parse: (data) => parseFloat(data.data?.priceUsd || "0")
609
- },
610
- {
611
- name: "binance",
612
- url: "https://api.binance.com/api/v3/ticker/price?symbol=SOLUSDT",
613
- parse: (data) => parseFloat(data.price || "0")
614
- },
615
- {
616
- name: "coingecko",
617
- url: "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
618
- parse: (data) => data.solana?.usd || 0
619
- },
620
- {
621
- name: "kraken",
622
- url: "https://api.kraken.com/0/public/Ticker?pair=SOLUSD",
623
- parse: (data) => parseFloat(data.result?.SOLUSD?.c?.[0] || "0")
624
- }
625
- ];
626
- async function fetchFromProvider(provider, timeout) {
627
- const controller = new AbortController();
628
- const timeoutId = setTimeout(() => controller.abort(), timeout);
629
- try {
630
- const response = await fetch(provider.url, {
631
- headers: { "Accept": "application/json" },
632
- signal: controller.signal
633
- });
634
- if (!response.ok) {
635
- throw new Error(`HTTP ${response.status}`);
636
- }
637
- const data = await response.json();
638
- const price = provider.parse(data);
639
- if (!price || price <= 0) {
640
- throw new Error("Invalid price");
641
- }
642
- return price;
643
- } finally {
644
- clearTimeout(timeoutId);
645
- }
646
- }
647
- async function getSolPrice() {
648
- const cacheTTL = config.cacheTTL ?? 6e4;
649
- const timeout = config.timeout ?? 5e3;
650
- if (cachedPrice && Date.now() - cachedPrice.fetchedAt.getTime() < cacheTTL) {
651
- return cachedPrice;
652
- }
653
- if (config.customProvider) {
654
- try {
655
- const price = await config.customProvider();
656
- if (price > 0) {
657
- cachedPrice = {
658
- solPrice: price,
659
- fetchedAt: /* @__PURE__ */ new Date(),
660
- source: "custom"
661
- };
662
- return cachedPrice;
663
- }
664
- } catch {
665
- }
666
- }
667
- for (let i = 0; i < PROVIDERS.length; i++) {
668
- const idx = (lastProviderIndex + 1 + i) % PROVIDERS.length;
669
- const provider = PROVIDERS[idx];
670
- try {
671
- const price = await fetchFromProvider(provider, timeout);
672
- lastProviderIndex = idx;
673
- cachedPrice = {
674
- solPrice: price,
675
- fetchedAt: /* @__PURE__ */ new Date(),
676
- source: provider.name
677
- };
678
- return cachedPrice;
679
- } catch {
680
- continue;
681
- }
682
- }
683
- if (cachedPrice) {
684
- return {
685
- ...cachedPrice,
686
- source: `${cachedPrice.source} (stale)`
687
- };
688
- }
689
- throw new Error(
690
- "Failed to fetch SOL price from all providers. Configure a custom provider or ensure network connectivity."
691
- );
692
- }
693
- async function lamportsToUsd(lamports) {
694
- const { solPrice } = await getSolPrice();
695
- const sol = Number(lamports) / 1e9;
696
- return sol * solPrice;
697
- }
698
- async function usdToLamports(usd) {
699
- const { solPrice } = await getSolPrice();
700
- const sol = usd / solPrice;
701
- return BigInt(Math.floor(sol * 1e9));
702
- }
703
- async function formatPriceDisplay(lamports) {
704
- const { solPrice } = await getSolPrice();
705
- const sol = Number(lamports) / 1e9;
706
- const usd = sol * solPrice;
707
- return `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`;
708
- }
709
- function formatPriceSync(lamports, solPrice) {
710
- const sol = Number(lamports) / 1e9;
711
- const usd = sol * solPrice;
712
- return {
713
- sol,
714
- usd,
715
- formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`
716
- };
717
- }
718
- function clearPriceCache() {
719
- cachedPrice = null;
720
- lastProviderIndex = -1;
721
- }
722
- function getProviders() {
723
- return PROVIDERS.map((p) => ({ name: p.name, url: p.url }));
724
- }
725
-
726
- export { addCredits, buildSolanaPayUrl, clearPriceCache, configurePricing, createCreditSession, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, executeAgentPayment, formatPriceDisplay, formatPriceSync, generateAgentKeypair, getAgentBalance, getProviders, getRemainingCredits, getSolPrice, hasAgentSufficientBalance, keypairFromBase58, lamportsToSol, lamportsToUsd, sendSolanaPayment, usdToLamports, useCredit, usePaywallResource, validateCreditSession };
846
+ export { addCredits, buildSolanaPayUrl, clearPriceCache, configurePricing, createCreditSession, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, executeAgentPayment, formatPriceDisplay, formatPriceSync, generateAgentKeypair, getAgentBalance, getProviders, getRemainingCredits, getSolPrice, hasAgentSufficientBalance, keypairFromBase58, lamportsToSol, lamportsToUsd, sendSolanaPayment, usdToLamports, useCredit, useFormatPrice, useLamportsToUsd, useMicropay, usePaywallResource, usePricing, validateCreditSession };