@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/README.md CHANGED
@@ -166,6 +166,36 @@ if (result.success) {
166
166
  }
167
167
  ```
168
168
 
169
+ ## 🎣 React Hooks (New in v3.2.2)
170
+
171
+ ```typescript
172
+ import { usePricing, useFormatPrice } from '@alleyboss/micropay-solana-x402-paywall/client';
173
+
174
+ function PaywallBanner({ priceInLamports }) {
175
+ const { formatted, isLoading } = useFormatPrice(priceInLamports);
176
+
177
+ return (
178
+ <div className="paywall">
179
+ <span>Unlock for {isLoading ? '...' : formatted}</span>
180
+ </div>
181
+ );
182
+ }
183
+ ```
184
+
185
+ ## πŸ—ΊοΈ Roadmap
186
+
187
+ We're actively working on these exciting features:
188
+
189
+ | Feature | Status | Description |
190
+ |---------|--------|-------------|
191
+ | ⚑ **Jupiter Swap-on-Pay** | πŸ”œ Coming Soon | Pay with any token, auto-swap to SOL/USDC |
192
+ | πŸ–ΌοΈ **NFT/Token-Gating** | πŸ”œ Coming Soon | Verify NFT ownership for access discounts |
193
+ | πŸ“Š **Payment Analytics** | πŸ”œ Coming Soon | Webhooks & callbacks for payment events |
194
+ | 🌳 **Compressed NFTs** | πŸ”œ Planned | Scalable access tokens via cNFTs |
195
+ | πŸ”„ **Payment Streaming** | πŸ”œ Planned | Pay-as-you-consume for APIs |
196
+
197
+ Want to contribute or sponsor a feature? Open an issue on [GitHub](https://github.com/AlleyBo55/micropay-solana-x402-paywall)!
198
+
169
199
  ## πŸ“š Documentation
170
200
 
171
201
  For full documentation:
@@ -146,9 +146,9 @@ async function getAgentBalance(connection, agentKeypair) {
146
146
  balanceSol: balance / web3_js.LAMPORTS_PER_SOL
147
147
  };
148
148
  }
149
- async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports) {
149
+ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports, feeBufferLamports = 5000000n) {
150
150
  const { balance } = await getAgentBalance(connection, agentKeypair);
151
- const totalRequired = requiredLamports + 10000n;
151
+ const totalRequired = requiredLamports + feeBufferLamports;
152
152
  return {
153
153
  sufficient: balance >= totalRequired,
154
154
  balance,
@@ -88,7 +88,7 @@ declare function getAgentBalance(connection: Connection, agentKeypair: Keypair):
88
88
  /**
89
89
  * Check if agent has sufficient balance for a payment
90
90
  */
91
- declare function hasAgentSufficientBalance(connection: Connection, agentKeypair: Keypair, requiredLamports: bigint): Promise<{
91
+ declare function hasAgentSufficientBalance(connection: Connection, agentKeypair: Keypair, requiredLamports: bigint, feeBufferLamports?: bigint): Promise<{
92
92
  sufficient: boolean;
93
93
  balance: bigint;
94
94
  required: bigint;
@@ -88,7 +88,7 @@ declare function getAgentBalance(connection: Connection, agentKeypair: Keypair):
88
88
  /**
89
89
  * Check if agent has sufficient balance for a payment
90
90
  */
91
- declare function hasAgentSufficientBalance(connection: Connection, agentKeypair: Keypair, requiredLamports: bigint): Promise<{
91
+ declare function hasAgentSufficientBalance(connection: Connection, agentKeypair: Keypair, requiredLamports: bigint, feeBufferLamports?: bigint): Promise<{
92
92
  sufficient: boolean;
93
93
  balance: bigint;
94
94
  required: bigint;
@@ -140,9 +140,9 @@ async function getAgentBalance(connection, agentKeypair) {
140
140
  balanceSol: balance / LAMPORTS_PER_SOL
141
141
  };
142
142
  }
143
- async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports) {
143
+ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports, feeBufferLamports = 5000000n) {
144
144
  const { balance } = await getAgentBalance(connection, agentKeypair);
145
- const totalRequired = requiredLamports + 10000n;
145
+ const totalRequired = requiredLamports + feeBufferLamports;
146
146
  return {
147
147
  sufficient: balance >= totalRequired,
148
148
  balance,
@@ -241,9 +241,229 @@ function usePaywallResource({
241
241
  };
242
242
  }
243
243
 
244
+ // src/pricing/index.ts
245
+ var priceCache = null;
246
+ var currentConfig = {};
247
+ var PROVIDERS = [
248
+ {
249
+ name: "coincap",
250
+ url: "https://api.coincap.io/v2/assets/solana",
251
+ parse: (data) => parseFloat(data.data?.priceUsd || "0")
252
+ },
253
+ {
254
+ name: "binance",
255
+ url: "https://api.binance.com/api/v3/ticker/price?symbol=SOLUSDT",
256
+ parse: (data) => parseFloat(data.price || "0")
257
+ },
258
+ {
259
+ name: "coingecko",
260
+ url: "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
261
+ parse: (data) => data.solana?.usd || 0
262
+ },
263
+ {
264
+ name: "kraken",
265
+ url: "https://api.kraken.com/0/public/Ticker?pair=SOLUSD",
266
+ parse: (data) => parseFloat(data.result?.SOLUSD?.c?.[0] || "0")
267
+ }
268
+ ];
269
+ async function fetchFromProvider(provider, timeout) {
270
+ const controller = new AbortController();
271
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
272
+ try {
273
+ const response = await fetch(provider.url, {
274
+ headers: { "Accept": "application/json" },
275
+ signal: controller.signal
276
+ });
277
+ if (!response.ok) {
278
+ throw new Error(`HTTP ${response.status}`);
279
+ }
280
+ const data = await response.json();
281
+ const price = provider.parse(data);
282
+ if (!price || price <= 0) {
283
+ throw new Error("Invalid price");
284
+ }
285
+ return { price, source: provider.name };
286
+ } finally {
287
+ clearTimeout(timeoutId);
288
+ }
289
+ }
290
+ async function fetchPriceParallel(timeout) {
291
+ const promises = PROVIDERS.map(
292
+ (provider) => fetchFromProvider(provider, timeout).catch(() => null)
293
+ );
294
+ const results = await Promise.all(promises);
295
+ const validResult = results.find((r) => r !== null);
296
+ if (validResult) {
297
+ return validResult;
298
+ }
299
+ throw new Error("All providers failed");
300
+ }
301
+ async function fetchPriceSequential(timeout) {
302
+ for (const provider of PROVIDERS) {
303
+ try {
304
+ return await fetchFromProvider(provider, timeout);
305
+ } catch {
306
+ continue;
307
+ }
308
+ }
309
+ throw new Error("All providers failed");
310
+ }
311
+ async function getSolPrice() {
312
+ const cacheTTL = currentConfig.cacheTTL ?? 6e4;
313
+ const timeout = currentConfig.timeout ?? 3e3;
314
+ const useParallel = currentConfig.parallelFetch ?? true;
315
+ const now = Date.now();
316
+ if (priceCache && now - priceCache.timestamp < cacheTTL) {
317
+ return priceCache.data;
318
+ }
319
+ if (currentConfig.customProvider) {
320
+ try {
321
+ const price = await currentConfig.customProvider();
322
+ if (price > 0) {
323
+ const data = {
324
+ solPrice: price,
325
+ fetchedAt: /* @__PURE__ */ new Date(),
326
+ source: "custom"
327
+ };
328
+ priceCache = { data, timestamp: now };
329
+ return data;
330
+ }
331
+ } catch {
332
+ }
333
+ }
334
+ try {
335
+ const result = useParallel ? await fetchPriceParallel(timeout) : await fetchPriceSequential(timeout);
336
+ const data = {
337
+ solPrice: result.price,
338
+ fetchedAt: /* @__PURE__ */ new Date(),
339
+ source: result.source
340
+ };
341
+ priceCache = { data, timestamp: now };
342
+ return data;
343
+ } catch {
344
+ if (priceCache) {
345
+ return {
346
+ ...priceCache.data,
347
+ source: `${priceCache.data.source} (stale)`
348
+ };
349
+ }
350
+ throw new Error(
351
+ "Failed to fetch SOL price from all providers. Configure a custom provider or ensure network connectivity."
352
+ );
353
+ }
354
+ }
355
+
356
+ // src/client/hooks.ts
357
+ function usePricing(refreshIntervalMs = 6e4) {
358
+ const [priceData, setPriceData] = react.useState(null);
359
+ const [isLoading, setIsLoading] = react.useState(true);
360
+ const [error, setError] = react.useState(null);
361
+ const intervalRef = react.useRef(null);
362
+ const fetchPrice = react.useCallback(async () => {
363
+ try {
364
+ setIsLoading(true);
365
+ setError(null);
366
+ const data = await getSolPrice();
367
+ setPriceData(data);
368
+ } catch (err) {
369
+ setError(err instanceof Error ? err.message : "Failed to fetch price");
370
+ } finally {
371
+ setIsLoading(false);
372
+ }
373
+ }, []);
374
+ react.useEffect(() => {
375
+ fetchPrice();
376
+ if (refreshIntervalMs > 0) {
377
+ intervalRef.current = setInterval(fetchPrice, refreshIntervalMs);
378
+ }
379
+ return () => {
380
+ if (intervalRef.current) {
381
+ clearInterval(intervalRef.current);
382
+ }
383
+ };
384
+ }, [fetchPrice, refreshIntervalMs]);
385
+ return {
386
+ solPrice: priceData?.solPrice ?? null,
387
+ source: priceData?.source ?? null,
388
+ fetchedAt: priceData?.fetchedAt ?? null,
389
+ isLoading,
390
+ error,
391
+ refresh: fetchPrice
392
+ };
393
+ }
394
+ function useLamportsToUsd(lamports) {
395
+ const { solPrice, isLoading } = usePricing();
396
+ if (isLoading || !solPrice || lamports === null) {
397
+ return { usd: null, formatted: null, isLoading };
398
+ }
399
+ const lamportsBigInt = typeof lamports === "number" ? BigInt(lamports) : lamports;
400
+ const sol = Number(lamportsBigInt) / 1e9;
401
+ const usd = sol * solPrice;
402
+ return {
403
+ usd,
404
+ formatted: `$${usd.toFixed(2)}`,
405
+ isLoading: false
406
+ };
407
+ }
408
+ function useMicropay() {
409
+ const [status, setStatus] = react.useState("idle");
410
+ const [error, setError] = react.useState(null);
411
+ const [signature, setSignature] = react.useState(null);
412
+ const reset = react.useCallback(() => {
413
+ setStatus("idle");
414
+ setError(null);
415
+ setSignature(null);
416
+ }, []);
417
+ const pay = react.useCallback(async (_options) => {
418
+ setStatus("pending");
419
+ setError(null);
420
+ try {
421
+ throw new Error(
422
+ "useMicropay requires implementation of onSign/onSend callbacks. See documentation for wallet adapter integration."
423
+ );
424
+ } catch (err) {
425
+ const errorMessage = err instanceof Error ? err.message : "Payment failed";
426
+ setError(errorMessage);
427
+ setStatus("error");
428
+ return { success: false, error: errorMessage };
429
+ }
430
+ }, []);
431
+ return {
432
+ status,
433
+ error,
434
+ signature,
435
+ pay,
436
+ reset
437
+ };
438
+ }
439
+ function useFormatPrice(lamports) {
440
+ const { solPrice, isLoading } = usePricing();
441
+ if (isLoading || !solPrice || lamports === null) {
442
+ return {
443
+ sol: null,
444
+ usd: null,
445
+ formatted: "Loading...",
446
+ isLoading
447
+ };
448
+ }
449
+ const lamportsBigInt = typeof lamports === "number" ? BigInt(lamports) : lamports;
450
+ const sol = Number(lamportsBigInt) / 1e9;
451
+ const usd = sol * solPrice;
452
+ return {
453
+ sol,
454
+ usd,
455
+ formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`,
456
+ isLoading: false
457
+ };
458
+ }
459
+
244
460
  exports.buildSolanaPayUrl = buildSolanaPayUrl;
245
461
  exports.createPaymentFlow = createPaymentFlow;
246
462
  exports.createPaymentReference = createPaymentReference;
247
463
  exports.createX402AuthorizationHeader = createX402AuthorizationHeader;
248
464
  exports.sendSolanaPayment = sendSolanaPayment;
465
+ exports.useFormatPrice = useFormatPrice;
466
+ exports.useLamportsToUsd = useLamportsToUsd;
467
+ exports.useMicropay = useMicropay;
249
468
  exports.usePaywallResource = usePaywallResource;
469
+ exports.usePricing = usePricing;
@@ -219,4 +219,104 @@ interface PaywallState<T> {
219
219
  */
220
220
  declare function usePaywallResource<T = any>({ url, connection, wallet }: PaywallConfig): PaywallState<T>;
221
221
 
222
- export { type PaymentFlowConfig, type PaymentResult, type PaywallConfig, type PaywallState, type SendPaymentParams, type SolanaPayUrlParams, type WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource };
222
+ /**
223
+ * Hook to fetch and display SOL price with auto-refresh
224
+ *
225
+ * @example
226
+ * ```tsx
227
+ * function PriceDisplay() {
228
+ * const { solPrice, source, isLoading, error, refresh } = usePricing();
229
+ *
230
+ * if (isLoading) return <span>Loading...</span>;
231
+ * if (error) return <span>Price unavailable</span>;
232
+ *
233
+ * return <span>SOL: ${solPrice?.toFixed(2)} (via {source})</span>;
234
+ * }
235
+ * ```
236
+ */
237
+ declare function usePricing(refreshIntervalMs?: number): {
238
+ solPrice: number | null;
239
+ source: string | null;
240
+ fetchedAt: Date | null;
241
+ isLoading: boolean;
242
+ error: string | null;
243
+ refresh: () => Promise<void>;
244
+ };
245
+ /**
246
+ * Format lamports to USD display with live price
247
+ */
248
+ declare function useLamportsToUsd(lamports: bigint | number | null): {
249
+ usd: null;
250
+ formatted: null;
251
+ isLoading: boolean;
252
+ } | {
253
+ usd: number;
254
+ formatted: string;
255
+ isLoading: boolean;
256
+ };
257
+ /**
258
+ * Payment status for useMicropay hook
259
+ */
260
+ type PaymentStatus = 'idle' | 'pending' | 'confirming' | 'success' | 'error';
261
+ /**
262
+ * Hook for client-side micropayment flow state management
263
+ *
264
+ * @example
265
+ * ```tsx
266
+ * function PayButton({ articleId, priceInLamports }) {
267
+ * const { status, error, pay, reset } = useMicropay();
268
+ *
269
+ * const handlePay = async () => {
270
+ * const result = await pay({
271
+ * priceInLamports,
272
+ * recipientAddress: 'CREATOR_WALLET',
273
+ * onSign: (tx) => wallet.signTransaction(tx),
274
+ * onSend: (signedTx) => connection.sendTransaction(signedTx),
275
+ * });
276
+ *
277
+ * if (result.success) {
278
+ * // Refresh page or unlock content
279
+ * }
280
+ * };
281
+ *
282
+ * return (
283
+ * <button onClick={handlePay} disabled={status === 'pending'}>
284
+ * {status === 'pending' ? 'Processing...' : 'Pay to Unlock'}
285
+ * </button>
286
+ * );
287
+ * }
288
+ * ```
289
+ */
290
+ declare function useMicropay(): {
291
+ status: PaymentStatus;
292
+ error: string | null;
293
+ signature: string | null;
294
+ pay: (_options: {
295
+ priceInLamports: bigint;
296
+ recipientAddress: string;
297
+ onSign: (tx: any) => Promise<any>;
298
+ onSend: (signedTx: any) => Promise<string>;
299
+ onConfirm?: (sig: string) => Promise<void>;
300
+ }) => Promise<{
301
+ success: boolean;
302
+ signature?: string;
303
+ error?: string;
304
+ }>;
305
+ reset: () => void;
306
+ };
307
+ /**
308
+ * Hook to format price display in both SOL and USD
309
+ */
310
+ declare function useFormatPrice(lamports: bigint | number | null): {
311
+ sol: null;
312
+ usd: null;
313
+ formatted: string;
314
+ isLoading: boolean;
315
+ } | {
316
+ sol: number;
317
+ usd: number;
318
+ formatted: string;
319
+ isLoading: boolean;
320
+ };
321
+
322
+ export { type PaymentFlowConfig, type PaymentResult, type PaymentStatus, type PaywallConfig, type PaywallState, type SendPaymentParams, type SolanaPayUrlParams, type WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, useFormatPrice, useLamportsToUsd, useMicropay, usePaywallResource, usePricing };
@@ -219,4 +219,104 @@ interface PaywallState<T> {
219
219
  */
220
220
  declare function usePaywallResource<T = any>({ url, connection, wallet }: PaywallConfig): PaywallState<T>;
221
221
 
222
- export { type PaymentFlowConfig, type PaymentResult, type PaywallConfig, type PaywallState, type SendPaymentParams, type SolanaPayUrlParams, type WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource };
222
+ /**
223
+ * Hook to fetch and display SOL price with auto-refresh
224
+ *
225
+ * @example
226
+ * ```tsx
227
+ * function PriceDisplay() {
228
+ * const { solPrice, source, isLoading, error, refresh } = usePricing();
229
+ *
230
+ * if (isLoading) return <span>Loading...</span>;
231
+ * if (error) return <span>Price unavailable</span>;
232
+ *
233
+ * return <span>SOL: ${solPrice?.toFixed(2)} (via {source})</span>;
234
+ * }
235
+ * ```
236
+ */
237
+ declare function usePricing(refreshIntervalMs?: number): {
238
+ solPrice: number | null;
239
+ source: string | null;
240
+ fetchedAt: Date | null;
241
+ isLoading: boolean;
242
+ error: string | null;
243
+ refresh: () => Promise<void>;
244
+ };
245
+ /**
246
+ * Format lamports to USD display with live price
247
+ */
248
+ declare function useLamportsToUsd(lamports: bigint | number | null): {
249
+ usd: null;
250
+ formatted: null;
251
+ isLoading: boolean;
252
+ } | {
253
+ usd: number;
254
+ formatted: string;
255
+ isLoading: boolean;
256
+ };
257
+ /**
258
+ * Payment status for useMicropay hook
259
+ */
260
+ type PaymentStatus = 'idle' | 'pending' | 'confirming' | 'success' | 'error';
261
+ /**
262
+ * Hook for client-side micropayment flow state management
263
+ *
264
+ * @example
265
+ * ```tsx
266
+ * function PayButton({ articleId, priceInLamports }) {
267
+ * const { status, error, pay, reset } = useMicropay();
268
+ *
269
+ * const handlePay = async () => {
270
+ * const result = await pay({
271
+ * priceInLamports,
272
+ * recipientAddress: 'CREATOR_WALLET',
273
+ * onSign: (tx) => wallet.signTransaction(tx),
274
+ * onSend: (signedTx) => connection.sendTransaction(signedTx),
275
+ * });
276
+ *
277
+ * if (result.success) {
278
+ * // Refresh page or unlock content
279
+ * }
280
+ * };
281
+ *
282
+ * return (
283
+ * <button onClick={handlePay} disabled={status === 'pending'}>
284
+ * {status === 'pending' ? 'Processing...' : 'Pay to Unlock'}
285
+ * </button>
286
+ * );
287
+ * }
288
+ * ```
289
+ */
290
+ declare function useMicropay(): {
291
+ status: PaymentStatus;
292
+ error: string | null;
293
+ signature: string | null;
294
+ pay: (_options: {
295
+ priceInLamports: bigint;
296
+ recipientAddress: string;
297
+ onSign: (tx: any) => Promise<any>;
298
+ onSend: (signedTx: any) => Promise<string>;
299
+ onConfirm?: (sig: string) => Promise<void>;
300
+ }) => Promise<{
301
+ success: boolean;
302
+ signature?: string;
303
+ error?: string;
304
+ }>;
305
+ reset: () => void;
306
+ };
307
+ /**
308
+ * Hook to format price display in both SOL and USD
309
+ */
310
+ declare function useFormatPrice(lamports: bigint | number | null): {
311
+ sol: null;
312
+ usd: null;
313
+ formatted: string;
314
+ isLoading: boolean;
315
+ } | {
316
+ sol: number;
317
+ usd: number;
318
+ formatted: string;
319
+ isLoading: boolean;
320
+ };
321
+
322
+ export { type PaymentFlowConfig, type PaymentResult, type PaymentStatus, type PaywallConfig, type PaywallState, type SendPaymentParams, type SolanaPayUrlParams, type WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, useFormatPrice, useLamportsToUsd, useMicropay, usePaywallResource, usePricing };