@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/client/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from '@x402/core/http';
|
|
2
2
|
import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL } from '@solana/web3.js';
|
|
3
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
3
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
4
4
|
|
|
5
5
|
// src/client/types.ts
|
|
6
6
|
var TOKEN_MINTS = {
|
|
@@ -180,13 +180,17 @@ function usePaywallResource({
|
|
|
180
180
|
if (wwwAuth) {
|
|
181
181
|
setPaymentHeader(wwwAuth);
|
|
182
182
|
try {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
const { decodePaymentRequiredHeader: decodePaymentRequiredHeader2 } = await import('@x402/core/http');
|
|
184
|
+
const cleanHeader = wwwAuth.replace(/^[Xx]402\s+/, "");
|
|
185
|
+
const decoded = decodePaymentRequiredHeader2(cleanHeader);
|
|
186
|
+
const accepts = Array.isArray(decoded.accepts) ? decoded.accepts[0] : decoded.accepts;
|
|
187
|
+
if (accepts) {
|
|
188
|
+
const amountStr = accepts.amount || accepts.maxAmountRequired || "0";
|
|
189
|
+
setPrice(BigInt(amountStr));
|
|
190
|
+
setRecipient(accepts.payTo);
|
|
187
191
|
}
|
|
188
192
|
} catch (e) {
|
|
189
|
-
console.warn("Failed to parse
|
|
193
|
+
console.warn("[usePaywallResource] Failed to parse x402 header:", e);
|
|
190
194
|
}
|
|
191
195
|
}
|
|
192
196
|
setIsLocked(true);
|
|
@@ -239,4 +243,220 @@ function usePaywallResource({
|
|
|
239
243
|
};
|
|
240
244
|
}
|
|
241
245
|
|
|
242
|
-
|
|
246
|
+
// src/pricing/index.ts
|
|
247
|
+
var priceCache = null;
|
|
248
|
+
var currentConfig = {};
|
|
249
|
+
var PROVIDERS = [
|
|
250
|
+
{
|
|
251
|
+
name: "coincap",
|
|
252
|
+
url: "https://api.coincap.io/v2/assets/solana",
|
|
253
|
+
parse: (data) => parseFloat(data.data?.priceUsd || "0")
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "binance",
|
|
257
|
+
url: "https://api.binance.com/api/v3/ticker/price?symbol=SOLUSDT",
|
|
258
|
+
parse: (data) => parseFloat(data.price || "0")
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "coingecko",
|
|
262
|
+
url: "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
|
|
263
|
+
parse: (data) => data.solana?.usd || 0
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "kraken",
|
|
267
|
+
url: "https://api.kraken.com/0/public/Ticker?pair=SOLUSD",
|
|
268
|
+
parse: (data) => parseFloat(data.result?.SOLUSD?.c?.[0] || "0")
|
|
269
|
+
}
|
|
270
|
+
];
|
|
271
|
+
async function fetchFromProvider(provider, timeout) {
|
|
272
|
+
const controller = new AbortController();
|
|
273
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
274
|
+
try {
|
|
275
|
+
const response = await fetch(provider.url, {
|
|
276
|
+
headers: { "Accept": "application/json" },
|
|
277
|
+
signal: controller.signal
|
|
278
|
+
});
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
throw new Error(`HTTP ${response.status}`);
|
|
281
|
+
}
|
|
282
|
+
const data = await response.json();
|
|
283
|
+
const price = provider.parse(data);
|
|
284
|
+
if (!price || price <= 0) {
|
|
285
|
+
throw new Error("Invalid price");
|
|
286
|
+
}
|
|
287
|
+
return { price, source: provider.name };
|
|
288
|
+
} finally {
|
|
289
|
+
clearTimeout(timeoutId);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function fetchPriceParallel(timeout) {
|
|
293
|
+
const promises = PROVIDERS.map(
|
|
294
|
+
(provider) => fetchFromProvider(provider, timeout).catch(() => null)
|
|
295
|
+
);
|
|
296
|
+
const results = await Promise.all(promises);
|
|
297
|
+
const validResult = results.find((r) => r !== null);
|
|
298
|
+
if (validResult) {
|
|
299
|
+
return validResult;
|
|
300
|
+
}
|
|
301
|
+
throw new Error("All providers failed");
|
|
302
|
+
}
|
|
303
|
+
async function fetchPriceSequential(timeout) {
|
|
304
|
+
for (const provider of PROVIDERS) {
|
|
305
|
+
try {
|
|
306
|
+
return await fetchFromProvider(provider, timeout);
|
|
307
|
+
} catch {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
throw new Error("All providers failed");
|
|
312
|
+
}
|
|
313
|
+
async function getSolPrice() {
|
|
314
|
+
const cacheTTL = currentConfig.cacheTTL ?? 6e4;
|
|
315
|
+
const timeout = currentConfig.timeout ?? 3e3;
|
|
316
|
+
const useParallel = currentConfig.parallelFetch ?? true;
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
if (priceCache && now - priceCache.timestamp < cacheTTL) {
|
|
319
|
+
return priceCache.data;
|
|
320
|
+
}
|
|
321
|
+
if (currentConfig.customProvider) {
|
|
322
|
+
try {
|
|
323
|
+
const price = await currentConfig.customProvider();
|
|
324
|
+
if (price > 0) {
|
|
325
|
+
const data = {
|
|
326
|
+
solPrice: price,
|
|
327
|
+
fetchedAt: /* @__PURE__ */ new Date(),
|
|
328
|
+
source: "custom"
|
|
329
|
+
};
|
|
330
|
+
priceCache = { data, timestamp: now };
|
|
331
|
+
return data;
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const result = useParallel ? await fetchPriceParallel(timeout) : await fetchPriceSequential(timeout);
|
|
338
|
+
const data = {
|
|
339
|
+
solPrice: result.price,
|
|
340
|
+
fetchedAt: /* @__PURE__ */ new Date(),
|
|
341
|
+
source: result.source
|
|
342
|
+
};
|
|
343
|
+
priceCache = { data, timestamp: now };
|
|
344
|
+
return data;
|
|
345
|
+
} catch {
|
|
346
|
+
if (priceCache) {
|
|
347
|
+
return {
|
|
348
|
+
...priceCache.data,
|
|
349
|
+
source: `${priceCache.data.source} (stale)`
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
throw new Error(
|
|
353
|
+
"Failed to fetch SOL price from all providers. Configure a custom provider or ensure network connectivity."
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/client/hooks.ts
|
|
359
|
+
function usePricing(refreshIntervalMs = 6e4) {
|
|
360
|
+
const [priceData, setPriceData] = useState(null);
|
|
361
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
362
|
+
const [error, setError] = useState(null);
|
|
363
|
+
const intervalRef = useRef(null);
|
|
364
|
+
const fetchPrice = useCallback(async () => {
|
|
365
|
+
try {
|
|
366
|
+
setIsLoading(true);
|
|
367
|
+
setError(null);
|
|
368
|
+
const data = await getSolPrice();
|
|
369
|
+
setPriceData(data);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
setError(err instanceof Error ? err.message : "Failed to fetch price");
|
|
372
|
+
} finally {
|
|
373
|
+
setIsLoading(false);
|
|
374
|
+
}
|
|
375
|
+
}, []);
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
fetchPrice();
|
|
378
|
+
if (refreshIntervalMs > 0) {
|
|
379
|
+
intervalRef.current = setInterval(fetchPrice, refreshIntervalMs);
|
|
380
|
+
}
|
|
381
|
+
return () => {
|
|
382
|
+
if (intervalRef.current) {
|
|
383
|
+
clearInterval(intervalRef.current);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}, [fetchPrice, refreshIntervalMs]);
|
|
387
|
+
return {
|
|
388
|
+
solPrice: priceData?.solPrice ?? null,
|
|
389
|
+
source: priceData?.source ?? null,
|
|
390
|
+
fetchedAt: priceData?.fetchedAt ?? null,
|
|
391
|
+
isLoading,
|
|
392
|
+
error,
|
|
393
|
+
refresh: fetchPrice
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function useLamportsToUsd(lamports) {
|
|
397
|
+
const { solPrice, isLoading } = usePricing();
|
|
398
|
+
if (isLoading || !solPrice || lamports === null) {
|
|
399
|
+
return { usd: null, formatted: null, isLoading };
|
|
400
|
+
}
|
|
401
|
+
const lamportsBigInt = typeof lamports === "number" ? BigInt(lamports) : lamports;
|
|
402
|
+
const sol = Number(lamportsBigInt) / 1e9;
|
|
403
|
+
const usd = sol * solPrice;
|
|
404
|
+
return {
|
|
405
|
+
usd,
|
|
406
|
+
formatted: `$${usd.toFixed(2)}`,
|
|
407
|
+
isLoading: false
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function useMicropay() {
|
|
411
|
+
const [status, setStatus] = useState("idle");
|
|
412
|
+
const [error, setError] = useState(null);
|
|
413
|
+
const [signature, setSignature] = useState(null);
|
|
414
|
+
const reset = useCallback(() => {
|
|
415
|
+
setStatus("idle");
|
|
416
|
+
setError(null);
|
|
417
|
+
setSignature(null);
|
|
418
|
+
}, []);
|
|
419
|
+
const pay = useCallback(async (_options) => {
|
|
420
|
+
setStatus("pending");
|
|
421
|
+
setError(null);
|
|
422
|
+
try {
|
|
423
|
+
throw new Error(
|
|
424
|
+
"useMicropay requires implementation of onSign/onSend callbacks. See documentation for wallet adapter integration."
|
|
425
|
+
);
|
|
426
|
+
} catch (err) {
|
|
427
|
+
const errorMessage = err instanceof Error ? err.message : "Payment failed";
|
|
428
|
+
setError(errorMessage);
|
|
429
|
+
setStatus("error");
|
|
430
|
+
return { success: false, error: errorMessage };
|
|
431
|
+
}
|
|
432
|
+
}, []);
|
|
433
|
+
return {
|
|
434
|
+
status,
|
|
435
|
+
error,
|
|
436
|
+
signature,
|
|
437
|
+
pay,
|
|
438
|
+
reset
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function useFormatPrice(lamports) {
|
|
442
|
+
const { solPrice, isLoading } = usePricing();
|
|
443
|
+
if (isLoading || !solPrice || lamports === null) {
|
|
444
|
+
return {
|
|
445
|
+
sol: null,
|
|
446
|
+
usd: null,
|
|
447
|
+
formatted: "Loading...",
|
|
448
|
+
isLoading
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const lamportsBigInt = typeof lamports === "number" ? BigInt(lamports) : lamports;
|
|
452
|
+
const sol = Number(lamportsBigInt) / 1e9;
|
|
453
|
+
const usd = sol * solPrice;
|
|
454
|
+
return {
|
|
455
|
+
sol,
|
|
456
|
+
usd,
|
|
457
|
+
formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`,
|
|
458
|
+
isLoading: false
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export { buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, useFormatPrice, useLamportsToUsd, useMicropay, usePaywallResource, usePricing };
|