@alleyboss/micropay-solana-x402-paywall 3.2.2 → 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 +30 -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 +220 -0
- package/dist/client/index.d.cts +101 -1
- package/dist/client/index.d.ts +101 -1
- package/dist/client/index.js +218 -2
- package/dist/index.cjs +278 -154
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +276 -156
- package/dist/next/index.cjs +58 -23
- package/dist/next/index.js +58 -23
- 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 +1 -1
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 */
|
|
@@ -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(
|
|
252
|
-
const { enabled = false, microLamports, computeUnits } =
|
|
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(
|
|
518
|
+
async function buildVersionedTransaction(config) {
|
|
264
519
|
const {
|
|
265
520
|
connection,
|
|
266
521
|
payer,
|
|
267
522
|
instructions,
|
|
268
523
|
priorityFee,
|
|
269
524
|
recentBlockhash
|
|
270
|
-
} =
|
|
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 +
|
|
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,
|
|
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 (
|
|
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 (!
|
|
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 +
|
|
450
|
-
const bundleExpiry =
|
|
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:
|
|
713
|
+
credits: config.initialCredits,
|
|
459
714
|
bundleExpiry,
|
|
460
|
-
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:
|
|
722
|
+
credits: config.initialCredits,
|
|
468
723
|
bundleExpiry,
|
|
469
|
-
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(`${
|
|
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
|
-
|
|
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 };
|
package/dist/next/index.cjs
CHANGED
|
@@ -69,6 +69,10 @@ var LocalSvmFacilitator = class {
|
|
|
69
69
|
getSigners(_network) {
|
|
70
70
|
return [];
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Enable debug logging (disable in production)
|
|
74
|
+
*/
|
|
75
|
+
debug = process.env.NODE_ENV === "development";
|
|
72
76
|
/**
|
|
73
77
|
* Verify a payment on-chain
|
|
74
78
|
*/
|
|
@@ -81,57 +85,89 @@ var LocalSvmFacilitator = class {
|
|
|
81
85
|
const payTo = requirements.payTo;
|
|
82
86
|
const amountVal = requirements.amount || requirements.maxAmountRequired || "0";
|
|
83
87
|
const requiredAmount = BigInt(amountVal);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const tx = await this.
|
|
88
|
-
maxSupportedTransactionVersion: 0,
|
|
89
|
-
commitment: "confirmed"
|
|
90
|
-
});
|
|
88
|
+
if (this.debug) {
|
|
89
|
+
console.log(`[LocalSvmFacilitator] Verifying tx: ${signature.slice(0, 8)}...`);
|
|
90
|
+
}
|
|
91
|
+
const tx = await this.fetchTransactionWithRetry(signature, 3);
|
|
91
92
|
if (!tx) {
|
|
92
|
-
console.error("[LocalSvmFacilitator] Transaction not found or not confirmed");
|
|
93
93
|
return { isValid: false, invalidReason: "Transaction not found or not confirmed" };
|
|
94
94
|
}
|
|
95
|
-
console.log("[LocalSvmFacilitator] Transaction found. Parsing instructions...");
|
|
96
95
|
const instructions = tx.transaction.message.instructions;
|
|
97
96
|
let paidAmount = 0n;
|
|
98
97
|
let payer = void 0;
|
|
99
98
|
for (const ix of instructions) {
|
|
100
99
|
if ("program" in ix && ix.program === "system") {
|
|
101
100
|
const parsed = ix.parsed;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
if (parsed?.type === "transfer" && parsed.info?.destination === payTo) {
|
|
102
|
+
paidAmount += BigInt(parsed.info.lamports);
|
|
103
|
+
if (!payer) payer = parsed.info.source;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if ("program" in ix && (ix.program === "spl-token" || ix.program === "spl-token-2022")) {
|
|
107
|
+
const parsed = ix.parsed;
|
|
108
|
+
if (parsed?.type === "transferChecked" || parsed?.type === "transfer") {
|
|
109
|
+
if (this.debug) {
|
|
110
|
+
console.log(`[LocalSvmFacilitator] Found SPL transfer`);
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
|
-
console.log(`[LocalSvmFacilitator] Total Paid Correctly: ${paidAmount}`);
|
|
114
115
|
if (paidAmount >= requiredAmount) {
|
|
115
|
-
|
|
116
|
+
if (this.debug) {
|
|
117
|
+
console.log(`[LocalSvmFacilitator] Verification SUCCESS for tx: ${signature.slice(0, 8)}...`);
|
|
118
|
+
}
|
|
116
119
|
return {
|
|
117
120
|
isValid: true,
|
|
118
121
|
payer: payer || tx.transaction.message.accountKeys[0].pubkey.toBase58()
|
|
119
122
|
};
|
|
120
123
|
}
|
|
121
|
-
console.error(`[LocalSvmFacilitator] Verification FAILED. Paid: ${paidAmount}, Required: ${requiredAmount}`);
|
|
122
124
|
return {
|
|
123
125
|
isValid: false,
|
|
124
|
-
invalidReason:
|
|
126
|
+
invalidReason: "Insufficient payment amount",
|
|
125
127
|
payer
|
|
126
128
|
};
|
|
127
129
|
} catch (error) {
|
|
128
|
-
|
|
130
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
131
|
+
if (this.debug) {
|
|
132
|
+
console.error("[LocalSvmFacilitator] Verify error:", errorMessage);
|
|
133
|
+
}
|
|
129
134
|
throw new types.VerifyError(500, {
|
|
130
135
|
isValid: false,
|
|
131
|
-
invalidReason:
|
|
136
|
+
invalidReason: errorMessage
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Fetch transaction with exponential backoff retry
|
|
142
|
+
*/
|
|
143
|
+
async fetchTransactionWithRetry(signature, maxRetries = 3) {
|
|
144
|
+
let lastError;
|
|
145
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
146
|
+
try {
|
|
147
|
+
const tx = await this.connection.getParsedTransaction(signature, {
|
|
148
|
+
maxSupportedTransactionVersion: 0,
|
|
149
|
+
commitment: "confirmed"
|
|
150
|
+
});
|
|
151
|
+
if (tx) return tx;
|
|
152
|
+
if (attempt < maxRetries - 1) {
|
|
153
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
lastError = error instanceof Error ? error : new Error("RPC error");
|
|
157
|
+
if (attempt < maxRetries - 1) {
|
|
158
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (lastError) throw lastError;
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Sleep helper
|
|
167
|
+
*/
|
|
168
|
+
sleep(ms) {
|
|
169
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
170
|
+
}
|
|
135
171
|
/**
|
|
136
172
|
* Settle a payment (not applicable for direct chain verification, usually)
|
|
137
173
|
* But we must implement it. For 'exact', settlement is just verification + finality.
|
|
@@ -177,7 +213,6 @@ function createX402Middleware(config) {
|
|
|
177
213
|
network: config.network === "mainnet-beta" ? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" : "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"
|
|
178
214
|
}
|
|
179
215
|
};
|
|
180
|
-
console.log("[createX402Middleware] Final Config:", JSON.stringify(finalConfig));
|
|
181
216
|
return next.withX402(handler, finalConfig, server$2);
|
|
182
217
|
};
|
|
183
218
|
}
|