@alleyboss/micropay-solana-x402-paywall 2.0.0 β 2.0.2
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 +63 -115
- package/dist/index.cjs +110 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +109 -48
- package/dist/index.js.map +1 -1
- package/dist/pricing/index.cjs +90 -27
- package/dist/pricing/index.cjs.map +1 -1
- package/dist/pricing/index.d.cts +53 -9
- package/dist/pricing/index.d.ts +53 -9
- package/dist/pricing/index.js +89 -28
- package/dist/pricing/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,165 +1,113 @@
|
|
|
1
1
|
# @alleyboss/micropay-solana-x402-paywall
|
|
2
2
|
|
|
3
|
-
> Production-ready Solana micropayments library implementing the x402 protocol
|
|
3
|
+
> Production-ready Solana micropayments library implementing the x402 protocol.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@alleyboss/micropay-solana-x402-paywall)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://bundlephobia.com/package/@alleyboss/micropay-solana-x402-paywall)
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
+
## π What It Does
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
- β‘ **Solana Native** β Fast, low-cost SOL & USDC micropayments
|
|
12
|
-
- π **JWT Sessions** β Secure unlock tracking with anti-replay
|
|
13
|
-
- π¦ **Framework Agnostic** β Express, Next.js, Fastify ready
|
|
14
|
-
- π³ **Tree-shakeable** β Import only what you need (35KB full, 1-13KB per module)
|
|
15
|
-
- π° **SPL Tokens** β USDC, USDT, and custom token support
|
|
16
|
-
- π **Retry Logic** β Built-in resilience for RPC failures
|
|
17
|
-
|
|
18
|
-
## Installation
|
|
11
|
+
Turn any content into paid content with **one-time micropayments** on Solana. No subscriptions, no recurring chargesβjust pay to unlock.
|
|
19
12
|
|
|
20
13
|
```bash
|
|
21
14
|
npm install @alleyboss/micropay-solana-x402-paywall @solana/web3.js
|
|
22
15
|
```
|
|
23
16
|
|
|
24
|
-
##
|
|
17
|
+
## β¨ Features
|
|
18
|
+
|
|
19
|
+
| Feature | Description |
|
|
20
|
+
|---------|-------------|
|
|
21
|
+
| π° **SOL & USDC Payments** | Native SOL and SPL tokens (USDC, USDT) |
|
|
22
|
+
| π **x402 Protocol** | HTTP 402 Payment Required standard |
|
|
23
|
+
| π **JWT Sessions** | Secure unlock tracking with anti-replay |
|
|
24
|
+
| οΏ½οΈ **Signature Store** | Prevent double-spend at app layer |
|
|
25
|
+
| π **Express & Next.js** | Zero-boilerplate middleware |
|
|
26
|
+
| οΏ½ **Price Conversion** | USDβSOL with multi-provider fallback |
|
|
27
|
+
| π³ **Tree-Shakeable** | Import only what you need |
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
## π¦ Quick Example
|
|
27
30
|
|
|
28
31
|
```typescript
|
|
29
|
-
import { verifyPayment,
|
|
32
|
+
import { verifyPayment, createSession } from '@alleyboss/micropay-solana-x402-paywall';
|
|
30
33
|
|
|
31
|
-
//
|
|
34
|
+
// Verify on-chain payment
|
|
32
35
|
const result = await verifyPayment({
|
|
33
36
|
signature: 'tx...',
|
|
34
37
|
expectedRecipient: 'CreatorWallet',
|
|
35
38
|
expectedAmount: 10_000_000n, // 0.01 SOL
|
|
36
|
-
clientConfig: { network: 'devnet' },
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// USDC payment
|
|
40
|
-
const usdcResult = await verifySPLPayment({
|
|
41
|
-
signature: 'tx...',
|
|
42
|
-
expectedRecipient: 'CreatorWallet',
|
|
43
|
-
expectedAmount: 1_000_000n, // 1 USDC
|
|
44
|
-
asset: 'usdc',
|
|
45
39
|
clientConfig: { network: 'mainnet-beta' },
|
|
46
40
|
});
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Express Middleware (Zero Boilerplate)
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
protectedPaths: ['/**'],
|
|
60
|
-
}));
|
|
61
|
-
|
|
62
|
-
app.get('/api/premium/content', (req, res) => {
|
|
63
|
-
res.json({ content: 'Premium!', wallet: req.session?.walletAddress });
|
|
64
|
-
});
|
|
42
|
+
// Create session for unlocked content
|
|
43
|
+
if (result.valid) {
|
|
44
|
+
const { token } = await createSession(
|
|
45
|
+
result.from!,
|
|
46
|
+
'article-123',
|
|
47
|
+
{ secret: process.env.SESSION_SECRET!, durationHours: 24 }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
65
50
|
```
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
// middleware.ts
|
|
71
|
-
import { createPaywallMiddleware } from '@alleyboss/micropay-solana-x402-paywall/middleware';
|
|
72
|
-
|
|
73
|
-
export const middleware = createPaywallMiddleware({
|
|
74
|
-
sessionSecret: process.env.SESSION_SECRET!,
|
|
75
|
-
protectedPaths: ['/api/premium/*', '/api/content/*'],
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
export const config = { matcher: ['/api/premium/:path*'] };
|
|
79
|
-
```
|
|
52
|
+
## π§ Modules
|
|
80
53
|
|
|
81
|
-
|
|
54
|
+
9 tree-shakeable entry points for minimal bundle size:
|
|
82
55
|
|
|
83
56
|
```typescript
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Development
|
|
87
|
-
const store = createMemoryStore();
|
|
57
|
+
// Core verification
|
|
58
|
+
import { verifyPayment, verifySPLPayment } from '@alleyboss/micropay-solana-x402-paywall/solana';
|
|
88
59
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Check before verification
|
|
93
|
-
if (await store.hasBeenUsed(signature)) {
|
|
94
|
-
throw new Error('Payment already used');
|
|
95
|
-
}
|
|
60
|
+
// Session management
|
|
61
|
+
import { createSession, validateSession } from '@alleyboss/micropay-solana-x402-paywall/session';
|
|
96
62
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
```
|
|
63
|
+
// x402 protocol
|
|
64
|
+
import { buildPaymentRequirement } from '@alleyboss/micropay-solana-x402-paywall/x402';
|
|
100
65
|
|
|
101
|
-
|
|
66
|
+
// Express/Next.js middleware
|
|
67
|
+
import { createExpressMiddleware, createPaywallMiddleware } from '@alleyboss/micropay-solana-x402-paywall/middleware';
|
|
102
68
|
|
|
103
|
-
|
|
104
|
-
import {
|
|
69
|
+
// Anti-replay signature store
|
|
70
|
+
import { createMemoryStore, createRedisStore } from '@alleyboss/micropay-solana-x402-paywall/store';
|
|
105
71
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
recipientWallet: 'CreatorWallet',
|
|
109
|
-
amount: 10_000_000n,
|
|
110
|
-
});
|
|
72
|
+
// Client-side helpers
|
|
73
|
+
import { createPaymentFlow, buildSolanaPayUrl } from '@alleyboss/micropay-solana-x402-paywall/client';
|
|
111
74
|
|
|
112
|
-
//
|
|
113
|
-
|
|
75
|
+
// Price conversion (4-provider rotation)
|
|
76
|
+
import { getSolPrice, formatPriceDisplay, configurePricing } from '@alleyboss/micropay-solana-x402-paywall/pricing';
|
|
114
77
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
// "0.0100 SOL (~$1.50)"
|
|
78
|
+
// Retry utilities
|
|
79
|
+
import { withRetry } from '@alleyboss/micropay-solana-x402-paywall/utils';
|
|
118
80
|
```
|
|
119
81
|
|
|
120
|
-
##
|
|
121
|
-
|
|
122
|
-
Import only what you need for minimal bundle size:
|
|
82
|
+
## π₯ New in v2.0
|
|
123
83
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
| `@.../store` | 2.6KB | `createMemoryStore`, `createRedisStore` |
|
|
131
|
-
| `@.../client` | 3.3KB | `createPaymentFlow`, `buildSolanaPayUrl` |
|
|
132
|
-
| `@.../pricing` | 2KB | `getSolPrice`, `formatPriceDisplay` |
|
|
133
|
-
| `@.../utils` | 1.7KB | `withRetry`, `isRetryableRPCError` |
|
|
84
|
+
- **SPL Token Support** β USDC, USDT, custom tokens
|
|
85
|
+
- **Multi-Provider Pricing** β CoinCap β Binance β CoinGecko β Kraken fallback
|
|
86
|
+
- **Custom Price API** β `configurePricing({ customProvider: yourFn })`
|
|
87
|
+
- **Express Middleware** β Works with Express, Fastify, Polka
|
|
88
|
+
- **Signature Store** β Memory & Redis adapters for anti-replay
|
|
89
|
+
- **Client Helpers** β Solana Pay URLs for QR codes
|
|
134
90
|
|
|
135
|
-
##
|
|
91
|
+
## π Documentation
|
|
136
92
|
|
|
137
|
-
Full
|
|
93
|
+
**Full documentation, API reference, and examples:**
|
|
138
94
|
|
|
139
|
-
|
|
140
|
-
import type {
|
|
141
|
-
PaymentRequirement,
|
|
142
|
-
PaymentAsset,
|
|
143
|
-
SessionData,
|
|
144
|
-
SignatureStore,
|
|
145
|
-
PaywallMiddlewareConfig,
|
|
146
|
-
} from '@alleyboss/micropay-solana-x402-paywall';
|
|
147
|
-
```
|
|
95
|
+
π **[solana-x402-paywall.vercel.app/docs](https://solana-x402-paywall.vercel.app/docs)**
|
|
148
96
|
|
|
149
|
-
## RPC
|
|
97
|
+
## π οΈ RPC Providers
|
|
150
98
|
|
|
151
|
-
|
|
99
|
+
Works with any Solana RPC provider:
|
|
152
100
|
|
|
153
101
|
```typescript
|
|
154
|
-
const
|
|
102
|
+
const config = {
|
|
155
103
|
network: 'mainnet-beta',
|
|
156
|
-
//
|
|
157
|
-
tatumApiKey: 'your-
|
|
158
|
-
//
|
|
159
|
-
rpcUrl: 'https://your-rpc
|
|
104
|
+
// Tatum.io
|
|
105
|
+
tatumApiKey: 'your-key',
|
|
106
|
+
// Or custom (Helius, QuickNode, etc.)
|
|
107
|
+
rpcUrl: 'https://your-rpc.com',
|
|
160
108
|
};
|
|
161
109
|
```
|
|
162
110
|
|
|
163
|
-
## License
|
|
111
|
+
## π License
|
|
164
112
|
|
|
165
113
|
MIT Β© AlleyBoss
|
package/dist/index.cjs
CHANGED
|
@@ -15,8 +15,8 @@ var TOKEN_MINTS = {
|
|
|
15
15
|
};
|
|
16
16
|
var cachedConnection = null;
|
|
17
17
|
var cachedNetwork = null;
|
|
18
|
-
function buildRpcUrl(
|
|
19
|
-
const { network, rpcUrl, tatumApiKey } =
|
|
18
|
+
function buildRpcUrl(config2) {
|
|
19
|
+
const { network, rpcUrl, tatumApiKey } = config2;
|
|
20
20
|
if (rpcUrl) {
|
|
21
21
|
if (rpcUrl.includes("tatum.io") && tatumApiKey && !rpcUrl.includes(tatumApiKey)) {
|
|
22
22
|
return rpcUrl.endsWith("/") ? `${rpcUrl}${tatumApiKey}` : `${rpcUrl}/${tatumApiKey}`;
|
|
@@ -29,12 +29,12 @@ function buildRpcUrl(config) {
|
|
|
29
29
|
}
|
|
30
30
|
return web3_js.clusterApiUrl(network);
|
|
31
31
|
}
|
|
32
|
-
function getConnection(
|
|
33
|
-
const { network } =
|
|
32
|
+
function getConnection(config2) {
|
|
33
|
+
const { network } = config2;
|
|
34
34
|
if (cachedConnection && cachedNetwork === network) {
|
|
35
35
|
return cachedConnection;
|
|
36
36
|
}
|
|
37
|
-
const rpcUrl = buildRpcUrl(
|
|
37
|
+
const rpcUrl = buildRpcUrl(config2);
|
|
38
38
|
cachedConnection = new web3_js.Connection(rpcUrl, {
|
|
39
39
|
commitment: "confirmed",
|
|
40
40
|
confirmTransactionInitialTimeout: 6e4
|
|
@@ -420,19 +420,19 @@ function validateArticleId(articleId) {
|
|
|
420
420
|
const safeIdRegex = /^[a-zA-Z0-9_-]+$/;
|
|
421
421
|
return safeIdRegex.test(articleId);
|
|
422
422
|
}
|
|
423
|
-
async function createSession(walletAddress, articleId,
|
|
423
|
+
async function createSession(walletAddress, articleId, config2, siteWide = false) {
|
|
424
424
|
if (!validateWalletAddress(walletAddress)) {
|
|
425
425
|
throw new Error("Invalid wallet address format");
|
|
426
426
|
}
|
|
427
427
|
if (!validateArticleId(articleId)) {
|
|
428
428
|
throw new Error("Invalid article ID format");
|
|
429
429
|
}
|
|
430
|
-
if (!
|
|
430
|
+
if (!config2.durationHours || config2.durationHours <= 0 || config2.durationHours > 720) {
|
|
431
431
|
throw new Error("Session duration must be between 1 and 720 hours");
|
|
432
432
|
}
|
|
433
433
|
const sessionId = uuid.v4();
|
|
434
434
|
const now = Math.floor(Date.now() / 1e3);
|
|
435
|
-
const expiresAt = now +
|
|
435
|
+
const expiresAt = now + config2.durationHours * 3600;
|
|
436
436
|
const session = {
|
|
437
437
|
id: sessionId,
|
|
438
438
|
walletAddress,
|
|
@@ -449,7 +449,7 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
|
|
|
449
449
|
iat: now,
|
|
450
450
|
exp: expiresAt
|
|
451
451
|
};
|
|
452
|
-
const token = await new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(`${
|
|
452
|
+
const token = await new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(`${config2.durationHours}h`).sign(getSecretKey(config2.secret));
|
|
453
453
|
return { token, session };
|
|
454
454
|
}
|
|
455
455
|
async function validateSession(token, secret) {
|
|
@@ -806,8 +806,8 @@ function matchesProtectedPath(path, patterns) {
|
|
|
806
806
|
}
|
|
807
807
|
return false;
|
|
808
808
|
}
|
|
809
|
-
async function checkPaywallAccess(path, sessionToken,
|
|
810
|
-
if (!matchesProtectedPath(path,
|
|
809
|
+
async function checkPaywallAccess(path, sessionToken, config2) {
|
|
810
|
+
if (!matchesProtectedPath(path, config2.protectedPaths)) {
|
|
811
811
|
return { allowed: true };
|
|
812
812
|
}
|
|
813
813
|
if (!sessionToken) {
|
|
@@ -817,7 +817,7 @@ async function checkPaywallAccess(path, sessionToken, config) {
|
|
|
817
817
|
requiresPayment: true
|
|
818
818
|
};
|
|
819
819
|
}
|
|
820
|
-
const validation = await validateSession(sessionToken,
|
|
820
|
+
const validation = await validateSession(sessionToken, config2.sessionSecret);
|
|
821
821
|
if (!validation.valid || !validation.session) {
|
|
822
822
|
return {
|
|
823
823
|
allowed: false,
|
|
@@ -830,8 +830,8 @@ async function checkPaywallAccess(path, sessionToken, config) {
|
|
|
830
830
|
session: validation.session
|
|
831
831
|
};
|
|
832
832
|
}
|
|
833
|
-
function createPaywallMiddleware(
|
|
834
|
-
const { cookieName = "x402_session" } =
|
|
833
|
+
function createPaywallMiddleware(config2) {
|
|
834
|
+
const { cookieName = "x402_session" } = config2;
|
|
835
835
|
return async function middleware(request) {
|
|
836
836
|
const url = new URL(request.url);
|
|
837
837
|
const path = url.pathname;
|
|
@@ -843,9 +843,9 @@ function createPaywallMiddleware(config) {
|
|
|
843
843
|
})
|
|
844
844
|
);
|
|
845
845
|
const sessionToken = cookies[cookieName];
|
|
846
|
-
const result = await checkPaywallAccess(path, sessionToken,
|
|
846
|
+
const result = await checkPaywallAccess(path, sessionToken, config2);
|
|
847
847
|
if (!result.allowed && result.requiresPayment) {
|
|
848
|
-
const body =
|
|
848
|
+
const body = config2.custom402Response ? config2.custom402Response(path) : {
|
|
849
849
|
error: "Payment Required",
|
|
850
850
|
message: "This resource requires payment to access",
|
|
851
851
|
path
|
|
@@ -981,8 +981,8 @@ function buildSolanaPayUrl(params) {
|
|
|
981
981
|
}
|
|
982
982
|
return url.toString();
|
|
983
983
|
}
|
|
984
|
-
function createPaymentFlow(
|
|
985
|
-
const { network, recipientWallet, amount, asset = "native", memo } =
|
|
984
|
+
function createPaymentFlow(config2) {
|
|
985
|
+
const { network, recipientWallet, amount, asset = "native", memo } = config2;
|
|
986
986
|
let decimals = 9;
|
|
987
987
|
let mintAddress;
|
|
988
988
|
if (asset === "usdc") {
|
|
@@ -998,7 +998,7 @@ function createPaymentFlow(config) {
|
|
|
998
998
|
const naturalAmount = Number(amount) / Math.pow(10, decimals);
|
|
999
999
|
return {
|
|
1000
1000
|
/** Get the payment configuration */
|
|
1001
|
-
getConfig: () => ({ ...
|
|
1001
|
+
getConfig: () => ({ ...config2 }),
|
|
1002
1002
|
/** Get amount in natural display units (e.g., 0.01 SOL) */
|
|
1003
1003
|
getDisplayAmount: () => naturalAmount,
|
|
1004
1004
|
/** Get amount formatted with symbol */
|
|
@@ -1044,43 +1044,100 @@ function createPaymentReference() {
|
|
|
1044
1044
|
|
|
1045
1045
|
// src/pricing/index.ts
|
|
1046
1046
|
var cachedPrice = null;
|
|
1047
|
-
var
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1047
|
+
var config = {};
|
|
1048
|
+
var lastProviderIndex = -1;
|
|
1049
|
+
function configurePricing(newConfig) {
|
|
1050
|
+
config = { ...config, ...newConfig };
|
|
1051
|
+
cachedPrice = null;
|
|
1052
|
+
}
|
|
1053
|
+
var PROVIDERS = [
|
|
1054
|
+
{
|
|
1055
|
+
name: "coincap",
|
|
1056
|
+
url: "https://api.coincap.io/v2/assets/solana",
|
|
1057
|
+
parse: (data) => parseFloat(data.data?.priceUsd || "0")
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
name: "binance",
|
|
1061
|
+
url: "https://api.binance.com/api/v3/ticker/price?symbol=SOLUSDT",
|
|
1062
|
+
parse: (data) => parseFloat(data.price || "0")
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
name: "coingecko",
|
|
1066
|
+
url: "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
|
|
1067
|
+
parse: (data) => data.solana?.usd || 0
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
name: "kraken",
|
|
1071
|
+
url: "https://api.kraken.com/0/public/Ticker?pair=SOLUSD",
|
|
1072
|
+
parse: (data) => parseFloat(data.result?.SOLUSD?.c?.[0] || "0")
|
|
1073
|
+
}
|
|
1074
|
+
];
|
|
1075
|
+
async function fetchFromProvider(provider, timeout) {
|
|
1076
|
+
const controller = new AbortController();
|
|
1077
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1052
1078
|
try {
|
|
1053
|
-
const response = await fetch(
|
|
1054
|
-
"
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
signal: AbortSignal.timeout(5e3)
|
|
1058
|
-
}
|
|
1059
|
-
);
|
|
1079
|
+
const response = await fetch(provider.url, {
|
|
1080
|
+
headers: { "Accept": "application/json" },
|
|
1081
|
+
signal: controller.signal
|
|
1082
|
+
});
|
|
1060
1083
|
if (!response.ok) {
|
|
1061
|
-
throw new Error(`
|
|
1084
|
+
throw new Error(`HTTP ${response.status}`);
|
|
1062
1085
|
}
|
|
1063
1086
|
const data = await response.json();
|
|
1064
|
-
|
|
1065
|
-
|
|
1087
|
+
const price = provider.parse(data);
|
|
1088
|
+
if (!price || price <= 0) {
|
|
1089
|
+
throw new Error("Invalid price");
|
|
1066
1090
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1091
|
+
return price;
|
|
1092
|
+
} finally {
|
|
1093
|
+
clearTimeout(timeoutId);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
async function getSolPrice() {
|
|
1097
|
+
const cacheTTL = config.cacheTTL ?? 6e4;
|
|
1098
|
+
const timeout = config.timeout ?? 5e3;
|
|
1099
|
+
if (cachedPrice && Date.now() - cachedPrice.fetchedAt.getTime() < cacheTTL) {
|
|
1072
1100
|
return cachedPrice;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1101
|
+
}
|
|
1102
|
+
if (config.customProvider) {
|
|
1103
|
+
try {
|
|
1104
|
+
const price = await config.customProvider();
|
|
1105
|
+
if (price > 0) {
|
|
1106
|
+
cachedPrice = {
|
|
1107
|
+
solPrice: price,
|
|
1108
|
+
fetchedAt: /* @__PURE__ */ new Date(),
|
|
1109
|
+
source: "custom"
|
|
1110
|
+
};
|
|
1111
|
+
return cachedPrice;
|
|
1112
|
+
}
|
|
1113
|
+
} catch {
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
for (let i = 0; i < PROVIDERS.length; i++) {
|
|
1117
|
+
const idx = (lastProviderIndex + 1 + i) % PROVIDERS.length;
|
|
1118
|
+
const provider = PROVIDERS[idx];
|
|
1119
|
+
try {
|
|
1120
|
+
const price = await fetchFromProvider(provider, timeout);
|
|
1121
|
+
lastProviderIndex = idx;
|
|
1122
|
+
cachedPrice = {
|
|
1123
|
+
solPrice: price,
|
|
1124
|
+
fetchedAt: /* @__PURE__ */ new Date(),
|
|
1125
|
+
source: provider.name
|
|
1126
|
+
};
|
|
1075
1127
|
return cachedPrice;
|
|
1128
|
+
} catch {
|
|
1129
|
+
continue;
|
|
1076
1130
|
}
|
|
1077
|
-
return {
|
|
1078
|
-
solPrice: 150,
|
|
1079
|
-
// Fallback price
|
|
1080
|
-
fetchedAt: /* @__PURE__ */ new Date(),
|
|
1081
|
-
source: "fallback"
|
|
1082
|
-
};
|
|
1083
1131
|
}
|
|
1132
|
+
if (cachedPrice) {
|
|
1133
|
+
return cachedPrice;
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
solPrice: 150,
|
|
1137
|
+
// Reasonable fallback
|
|
1138
|
+
fetchedAt: /* @__PURE__ */ new Date(),
|
|
1139
|
+
source: "fallback"
|
|
1140
|
+
};
|
|
1084
1141
|
}
|
|
1085
1142
|
async function lamportsToUsd(lamports) {
|
|
1086
1143
|
const { solPrice } = await getSolPrice();
|
|
@@ -1109,6 +1166,10 @@ function formatPriceSync(lamports, solPrice) {
|
|
|
1109
1166
|
}
|
|
1110
1167
|
function clearPriceCache() {
|
|
1111
1168
|
cachedPrice = null;
|
|
1169
|
+
lastProviderIndex = -1;
|
|
1170
|
+
}
|
|
1171
|
+
function getProviders() {
|
|
1172
|
+
return PROVIDERS.map((p) => ({ name: p.name, url: p.url }));
|
|
1112
1173
|
}
|
|
1113
1174
|
|
|
1114
1175
|
exports.TOKEN_MINTS = TOKEN_MINTS;
|
|
@@ -1118,6 +1179,7 @@ exports.buildPaymentRequirement = buildPaymentRequirement;
|
|
|
1118
1179
|
exports.buildSolanaPayUrl = buildSolanaPayUrl;
|
|
1119
1180
|
exports.checkPaywallAccess = checkPaywallAccess;
|
|
1120
1181
|
exports.clearPriceCache = clearPriceCache;
|
|
1182
|
+
exports.configurePricing = configurePricing;
|
|
1121
1183
|
exports.create402Headers = create402Headers;
|
|
1122
1184
|
exports.create402ResponseBody = create402ResponseBody;
|
|
1123
1185
|
exports.createMemoryStore = createMemoryStore;
|
|
@@ -1132,6 +1194,7 @@ exports.encodePaymentResponse = encodePaymentResponse;
|
|
|
1132
1194
|
exports.formatPriceDisplay = formatPriceDisplay;
|
|
1133
1195
|
exports.formatPriceSync = formatPriceSync;
|
|
1134
1196
|
exports.getConnection = getConnection;
|
|
1197
|
+
exports.getProviders = getProviders;
|
|
1135
1198
|
exports.getSolPrice = getSolPrice;
|
|
1136
1199
|
exports.getTokenDecimals = getTokenDecimals;
|
|
1137
1200
|
exports.getWalletTransactions = getWalletTransactions;
|