@alleyboss/micropay-solana-x402-paywall 3.2.2 → 3.3.1
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 +45 -0
- package/dist/agent/index.cjs +2 -2
- package/dist/agent/index.d.cts +1 -1
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +2 -2
- package/dist/client/index.cjs +229 -5
- package/dist/client/index.d.cts +101 -1
- package/dist/client/index.d.ts +101 -1
- package/dist/client/index.js +227 -7
- package/dist/index.cjs +287 -159
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +285 -161
- package/dist/next/index.cjs +91 -28
- package/dist/next/index.js +91 -28
- package/dist/pricing/index.cjs +54 -38
- package/dist/pricing/index.d.cts +8 -9
- package/dist/pricing/index.d.ts +8 -9
- package/dist/pricing/index.js +54 -38
- package/package.json +2 -2
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(
|
|
45
|
-
const { network, recipientWallet, amount, asset = "native", memo } =
|
|
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: () => ({ ...
|
|
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 */
|
|
@@ -188,13 +188,17 @@ function usePaywallResource({
|
|
|
188
188
|
if (wwwAuth) {
|
|
189
189
|
setPaymentHeader(wwwAuth);
|
|
190
190
|
try {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
const { decodePaymentRequiredHeader: decodePaymentRequiredHeader2 } = await import('@x402/core/http');
|
|
192
|
+
const cleanHeader = wwwAuth.replace(/^[Xx]402\s+/, "");
|
|
193
|
+
const decoded = decodePaymentRequiredHeader2(cleanHeader);
|
|
194
|
+
const accepts = Array.isArray(decoded.accepts) ? decoded.accepts[0] : decoded.accepts;
|
|
195
|
+
if (accepts) {
|
|
196
|
+
const amountStr = accepts.amount || accepts.maxAmountRequired || "0";
|
|
197
|
+
setPrice(BigInt(amountStr));
|
|
198
|
+
setRecipient(accepts.payTo);
|
|
195
199
|
}
|
|
196
200
|
} catch (e) {
|
|
197
|
-
console.warn("Failed to parse
|
|
201
|
+
console.warn("[usePaywallResource] Failed to parse x402 header:", e);
|
|
198
202
|
}
|
|
199
203
|
}
|
|
200
204
|
setIsLocked(true);
|
|
@@ -246,10 +250,265 @@ function usePaywallResource({
|
|
|
246
250
|
unlock
|
|
247
251
|
};
|
|
248
252
|
}
|
|
253
|
+
|
|
254
|
+
// src/pricing/utils.ts
|
|
255
|
+
function lamportsToSol(lamports) {
|
|
256
|
+
return Number(lamports) / 1e9;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/pricing/index.ts
|
|
260
|
+
var priceCache = null;
|
|
261
|
+
var currentConfig = {};
|
|
262
|
+
function configurePricing(newConfig) {
|
|
263
|
+
currentConfig = { ...currentConfig, ...newConfig };
|
|
264
|
+
}
|
|
265
|
+
var PROVIDERS = [
|
|
266
|
+
{
|
|
267
|
+
name: "coincap",
|
|
268
|
+
url: "https://api.coincap.io/v2/assets/solana",
|
|
269
|
+
parse: (data) => parseFloat(data.data?.priceUsd || "0")
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "binance",
|
|
273
|
+
url: "https://api.binance.com/api/v3/ticker/price?symbol=SOLUSDT",
|
|
274
|
+
parse: (data) => parseFloat(data.price || "0")
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "coingecko",
|
|
278
|
+
url: "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
|
|
279
|
+
parse: (data) => data.solana?.usd || 0
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "kraken",
|
|
283
|
+
url: "https://api.kraken.com/0/public/Ticker?pair=SOLUSD",
|
|
284
|
+
parse: (data) => parseFloat(data.result?.SOLUSD?.c?.[0] || "0")
|
|
285
|
+
}
|
|
286
|
+
];
|
|
287
|
+
async function fetchFromProvider(provider, timeout) {
|
|
288
|
+
const controller = new AbortController();
|
|
289
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
290
|
+
try {
|
|
291
|
+
const response = await fetch(provider.url, {
|
|
292
|
+
headers: { "Accept": "application/json" },
|
|
293
|
+
signal: controller.signal
|
|
294
|
+
});
|
|
295
|
+
if (!response.ok) {
|
|
296
|
+
throw new Error(`HTTP ${response.status}`);
|
|
297
|
+
}
|
|
298
|
+
const data = await response.json();
|
|
299
|
+
const price = provider.parse(data);
|
|
300
|
+
if (!price || price <= 0) {
|
|
301
|
+
throw new Error("Invalid price");
|
|
302
|
+
}
|
|
303
|
+
return { price, source: provider.name };
|
|
304
|
+
} finally {
|
|
305
|
+
clearTimeout(timeoutId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function fetchPriceParallel(timeout) {
|
|
309
|
+
const promises = PROVIDERS.map(
|
|
310
|
+
(provider) => fetchFromProvider(provider, timeout).catch(() => null)
|
|
311
|
+
);
|
|
312
|
+
const results = await Promise.all(promises);
|
|
313
|
+
const validResult = results.find((r) => r !== null);
|
|
314
|
+
if (validResult) {
|
|
315
|
+
return validResult;
|
|
316
|
+
}
|
|
317
|
+
throw new Error("All providers failed");
|
|
318
|
+
}
|
|
319
|
+
async function fetchPriceSequential(timeout) {
|
|
320
|
+
for (const provider of PROVIDERS) {
|
|
321
|
+
try {
|
|
322
|
+
return await fetchFromProvider(provider, timeout);
|
|
323
|
+
} catch {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
throw new Error("All providers failed");
|
|
328
|
+
}
|
|
329
|
+
async function getSolPrice() {
|
|
330
|
+
const cacheTTL = currentConfig.cacheTTL ?? 6e4;
|
|
331
|
+
const timeout = currentConfig.timeout ?? 3e3;
|
|
332
|
+
const useParallel = currentConfig.parallelFetch ?? true;
|
|
333
|
+
const now = Date.now();
|
|
334
|
+
if (priceCache && now - priceCache.timestamp < cacheTTL) {
|
|
335
|
+
return priceCache.data;
|
|
336
|
+
}
|
|
337
|
+
if (currentConfig.customProvider) {
|
|
338
|
+
try {
|
|
339
|
+
const price = await currentConfig.customProvider();
|
|
340
|
+
if (price > 0) {
|
|
341
|
+
const data = {
|
|
342
|
+
solPrice: price,
|
|
343
|
+
fetchedAt: /* @__PURE__ */ new Date(),
|
|
344
|
+
source: "custom"
|
|
345
|
+
};
|
|
346
|
+
priceCache = { data, timestamp: now };
|
|
347
|
+
return data;
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const result = useParallel ? await fetchPriceParallel(timeout) : await fetchPriceSequential(timeout);
|
|
354
|
+
const data = {
|
|
355
|
+
solPrice: result.price,
|
|
356
|
+
fetchedAt: /* @__PURE__ */ new Date(),
|
|
357
|
+
source: result.source
|
|
358
|
+
};
|
|
359
|
+
priceCache = { data, timestamp: now };
|
|
360
|
+
return data;
|
|
361
|
+
} catch {
|
|
362
|
+
if (priceCache) {
|
|
363
|
+
return {
|
|
364
|
+
...priceCache.data,
|
|
365
|
+
source: `${priceCache.data.source} (stale)`
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
throw new Error(
|
|
369
|
+
"Failed to fetch SOL price from all providers. Configure a custom provider or ensure network connectivity."
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async function lamportsToUsd(lamports) {
|
|
374
|
+
const { solPrice } = await getSolPrice();
|
|
375
|
+
const sol = Number(lamports) / 1e9;
|
|
376
|
+
return sol * solPrice;
|
|
377
|
+
}
|
|
378
|
+
async function usdToLamports(usd) {
|
|
379
|
+
const { solPrice } = await getSolPrice();
|
|
380
|
+
const sol = usd / solPrice;
|
|
381
|
+
return BigInt(Math.floor(sol * 1e9));
|
|
382
|
+
}
|
|
383
|
+
async function formatPriceDisplay(lamports) {
|
|
384
|
+
const { solPrice } = await getSolPrice();
|
|
385
|
+
const sol = Number(lamports) / 1e9;
|
|
386
|
+
const usd = sol * solPrice;
|
|
387
|
+
return `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`;
|
|
388
|
+
}
|
|
389
|
+
function formatPriceSync(lamports, solPrice) {
|
|
390
|
+
const sol = Number(lamports) / 1e9;
|
|
391
|
+
const usd = sol * solPrice;
|
|
392
|
+
return {
|
|
393
|
+
sol,
|
|
394
|
+
usd,
|
|
395
|
+
formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function clearPriceCache() {
|
|
399
|
+
priceCache = null;
|
|
400
|
+
}
|
|
401
|
+
function getProviders() {
|
|
402
|
+
return PROVIDERS.map((p) => ({ name: p.name, url: p.url }));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/client/hooks.ts
|
|
406
|
+
function usePricing(refreshIntervalMs = 6e4) {
|
|
407
|
+
const [priceData, setPriceData] = useState(null);
|
|
408
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
409
|
+
const [error, setError] = useState(null);
|
|
410
|
+
const intervalRef = useRef(null);
|
|
411
|
+
const fetchPrice = useCallback(async () => {
|
|
412
|
+
try {
|
|
413
|
+
setIsLoading(true);
|
|
414
|
+
setError(null);
|
|
415
|
+
const data = await getSolPrice();
|
|
416
|
+
setPriceData(data);
|
|
417
|
+
} catch (err) {
|
|
418
|
+
setError(err instanceof Error ? err.message : "Failed to fetch price");
|
|
419
|
+
} finally {
|
|
420
|
+
setIsLoading(false);
|
|
421
|
+
}
|
|
422
|
+
}, []);
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
fetchPrice();
|
|
425
|
+
if (refreshIntervalMs > 0) {
|
|
426
|
+
intervalRef.current = setInterval(fetchPrice, refreshIntervalMs);
|
|
427
|
+
}
|
|
428
|
+
return () => {
|
|
429
|
+
if (intervalRef.current) {
|
|
430
|
+
clearInterval(intervalRef.current);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}, [fetchPrice, refreshIntervalMs]);
|
|
434
|
+
return {
|
|
435
|
+
solPrice: priceData?.solPrice ?? null,
|
|
436
|
+
source: priceData?.source ?? null,
|
|
437
|
+
fetchedAt: priceData?.fetchedAt ?? null,
|
|
438
|
+
isLoading,
|
|
439
|
+
error,
|
|
440
|
+
refresh: fetchPrice
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function useLamportsToUsd(lamports) {
|
|
444
|
+
const { solPrice, isLoading } = usePricing();
|
|
445
|
+
if (isLoading || !solPrice || lamports === null) {
|
|
446
|
+
return { usd: null, formatted: null, isLoading };
|
|
447
|
+
}
|
|
448
|
+
const lamportsBigInt = typeof lamports === "number" ? BigInt(lamports) : lamports;
|
|
449
|
+
const sol = Number(lamportsBigInt) / 1e9;
|
|
450
|
+
const usd = sol * solPrice;
|
|
451
|
+
return {
|
|
452
|
+
usd,
|
|
453
|
+
formatted: `$${usd.toFixed(2)}`,
|
|
454
|
+
isLoading: false
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function useMicropay() {
|
|
458
|
+
const [status, setStatus] = useState("idle");
|
|
459
|
+
const [error, setError] = useState(null);
|
|
460
|
+
const [signature, setSignature] = useState(null);
|
|
461
|
+
const reset = useCallback(() => {
|
|
462
|
+
setStatus("idle");
|
|
463
|
+
setError(null);
|
|
464
|
+
setSignature(null);
|
|
465
|
+
}, []);
|
|
466
|
+
const pay = useCallback(async (_options) => {
|
|
467
|
+
setStatus("pending");
|
|
468
|
+
setError(null);
|
|
469
|
+
try {
|
|
470
|
+
throw new Error(
|
|
471
|
+
"useMicropay requires implementation of onSign/onSend callbacks. See documentation for wallet adapter integration."
|
|
472
|
+
);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
const errorMessage = err instanceof Error ? err.message : "Payment failed";
|
|
475
|
+
setError(errorMessage);
|
|
476
|
+
setStatus("error");
|
|
477
|
+
return { success: false, error: errorMessage };
|
|
478
|
+
}
|
|
479
|
+
}, []);
|
|
480
|
+
return {
|
|
481
|
+
status,
|
|
482
|
+
error,
|
|
483
|
+
signature,
|
|
484
|
+
pay,
|
|
485
|
+
reset
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function useFormatPrice(lamports) {
|
|
489
|
+
const { solPrice, isLoading } = usePricing();
|
|
490
|
+
if (isLoading || !solPrice || lamports === null) {
|
|
491
|
+
return {
|
|
492
|
+
sol: null,
|
|
493
|
+
usd: null,
|
|
494
|
+
formatted: "Loading...",
|
|
495
|
+
isLoading
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
const lamportsBigInt = typeof lamports === "number" ? BigInt(lamports) : lamports;
|
|
499
|
+
const sol = Number(lamportsBigInt) / 1e9;
|
|
500
|
+
const usd = sol * solPrice;
|
|
501
|
+
return {
|
|
502
|
+
sol,
|
|
503
|
+
usd,
|
|
504
|
+
formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`,
|
|
505
|
+
isLoading: false
|
|
506
|
+
};
|
|
507
|
+
}
|
|
249
508
|
var DEFAULT_COMPUTE_UNITS = 2e5;
|
|
250
509
|
var DEFAULT_MICRO_LAMPORTS = 1e3;
|
|
251
|
-
function createPriorityFeeInstructions(
|
|
252
|
-
const { enabled = false, microLamports, computeUnits } =
|
|
510
|
+
function createPriorityFeeInstructions(config = {}) {
|
|
511
|
+
const { enabled = false, microLamports, computeUnits } = config;
|
|
253
512
|
if (!enabled) {
|
|
254
513
|
return [];
|
|
255
514
|
}
|
|
@@ -260,14 +519,14 @@ function createPriorityFeeInstructions(config2 = {}) {
|
|
|
260
519
|
instructions.push(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: price }));
|
|
261
520
|
return instructions;
|
|
262
521
|
}
|
|
263
|
-
async function buildVersionedTransaction(
|
|
522
|
+
async function buildVersionedTransaction(config) {
|
|
264
523
|
const {
|
|
265
524
|
connection,
|
|
266
525
|
payer,
|
|
267
526
|
instructions,
|
|
268
527
|
priorityFee,
|
|
269
528
|
recentBlockhash
|
|
270
|
-
} =
|
|
529
|
+
} = config;
|
|
271
530
|
const priorityIxs = createPriorityFeeInstructions(priorityFee);
|
|
272
531
|
const allInstructions = [...priorityIxs, ...instructions];
|
|
273
532
|
let blockhash;
|
|
@@ -383,9 +642,9 @@ async function getAgentBalance(connection, agentKeypair) {
|
|
|
383
642
|
balanceSol: balance / LAMPORTS_PER_SOL
|
|
384
643
|
};
|
|
385
644
|
}
|
|
386
|
-
async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports) {
|
|
645
|
+
async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports, feeBufferLamports = 5000000n) {
|
|
387
646
|
const { balance } = await getAgentBalance(connection, agentKeypair);
|
|
388
|
-
const totalRequired = requiredLamports +
|
|
647
|
+
const totalRequired = requiredLamports + feeBufferLamports;
|
|
389
648
|
return {
|
|
390
649
|
sufficient: balance >= totalRequired,
|
|
391
650
|
balance,
|
|
@@ -434,20 +693,20 @@ function validateWalletAddress(address) {
|
|
|
434
693
|
const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
435
694
|
return base58Regex.test(address);
|
|
436
695
|
}
|
|
437
|
-
async function createCreditSession(walletAddress, purchaseId,
|
|
696
|
+
async function createCreditSession(walletAddress, purchaseId, config) {
|
|
438
697
|
if (!validateWalletAddress(walletAddress)) {
|
|
439
698
|
throw new Error("Invalid wallet address format");
|
|
440
699
|
}
|
|
441
|
-
if (
|
|
700
|
+
if (config.initialCredits <= 0 || config.initialCredits > MAX_CREDITS) {
|
|
442
701
|
throw new Error(`Credits must be between 1 and ${MAX_CREDITS}`);
|
|
443
702
|
}
|
|
444
|
-
if (!
|
|
703
|
+
if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 8760) {
|
|
445
704
|
throw new Error("Session duration must be between 1 and 8760 hours (1 year)");
|
|
446
705
|
}
|
|
447
706
|
const sessionId = uuidv4();
|
|
448
707
|
const now = Math.floor(Date.now() / 1e3);
|
|
449
|
-
const expiresAt = now +
|
|
450
|
-
const bundleExpiry =
|
|
708
|
+
const expiresAt = now + config.durationHours * 3600;
|
|
709
|
+
const bundleExpiry = config.bundleExpiryHours ? now + config.bundleExpiryHours * 3600 : expiresAt;
|
|
451
710
|
const session = {
|
|
452
711
|
id: sessionId,
|
|
453
712
|
walletAddress,
|
|
@@ -455,22 +714,22 @@ async function createCreditSession(walletAddress, purchaseId, config2) {
|
|
|
455
714
|
siteWideUnlock: false,
|
|
456
715
|
createdAt: now,
|
|
457
716
|
expiresAt,
|
|
458
|
-
credits:
|
|
717
|
+
credits: config.initialCredits,
|
|
459
718
|
bundleExpiry,
|
|
460
|
-
bundleType:
|
|
719
|
+
bundleType: config.bundleType
|
|
461
720
|
};
|
|
462
721
|
const payload = {
|
|
463
722
|
sub: walletAddress,
|
|
464
723
|
sid: sessionId,
|
|
465
724
|
articles: session.unlockedArticles,
|
|
466
725
|
siteWide: false,
|
|
467
|
-
credits:
|
|
726
|
+
credits: config.initialCredits,
|
|
468
727
|
bundleExpiry,
|
|
469
|
-
bundleType:
|
|
728
|
+
bundleType: config.bundleType,
|
|
470
729
|
iat: now,
|
|
471
730
|
exp: expiresAt
|
|
472
731
|
};
|
|
473
|
-
const token = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(`${
|
|
732
|
+
const token = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(`${config.durationHours}h`).sign(getSecretKey(config.secret));
|
|
474
733
|
return { token, session };
|
|
475
734
|
}
|
|
476
735
|
async function validateCreditSession(token, secret) {
|
|
@@ -588,139 +847,4 @@ async function getRemainingCredits(token, secret) {
|
|
|
588
847
|
};
|
|
589
848
|
}
|
|
590
849
|
|
|
591
|
-
|
|
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 };
|
|
850
|
+
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 };
|