@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/next/index.cjs
CHANGED
|
@@ -20,20 +20,48 @@ var LocalSvmFacilitator = class {
|
|
|
20
20
|
* Get supported payment kinds
|
|
21
21
|
* Mocking the response of the /supported endpoint
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Network Constants - CAIP-2 format for x402 v2
|
|
25
|
+
* These match the official Solana chain IDs used by x402 protocol
|
|
26
|
+
*/
|
|
24
27
|
NETWORKS = {
|
|
25
28
|
DEVNET: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
26
29
|
MAINNET: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
|
|
27
30
|
};
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
/**
|
|
32
|
+
* DUMMY_FEE_PAYER Explanation (for auditors):
|
|
33
|
+
*
|
|
34
|
+
* In a HOSTED facilitator (like x402.org), the `feePayer` field in the
|
|
35
|
+
* SupportedResponse.extra specifies which address will pay transaction fees
|
|
36
|
+
* when the facilitator submits transactions on behalf of users.
|
|
37
|
+
*
|
|
38
|
+
* In LOCAL/SELF-SOVEREIGN mode (this implementation):
|
|
39
|
+
* - The USER pays their own transaction fees directly
|
|
40
|
+
* - The facilitator NEVER submits transactions - it only VERIFIES them
|
|
41
|
+
* - Therefore, no fee payer address is actually used
|
|
42
|
+
*
|
|
43
|
+
* However, the x402 protocol REQUIRES this field in the response schema.
|
|
44
|
+
* We use the System Program address (all 1s) as a placeholder because:
|
|
45
|
+
* 1. It's clearly not a real wallet (obvious placeholder)
|
|
46
|
+
* 2. It cannot receive funds or sign transactions
|
|
47
|
+
* 3. It signals to developers that fee paying is handled differently
|
|
48
|
+
*
|
|
49
|
+
* SECURITY NOTE: This is NOT a security risk because:
|
|
50
|
+
* - The fee payer is never used in verify() or settle() methods
|
|
51
|
+
* - Users sign and pay for their own transactions
|
|
52
|
+
* - The address is just a protocol-required placeholder
|
|
53
|
+
*
|
|
54
|
+
* @see https://docs.x402.org for protocol specification
|
|
55
|
+
*/
|
|
30
56
|
DUMMY_FEE_PAYER = "11111111111111111111111111111111";
|
|
31
57
|
/**
|
|
32
58
|
* Get supported payment kinds
|
|
33
59
|
* Returns x402 v2 compatible response with CAIP-2 network identifiers
|
|
60
|
+
*
|
|
61
|
+
* NOTE: The feePayer in extra.feePayer is a placeholder only.
|
|
62
|
+
* In self-sovereign mode, users pay their own transaction fees.
|
|
34
63
|
*/
|
|
35
64
|
async getSupported(_extensionKeys = []) {
|
|
36
|
-
console.log("[LocalSvmFacilitator] getSupported called");
|
|
37
65
|
const supported = {
|
|
38
66
|
kinds: [
|
|
39
67
|
{
|
|
@@ -51,10 +79,10 @@ var LocalSvmFacilitator = class {
|
|
|
51
79
|
],
|
|
52
80
|
extensions: [],
|
|
53
81
|
signers: {
|
|
82
|
+
// Placeholder - in self-sovereign mode, users are their own signers
|
|
54
83
|
"solana:*": [this.DUMMY_FEE_PAYER]
|
|
55
84
|
}
|
|
56
85
|
};
|
|
57
|
-
console.log("[LocalSvmFacilitator] Returning supported:", JSON.stringify(supported));
|
|
58
86
|
return supported;
|
|
59
87
|
}
|
|
60
88
|
/**
|
|
@@ -69,6 +97,10 @@ var LocalSvmFacilitator = class {
|
|
|
69
97
|
getSigners(_network) {
|
|
70
98
|
return [];
|
|
71
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Enable debug logging (disable in production)
|
|
102
|
+
*/
|
|
103
|
+
debug = process.env.NODE_ENV === "development";
|
|
72
104
|
/**
|
|
73
105
|
* Verify a payment on-chain
|
|
74
106
|
*/
|
|
@@ -81,57 +113,89 @@ var LocalSvmFacilitator = class {
|
|
|
81
113
|
const payTo = requirements.payTo;
|
|
82
114
|
const amountVal = requirements.amount || requirements.maxAmountRequired || "0";
|
|
83
115
|
const requiredAmount = BigInt(amountVal);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const tx = await this.
|
|
88
|
-
maxSupportedTransactionVersion: 0,
|
|
89
|
-
commitment: "confirmed"
|
|
90
|
-
});
|
|
116
|
+
if (this.debug) {
|
|
117
|
+
console.log(`[LocalSvmFacilitator] Verifying tx: ${signature.slice(0, 8)}...`);
|
|
118
|
+
}
|
|
119
|
+
const tx = await this.fetchTransactionWithRetry(signature, 3);
|
|
91
120
|
if (!tx) {
|
|
92
|
-
console.error("[LocalSvmFacilitator] Transaction not found or not confirmed");
|
|
93
121
|
return { isValid: false, invalidReason: "Transaction not found or not confirmed" };
|
|
94
122
|
}
|
|
95
|
-
console.log("[LocalSvmFacilitator] Transaction found. Parsing instructions...");
|
|
96
123
|
const instructions = tx.transaction.message.instructions;
|
|
97
124
|
let paidAmount = 0n;
|
|
98
125
|
let payer = void 0;
|
|
99
126
|
for (const ix of instructions) {
|
|
100
127
|
if ("program" in ix && ix.program === "system") {
|
|
101
128
|
const parsed = ix.parsed;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
129
|
+
if (parsed?.type === "transfer" && parsed.info?.destination === payTo) {
|
|
130
|
+
paidAmount += BigInt(parsed.info.lamports);
|
|
131
|
+
if (!payer) payer = parsed.info.source;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if ("program" in ix && (ix.program === "spl-token" || ix.program === "spl-token-2022")) {
|
|
135
|
+
const parsed = ix.parsed;
|
|
136
|
+
if (parsed?.type === "transferChecked" || parsed?.type === "transfer") {
|
|
137
|
+
if (this.debug) {
|
|
138
|
+
console.log(`[LocalSvmFacilitator] Found SPL transfer`);
|
|
109
139
|
}
|
|
110
140
|
}
|
|
111
141
|
}
|
|
112
142
|
}
|
|
113
|
-
console.log(`[LocalSvmFacilitator] Total Paid Correctly: ${paidAmount}`);
|
|
114
143
|
if (paidAmount >= requiredAmount) {
|
|
115
|
-
|
|
144
|
+
if (this.debug) {
|
|
145
|
+
console.log(`[LocalSvmFacilitator] Verification SUCCESS for tx: ${signature.slice(0, 8)}...`);
|
|
146
|
+
}
|
|
116
147
|
return {
|
|
117
148
|
isValid: true,
|
|
118
149
|
payer: payer || tx.transaction.message.accountKeys[0].pubkey.toBase58()
|
|
119
150
|
};
|
|
120
151
|
}
|
|
121
|
-
console.error(`[LocalSvmFacilitator] Verification FAILED. Paid: ${paidAmount}, Required: ${requiredAmount}`);
|
|
122
152
|
return {
|
|
123
153
|
isValid: false,
|
|
124
|
-
invalidReason:
|
|
154
|
+
invalidReason: "Insufficient payment amount",
|
|
125
155
|
payer
|
|
126
156
|
};
|
|
127
157
|
} catch (error) {
|
|
128
|
-
|
|
158
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
159
|
+
if (this.debug) {
|
|
160
|
+
console.error("[LocalSvmFacilitator] Verify error:", errorMessage);
|
|
161
|
+
}
|
|
129
162
|
throw new types.VerifyError(500, {
|
|
130
163
|
isValid: false,
|
|
131
|
-
invalidReason:
|
|
164
|
+
invalidReason: errorMessage
|
|
132
165
|
});
|
|
133
166
|
}
|
|
134
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Fetch transaction with exponential backoff retry
|
|
170
|
+
*/
|
|
171
|
+
async fetchTransactionWithRetry(signature, maxRetries = 3) {
|
|
172
|
+
let lastError;
|
|
173
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
174
|
+
try {
|
|
175
|
+
const tx = await this.connection.getParsedTransaction(signature, {
|
|
176
|
+
maxSupportedTransactionVersion: 0,
|
|
177
|
+
commitment: "confirmed"
|
|
178
|
+
});
|
|
179
|
+
if (tx) return tx;
|
|
180
|
+
if (attempt < maxRetries - 1) {
|
|
181
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
lastError = error instanceof Error ? error : new Error("RPC error");
|
|
185
|
+
if (attempt < maxRetries - 1) {
|
|
186
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (lastError) throw lastError;
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Sleep helper
|
|
195
|
+
*/
|
|
196
|
+
sleep(ms) {
|
|
197
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
198
|
+
}
|
|
135
199
|
/**
|
|
136
200
|
* Settle a payment (not applicable for direct chain verification, usually)
|
|
137
201
|
* But we must implement it. For 'exact', settlement is just verification + finality.
|
|
@@ -177,7 +241,6 @@ function createX402Middleware(config) {
|
|
|
177
241
|
network: config.network === "mainnet-beta" ? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" : "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"
|
|
178
242
|
}
|
|
179
243
|
};
|
|
180
|
-
console.log("[createX402Middleware] Final Config:", JSON.stringify(finalConfig));
|
|
181
244
|
return next.withX402(handler, finalConfig, server$2);
|
|
182
245
|
};
|
|
183
246
|
}
|
package/dist/next/index.js
CHANGED
|
@@ -19,20 +19,48 @@ var LocalSvmFacilitator = class {
|
|
|
19
19
|
* Get supported payment kinds
|
|
20
20
|
* Mocking the response of the /supported endpoint
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Network Constants - CAIP-2 format for x402 v2
|
|
24
|
+
* These match the official Solana chain IDs used by x402 protocol
|
|
25
|
+
*/
|
|
23
26
|
NETWORKS = {
|
|
24
27
|
DEVNET: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
25
28
|
MAINNET: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
|
|
26
29
|
};
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
/**
|
|
31
|
+
* DUMMY_FEE_PAYER Explanation (for auditors):
|
|
32
|
+
*
|
|
33
|
+
* In a HOSTED facilitator (like x402.org), the `feePayer` field in the
|
|
34
|
+
* SupportedResponse.extra specifies which address will pay transaction fees
|
|
35
|
+
* when the facilitator submits transactions on behalf of users.
|
|
36
|
+
*
|
|
37
|
+
* In LOCAL/SELF-SOVEREIGN mode (this implementation):
|
|
38
|
+
* - The USER pays their own transaction fees directly
|
|
39
|
+
* - The facilitator NEVER submits transactions - it only VERIFIES them
|
|
40
|
+
* - Therefore, no fee payer address is actually used
|
|
41
|
+
*
|
|
42
|
+
* However, the x402 protocol REQUIRES this field in the response schema.
|
|
43
|
+
* We use the System Program address (all 1s) as a placeholder because:
|
|
44
|
+
* 1. It's clearly not a real wallet (obvious placeholder)
|
|
45
|
+
* 2. It cannot receive funds or sign transactions
|
|
46
|
+
* 3. It signals to developers that fee paying is handled differently
|
|
47
|
+
*
|
|
48
|
+
* SECURITY NOTE: This is NOT a security risk because:
|
|
49
|
+
* - The fee payer is never used in verify() or settle() methods
|
|
50
|
+
* - Users sign and pay for their own transactions
|
|
51
|
+
* - The address is just a protocol-required placeholder
|
|
52
|
+
*
|
|
53
|
+
* @see https://docs.x402.org for protocol specification
|
|
54
|
+
*/
|
|
29
55
|
DUMMY_FEE_PAYER = "11111111111111111111111111111111";
|
|
30
56
|
/**
|
|
31
57
|
* Get supported payment kinds
|
|
32
58
|
* Returns x402 v2 compatible response with CAIP-2 network identifiers
|
|
59
|
+
*
|
|
60
|
+
* NOTE: The feePayer in extra.feePayer is a placeholder only.
|
|
61
|
+
* In self-sovereign mode, users pay their own transaction fees.
|
|
33
62
|
*/
|
|
34
63
|
async getSupported(_extensionKeys = []) {
|
|
35
|
-
console.log("[LocalSvmFacilitator] getSupported called");
|
|
36
64
|
const supported = {
|
|
37
65
|
kinds: [
|
|
38
66
|
{
|
|
@@ -50,10 +78,10 @@ var LocalSvmFacilitator = class {
|
|
|
50
78
|
],
|
|
51
79
|
extensions: [],
|
|
52
80
|
signers: {
|
|
81
|
+
// Placeholder - in self-sovereign mode, users are their own signers
|
|
53
82
|
"solana:*": [this.DUMMY_FEE_PAYER]
|
|
54
83
|
}
|
|
55
84
|
};
|
|
56
|
-
console.log("[LocalSvmFacilitator] Returning supported:", JSON.stringify(supported));
|
|
57
85
|
return supported;
|
|
58
86
|
}
|
|
59
87
|
/**
|
|
@@ -68,6 +96,10 @@ var LocalSvmFacilitator = class {
|
|
|
68
96
|
getSigners(_network) {
|
|
69
97
|
return [];
|
|
70
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Enable debug logging (disable in production)
|
|
101
|
+
*/
|
|
102
|
+
debug = process.env.NODE_ENV === "development";
|
|
71
103
|
/**
|
|
72
104
|
* Verify a payment on-chain
|
|
73
105
|
*/
|
|
@@ -80,57 +112,89 @@ var LocalSvmFacilitator = class {
|
|
|
80
112
|
const payTo = requirements.payTo;
|
|
81
113
|
const amountVal = requirements.amount || requirements.maxAmountRequired || "0";
|
|
82
114
|
const requiredAmount = BigInt(amountVal);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const tx = await this.
|
|
87
|
-
maxSupportedTransactionVersion: 0,
|
|
88
|
-
commitment: "confirmed"
|
|
89
|
-
});
|
|
115
|
+
if (this.debug) {
|
|
116
|
+
console.log(`[LocalSvmFacilitator] Verifying tx: ${signature.slice(0, 8)}...`);
|
|
117
|
+
}
|
|
118
|
+
const tx = await this.fetchTransactionWithRetry(signature, 3);
|
|
90
119
|
if (!tx) {
|
|
91
|
-
console.error("[LocalSvmFacilitator] Transaction not found or not confirmed");
|
|
92
120
|
return { isValid: false, invalidReason: "Transaction not found or not confirmed" };
|
|
93
121
|
}
|
|
94
|
-
console.log("[LocalSvmFacilitator] Transaction found. Parsing instructions...");
|
|
95
122
|
const instructions = tx.transaction.message.instructions;
|
|
96
123
|
let paidAmount = 0n;
|
|
97
124
|
let payer = void 0;
|
|
98
125
|
for (const ix of instructions) {
|
|
99
126
|
if ("program" in ix && ix.program === "system") {
|
|
100
127
|
const parsed = ix.parsed;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
if (parsed?.type === "transfer" && parsed.info?.destination === payTo) {
|
|
129
|
+
paidAmount += BigInt(parsed.info.lamports);
|
|
130
|
+
if (!payer) payer = parsed.info.source;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if ("program" in ix && (ix.program === "spl-token" || ix.program === "spl-token-2022")) {
|
|
134
|
+
const parsed = ix.parsed;
|
|
135
|
+
if (parsed?.type === "transferChecked" || parsed?.type === "transfer") {
|
|
136
|
+
if (this.debug) {
|
|
137
|
+
console.log(`[LocalSvmFacilitator] Found SPL transfer`);
|
|
108
138
|
}
|
|
109
139
|
}
|
|
110
140
|
}
|
|
111
141
|
}
|
|
112
|
-
console.log(`[LocalSvmFacilitator] Total Paid Correctly: ${paidAmount}`);
|
|
113
142
|
if (paidAmount >= requiredAmount) {
|
|
114
|
-
|
|
143
|
+
if (this.debug) {
|
|
144
|
+
console.log(`[LocalSvmFacilitator] Verification SUCCESS for tx: ${signature.slice(0, 8)}...`);
|
|
145
|
+
}
|
|
115
146
|
return {
|
|
116
147
|
isValid: true,
|
|
117
148
|
payer: payer || tx.transaction.message.accountKeys[0].pubkey.toBase58()
|
|
118
149
|
};
|
|
119
150
|
}
|
|
120
|
-
console.error(`[LocalSvmFacilitator] Verification FAILED. Paid: ${paidAmount}, Required: ${requiredAmount}`);
|
|
121
151
|
return {
|
|
122
152
|
isValid: false,
|
|
123
|
-
invalidReason:
|
|
153
|
+
invalidReason: "Insufficient payment amount",
|
|
124
154
|
payer
|
|
125
155
|
};
|
|
126
156
|
} catch (error) {
|
|
127
|
-
|
|
157
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
158
|
+
if (this.debug) {
|
|
159
|
+
console.error("[LocalSvmFacilitator] Verify error:", errorMessage);
|
|
160
|
+
}
|
|
128
161
|
throw new VerifyError(500, {
|
|
129
162
|
isValid: false,
|
|
130
|
-
invalidReason:
|
|
163
|
+
invalidReason: errorMessage
|
|
131
164
|
});
|
|
132
165
|
}
|
|
133
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Fetch transaction with exponential backoff retry
|
|
169
|
+
*/
|
|
170
|
+
async fetchTransactionWithRetry(signature, maxRetries = 3) {
|
|
171
|
+
let lastError;
|
|
172
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
173
|
+
try {
|
|
174
|
+
const tx = await this.connection.getParsedTransaction(signature, {
|
|
175
|
+
maxSupportedTransactionVersion: 0,
|
|
176
|
+
commitment: "confirmed"
|
|
177
|
+
});
|
|
178
|
+
if (tx) return tx;
|
|
179
|
+
if (attempt < maxRetries - 1) {
|
|
180
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
lastError = error instanceof Error ? error : new Error("RPC error");
|
|
184
|
+
if (attempt < maxRetries - 1) {
|
|
185
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (lastError) throw lastError;
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Sleep helper
|
|
194
|
+
*/
|
|
195
|
+
sleep(ms) {
|
|
196
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
197
|
+
}
|
|
134
198
|
/**
|
|
135
199
|
* Settle a payment (not applicable for direct chain verification, usually)
|
|
136
200
|
* But we must implement it. For 'exact', settlement is just verification + finality.
|
|
@@ -176,7 +240,6 @@ function createX402Middleware(config) {
|
|
|
176
240
|
network: config.network === "mainnet-beta" ? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" : "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"
|
|
177
241
|
}
|
|
178
242
|
};
|
|
179
|
-
console.log("[createX402Middleware] Final Config:", JSON.stringify(finalConfig));
|
|
180
243
|
return withX402$1(handler, finalConfig, server);
|
|
181
244
|
};
|
|
182
245
|
}
|
package/dist/pricing/index.cjs
CHANGED
|
@@ -6,12 +6,10 @@ function lamportsToSol(lamports) {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
// src/pricing/index.ts
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var lastProviderIndex = -1;
|
|
9
|
+
var priceCache = null;
|
|
10
|
+
var currentConfig = {};
|
|
12
11
|
function configurePricing(newConfig) {
|
|
13
|
-
|
|
14
|
-
cachedPrice = null;
|
|
12
|
+
currentConfig = { ...currentConfig, ...newConfig };
|
|
15
13
|
}
|
|
16
14
|
var PROVIDERS = [
|
|
17
15
|
{
|
|
@@ -51,56 +49,75 @@ async function fetchFromProvider(provider, timeout) {
|
|
|
51
49
|
if (!price || price <= 0) {
|
|
52
50
|
throw new Error("Invalid price");
|
|
53
51
|
}
|
|
54
|
-
return price;
|
|
52
|
+
return { price, source: provider.name };
|
|
55
53
|
} finally {
|
|
56
54
|
clearTimeout(timeoutId);
|
|
57
55
|
}
|
|
58
56
|
}
|
|
57
|
+
async function fetchPriceParallel(timeout) {
|
|
58
|
+
const promises = PROVIDERS.map(
|
|
59
|
+
(provider) => fetchFromProvider(provider, timeout).catch(() => null)
|
|
60
|
+
);
|
|
61
|
+
const results = await Promise.all(promises);
|
|
62
|
+
const validResult = results.find((r) => r !== null);
|
|
63
|
+
if (validResult) {
|
|
64
|
+
return validResult;
|
|
65
|
+
}
|
|
66
|
+
throw new Error("All providers failed");
|
|
67
|
+
}
|
|
68
|
+
async function fetchPriceSequential(timeout) {
|
|
69
|
+
for (const provider of PROVIDERS) {
|
|
70
|
+
try {
|
|
71
|
+
return await fetchFromProvider(provider, timeout);
|
|
72
|
+
} catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
throw new Error("All providers failed");
|
|
77
|
+
}
|
|
59
78
|
async function getSolPrice() {
|
|
60
|
-
const cacheTTL =
|
|
61
|
-
const timeout =
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
const cacheTTL = currentConfig.cacheTTL ?? 6e4;
|
|
80
|
+
const timeout = currentConfig.timeout ?? 3e3;
|
|
81
|
+
const useParallel = currentConfig.parallelFetch ?? true;
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
if (priceCache && now - priceCache.timestamp < cacheTTL) {
|
|
84
|
+
return priceCache.data;
|
|
64
85
|
}
|
|
65
|
-
if (
|
|
86
|
+
if (currentConfig.customProvider) {
|
|
66
87
|
try {
|
|
67
|
-
const price = await
|
|
88
|
+
const price = await currentConfig.customProvider();
|
|
68
89
|
if (price > 0) {
|
|
69
|
-
|
|
90
|
+
const data = {
|
|
70
91
|
solPrice: price,
|
|
71
92
|
fetchedAt: /* @__PURE__ */ new Date(),
|
|
72
93
|
source: "custom"
|
|
73
94
|
};
|
|
74
|
-
|
|
95
|
+
priceCache = { data, timestamp: now };
|
|
96
|
+
return data;
|
|
75
97
|
}
|
|
76
98
|
} catch {
|
|
77
99
|
}
|
|
78
100
|
}
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
try {
|
|
102
|
+
const result = useParallel ? await fetchPriceParallel(timeout) : await fetchPriceSequential(timeout);
|
|
103
|
+
const data = {
|
|
104
|
+
solPrice: result.price,
|
|
105
|
+
fetchedAt: /* @__PURE__ */ new Date(),
|
|
106
|
+
source: result.source
|
|
107
|
+
};
|
|
108
|
+
priceCache = { data, timestamp: now };
|
|
109
|
+
return data;
|
|
110
|
+
} catch {
|
|
111
|
+
if (priceCache) {
|
|
112
|
+
return {
|
|
113
|
+
...priceCache.data,
|
|
114
|
+
source: `${priceCache.data.source} (stale)`
|
|
89
115
|
};
|
|
90
|
-
return cachedPrice;
|
|
91
|
-
} catch {
|
|
92
|
-
continue;
|
|
93
116
|
}
|
|
117
|
+
throw new Error(
|
|
118
|
+
"Failed to fetch SOL price from all providers. Configure a custom provider or ensure network connectivity."
|
|
119
|
+
);
|
|
94
120
|
}
|
|
95
|
-
if (cachedPrice) {
|
|
96
|
-
return {
|
|
97
|
-
...cachedPrice,
|
|
98
|
-
source: `${cachedPrice.source} (stale)`
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
throw new Error(
|
|
102
|
-
"Failed to fetch SOL price from all providers. Configure a custom provider or ensure network connectivity."
|
|
103
|
-
);
|
|
104
121
|
}
|
|
105
122
|
async function lamportsToUsd(lamports) {
|
|
106
123
|
const { solPrice } = await getSolPrice();
|
|
@@ -128,8 +145,7 @@ function formatPriceSync(lamports, solPrice) {
|
|
|
128
145
|
};
|
|
129
146
|
}
|
|
130
147
|
function clearPriceCache() {
|
|
131
|
-
|
|
132
|
-
lastProviderIndex = -1;
|
|
148
|
+
priceCache = null;
|
|
133
149
|
}
|
|
134
150
|
function getProviders() {
|
|
135
151
|
return PROVIDERS.map((p) => ({ name: p.name, url: p.url }));
|
package/dist/pricing/index.d.cts
CHANGED
|
@@ -23,8 +23,10 @@ interface PriceConfig {
|
|
|
23
23
|
customProvider?: CustomPriceProvider;
|
|
24
24
|
/** Cache TTL in milliseconds (default: 60000) */
|
|
25
25
|
cacheTTL?: number;
|
|
26
|
-
/** Request timeout in milliseconds (default:
|
|
26
|
+
/** Request timeout in milliseconds (default: 3000) */
|
|
27
27
|
timeout?: number;
|
|
28
|
+
/** Use parallel fetching (default: true, faster but more network calls) */
|
|
29
|
+
parallelFetch?: boolean;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Configure price fetching
|
|
@@ -39,19 +41,16 @@ interface PriceConfig {
|
|
|
39
41
|
* },
|
|
40
42
|
* });
|
|
41
43
|
*
|
|
42
|
-
* // Or
|
|
43
|
-
* configurePricing({ cacheTTL: 30000 });
|
|
44
|
+
* // Or adjust settings
|
|
45
|
+
* configurePricing({ cacheTTL: 30000, parallelFetch: true });
|
|
44
46
|
* ```
|
|
45
47
|
*/
|
|
46
48
|
declare function configurePricing(newConfig: PriceConfig): void;
|
|
47
49
|
/**
|
|
48
|
-
* Get SOL price with multi-provider
|
|
50
|
+
* Get SOL price with multi-provider support
|
|
49
51
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* 2. Binance (backup #1)
|
|
53
|
-
* 3. CoinGecko (backup #2)
|
|
54
|
-
* 4. Kraken (backup #3)
|
|
52
|
+
* Default behavior: Parallel fetch (all providers race, fastest wins)
|
|
53
|
+
* This is faster and more reliable in serverless environments.
|
|
55
54
|
*
|
|
56
55
|
* @example
|
|
57
56
|
* ```typescript
|
package/dist/pricing/index.d.ts
CHANGED
|
@@ -23,8 +23,10 @@ interface PriceConfig {
|
|
|
23
23
|
customProvider?: CustomPriceProvider;
|
|
24
24
|
/** Cache TTL in milliseconds (default: 60000) */
|
|
25
25
|
cacheTTL?: number;
|
|
26
|
-
/** Request timeout in milliseconds (default:
|
|
26
|
+
/** Request timeout in milliseconds (default: 3000) */
|
|
27
27
|
timeout?: number;
|
|
28
|
+
/** Use parallel fetching (default: true, faster but more network calls) */
|
|
29
|
+
parallelFetch?: boolean;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Configure price fetching
|
|
@@ -39,19 +41,16 @@ interface PriceConfig {
|
|
|
39
41
|
* },
|
|
40
42
|
* });
|
|
41
43
|
*
|
|
42
|
-
* // Or
|
|
43
|
-
* configurePricing({ cacheTTL: 30000 });
|
|
44
|
+
* // Or adjust settings
|
|
45
|
+
* configurePricing({ cacheTTL: 30000, parallelFetch: true });
|
|
44
46
|
* ```
|
|
45
47
|
*/
|
|
46
48
|
declare function configurePricing(newConfig: PriceConfig): void;
|
|
47
49
|
/**
|
|
48
|
-
* Get SOL price with multi-provider
|
|
50
|
+
* Get SOL price with multi-provider support
|
|
49
51
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* 2. Binance (backup #1)
|
|
53
|
-
* 3. CoinGecko (backup #2)
|
|
54
|
-
* 4. Kraken (backup #3)
|
|
52
|
+
* Default behavior: Parallel fetch (all providers race, fastest wins)
|
|
53
|
+
* This is faster and more reliable in serverless environments.
|
|
55
54
|
*
|
|
56
55
|
* @example
|
|
57
56
|
* ```typescript
|