@alleyboss/micropay-solana-x402-paywall 1.0.0 → 1.0.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/dist/{index-DptevtnU.d.cts → index-uxMb72hH.d.cts} +3 -0
- package/dist/{index-DptevtnU.d.ts → index-uxMb72hH.d.ts} +3 -0
- package/dist/index.cjs +183 -39
- 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 +183 -39
- package/dist/index.js.map +1 -1
- package/dist/session/index.cjs +51 -11
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +1 -1
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js +51 -11
- package/dist/session/index.js.map +1 -1
- package/dist/solana/index.cjs +42 -15
- package/dist/solana/index.cjs.map +1 -1
- package/dist/solana/index.d.cts +1 -0
- package/dist/solana/index.d.ts +1 -0
- package/dist/solana/index.js +42 -15
- package/dist/solana/index.js.map +1 -1
- package/dist/x402/index.cjs +117 -17
- package/dist/x402/index.cjs.map +1 -1
- package/dist/x402/index.d.cts +4 -0
- package/dist/x402/index.d.ts +4 -0
- package/dist/x402/index.js +117 -17
- package/dist/x402/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42,6 +42,16 @@ function isMainnet(network) {
|
|
|
42
42
|
function toX402Network(network) {
|
|
43
43
|
return network === "mainnet-beta" ? "solana-mainnet" : "solana-devnet";
|
|
44
44
|
}
|
|
45
|
+
var SIGNATURE_REGEX = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;
|
|
46
|
+
var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
47
|
+
function isValidSignature(signature) {
|
|
48
|
+
if (!signature || typeof signature !== "string") return false;
|
|
49
|
+
return SIGNATURE_REGEX.test(signature);
|
|
50
|
+
}
|
|
51
|
+
function isValidWalletAddress(address) {
|
|
52
|
+
if (!address || typeof address !== "string") return false;
|
|
53
|
+
return WALLET_REGEX.test(address);
|
|
54
|
+
}
|
|
45
55
|
function parseSOLTransfer(transaction, expectedRecipient) {
|
|
46
56
|
const instructions = transaction.transaction.message.instructions;
|
|
47
57
|
for (const ix of instructions) {
|
|
@@ -82,6 +92,16 @@ async function verifyPayment(params) {
|
|
|
82
92
|
maxAgeSeconds = 300,
|
|
83
93
|
clientConfig
|
|
84
94
|
} = params;
|
|
95
|
+
if (!isValidSignature(signature)) {
|
|
96
|
+
return { valid: false, confirmed: false, signature, error: "Invalid signature format" };
|
|
97
|
+
}
|
|
98
|
+
if (!isValidWalletAddress(expectedRecipient)) {
|
|
99
|
+
return { valid: false, confirmed: false, signature, error: "Invalid recipient address" };
|
|
100
|
+
}
|
|
101
|
+
if (expectedAmount <= 0n) {
|
|
102
|
+
return { valid: false, confirmed: false, signature, error: "Invalid expected amount" };
|
|
103
|
+
}
|
|
104
|
+
const effectiveMaxAge = Math.min(Math.max(maxAgeSeconds, 60), 3600);
|
|
85
105
|
const connection = getConnection(clientConfig);
|
|
86
106
|
try {
|
|
87
107
|
const transaction = await connection.getParsedTransaction(signature, {
|
|
@@ -96,14 +116,17 @@ async function verifyPayment(params) {
|
|
|
96
116
|
valid: false,
|
|
97
117
|
confirmed: true,
|
|
98
118
|
signature,
|
|
99
|
-
error:
|
|
119
|
+
error: "Transaction failed on-chain"
|
|
100
120
|
};
|
|
101
121
|
}
|
|
102
122
|
if (transaction.blockTime) {
|
|
103
123
|
const now = Math.floor(Date.now() / 1e3);
|
|
104
|
-
if (now - transaction.blockTime >
|
|
124
|
+
if (now - transaction.blockTime > effectiveMaxAge) {
|
|
105
125
|
return { valid: false, confirmed: true, signature, error: "Transaction too old" };
|
|
106
126
|
}
|
|
127
|
+
if (transaction.blockTime > now + 60) {
|
|
128
|
+
return { valid: false, confirmed: true, signature, error: "Invalid transaction time" };
|
|
129
|
+
}
|
|
107
130
|
}
|
|
108
131
|
const transferDetails = parseSOLTransfer(transaction, expectedRecipient);
|
|
109
132
|
if (!transferDetails) {
|
|
@@ -122,7 +145,7 @@ async function verifyPayment(params) {
|
|
|
122
145
|
from: transferDetails.from,
|
|
123
146
|
to: transferDetails.to,
|
|
124
147
|
amount: transferDetails.amount,
|
|
125
|
-
error:
|
|
148
|
+
error: "Insufficient payment amount"
|
|
126
149
|
};
|
|
127
150
|
}
|
|
128
151
|
return {
|
|
@@ -140,33 +163,34 @@ async function verifyPayment(params) {
|
|
|
140
163
|
valid: false,
|
|
141
164
|
confirmed: false,
|
|
142
165
|
signature,
|
|
143
|
-
error:
|
|
166
|
+
error: "Verification failed"
|
|
144
167
|
};
|
|
145
168
|
}
|
|
146
169
|
}
|
|
147
170
|
async function waitForConfirmation(signature, clientConfig) {
|
|
171
|
+
if (!isValidSignature(signature)) {
|
|
172
|
+
return { confirmed: false, error: "Invalid signature format" };
|
|
173
|
+
}
|
|
148
174
|
const connection = getConnection(clientConfig);
|
|
149
175
|
try {
|
|
150
176
|
const confirmation = await connection.confirmTransaction(signature, "confirmed");
|
|
151
177
|
if (confirmation.value.err) {
|
|
152
|
-
return {
|
|
153
|
-
confirmed: false,
|
|
154
|
-
error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`
|
|
155
|
-
};
|
|
178
|
+
return { confirmed: false, error: "Transaction failed" };
|
|
156
179
|
}
|
|
157
180
|
return { confirmed: true, slot: confirmation.context?.slot };
|
|
158
|
-
} catch
|
|
159
|
-
return {
|
|
160
|
-
confirmed: false,
|
|
161
|
-
error: error instanceof Error ? error.message : "Confirmation timeout"
|
|
162
|
-
};
|
|
181
|
+
} catch {
|
|
182
|
+
return { confirmed: false, error: "Confirmation timeout" };
|
|
163
183
|
}
|
|
164
184
|
}
|
|
165
185
|
async function getWalletTransactions(walletAddress, clientConfig, limit = 20) {
|
|
186
|
+
if (!isValidWalletAddress(walletAddress)) {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const safeLimit = Math.min(Math.max(limit, 1), 100);
|
|
166
190
|
const connection = getConnection(clientConfig);
|
|
167
|
-
const pubkey = new PublicKey(walletAddress);
|
|
168
191
|
try {
|
|
169
|
-
const
|
|
192
|
+
const pubkey = new PublicKey(walletAddress);
|
|
193
|
+
const signatures = await connection.getSignaturesForAddress(pubkey, { limit: safeLimit });
|
|
170
194
|
return signatures.map((sig) => ({
|
|
171
195
|
signature: sig.signature,
|
|
172
196
|
blockTime: sig.blockTime ?? void 0,
|
|
@@ -180,15 +204,43 @@ function lamportsToSol(lamports) {
|
|
|
180
204
|
return Number(lamports) / LAMPORTS_PER_SOL;
|
|
181
205
|
}
|
|
182
206
|
function solToLamports(sol) {
|
|
207
|
+
if (!Number.isFinite(sol) || sol < 0) {
|
|
208
|
+
throw new Error("Invalid SOL amount");
|
|
209
|
+
}
|
|
183
210
|
return BigInt(Math.floor(sol * LAMPORTS_PER_SOL));
|
|
184
211
|
}
|
|
212
|
+
var MAX_ARTICLES_PER_SESSION = 100;
|
|
213
|
+
var MIN_SECRET_LENGTH = 32;
|
|
185
214
|
function getSecretKey(secret) {
|
|
186
|
-
if (secret
|
|
187
|
-
throw new Error("Session secret
|
|
215
|
+
if (!secret || typeof secret !== "string") {
|
|
216
|
+
throw new Error("Session secret is required");
|
|
217
|
+
}
|
|
218
|
+
if (secret.length < MIN_SECRET_LENGTH) {
|
|
219
|
+
throw new Error(`Session secret must be at least ${MIN_SECRET_LENGTH} characters`);
|
|
188
220
|
}
|
|
189
221
|
return new TextEncoder().encode(secret);
|
|
190
222
|
}
|
|
223
|
+
function validateWalletAddress(address) {
|
|
224
|
+
if (!address || typeof address !== "string") return false;
|
|
225
|
+
const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
226
|
+
return base58Regex.test(address);
|
|
227
|
+
}
|
|
228
|
+
function validateArticleId(articleId) {
|
|
229
|
+
if (!articleId || typeof articleId !== "string") return false;
|
|
230
|
+
if (articleId.length > 128) return false;
|
|
231
|
+
const safeIdRegex = /^[a-zA-Z0-9_-]+$/;
|
|
232
|
+
return safeIdRegex.test(articleId);
|
|
233
|
+
}
|
|
191
234
|
async function createSession(walletAddress, articleId, config, siteWide = false) {
|
|
235
|
+
if (!validateWalletAddress(walletAddress)) {
|
|
236
|
+
throw new Error("Invalid wallet address format");
|
|
237
|
+
}
|
|
238
|
+
if (!validateArticleId(articleId)) {
|
|
239
|
+
throw new Error("Invalid article ID format");
|
|
240
|
+
}
|
|
241
|
+
if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 720) {
|
|
242
|
+
throw new Error("Session duration must be between 1 and 720 hours");
|
|
243
|
+
}
|
|
192
244
|
const sessionId = v4();
|
|
193
245
|
const now = Math.floor(Date.now() / 1e3);
|
|
194
246
|
const expiresAt = now + config.durationHours * 3600;
|
|
@@ -196,7 +248,7 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
|
|
|
196
248
|
id: sessionId,
|
|
197
249
|
walletAddress,
|
|
198
250
|
unlockedArticles: [articleId],
|
|
199
|
-
siteWideUnlock: siteWide,
|
|
251
|
+
siteWideUnlock: Boolean(siteWide),
|
|
200
252
|
createdAt: now,
|
|
201
253
|
expiresAt
|
|
202
254
|
};
|
|
@@ -204,7 +256,7 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
|
|
|
204
256
|
sub: walletAddress,
|
|
205
257
|
sid: sessionId,
|
|
206
258
|
articles: session.unlockedArticles,
|
|
207
|
-
siteWide,
|
|
259
|
+
siteWide: session.siteWideUnlock,
|
|
208
260
|
iat: now,
|
|
209
261
|
exp: expiresAt
|
|
210
262
|
};
|
|
@@ -212,30 +264,39 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
|
|
|
212
264
|
return { token, session };
|
|
213
265
|
}
|
|
214
266
|
async function validateSession(token, secret) {
|
|
267
|
+
if (!token || typeof token !== "string") {
|
|
268
|
+
return { valid: false, reason: "Invalid token format" };
|
|
269
|
+
}
|
|
215
270
|
try {
|
|
216
271
|
const { payload } = await jwtVerify(token, getSecretKey(secret));
|
|
217
272
|
const sessionPayload = payload;
|
|
273
|
+
if (!sessionPayload.sub || !sessionPayload.sid || !sessionPayload.exp) {
|
|
274
|
+
return { valid: false, reason: "Malformed session payload" };
|
|
275
|
+
}
|
|
218
276
|
const now = Math.floor(Date.now() / 1e3);
|
|
219
277
|
if (sessionPayload.exp < now) {
|
|
220
278
|
return { valid: false, reason: "Session expired" };
|
|
221
279
|
}
|
|
280
|
+
if (!validateWalletAddress(sessionPayload.sub)) {
|
|
281
|
+
return { valid: false, reason: "Invalid session data" };
|
|
282
|
+
}
|
|
222
283
|
const session = {
|
|
223
284
|
id: sessionPayload.sid,
|
|
224
285
|
walletAddress: sessionPayload.sub,
|
|
225
|
-
unlockedArticles: sessionPayload.articles,
|
|
226
|
-
siteWideUnlock: sessionPayload.siteWide,
|
|
227
|
-
createdAt: sessionPayload.iat,
|
|
286
|
+
unlockedArticles: Array.isArray(sessionPayload.articles) ? sessionPayload.articles : [],
|
|
287
|
+
siteWideUnlock: Boolean(sessionPayload.siteWide),
|
|
288
|
+
createdAt: sessionPayload.iat ?? 0,
|
|
228
289
|
expiresAt: sessionPayload.exp
|
|
229
290
|
};
|
|
230
291
|
return { valid: true, session };
|
|
231
292
|
} catch (error) {
|
|
232
|
-
return {
|
|
233
|
-
valid: false,
|
|
234
|
-
reason: error instanceof Error ? error.message : "Invalid session"
|
|
235
|
-
};
|
|
293
|
+
return { valid: false, reason: "Invalid session" };
|
|
236
294
|
}
|
|
237
295
|
}
|
|
238
296
|
async function addArticleToSession(token, articleId, secret) {
|
|
297
|
+
if (!validateArticleId(articleId)) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
239
300
|
const validation = await validateSession(token, secret);
|
|
240
301
|
if (!validation.valid || !validation.session) {
|
|
241
302
|
return null;
|
|
@@ -244,6 +305,9 @@ async function addArticleToSession(token, articleId, secret) {
|
|
|
244
305
|
if (session.unlockedArticles.includes(articleId)) {
|
|
245
306
|
return { token, session };
|
|
246
307
|
}
|
|
308
|
+
if (session.unlockedArticles.length >= MAX_ARTICLES_PER_SESSION) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
247
311
|
const updatedArticles = [...session.unlockedArticles, articleId];
|
|
248
312
|
const payload = {
|
|
249
313
|
sub: session.walletAddress,
|
|
@@ -260,6 +324,9 @@ async function addArticleToSession(token, articleId, secret) {
|
|
|
260
324
|
};
|
|
261
325
|
}
|
|
262
326
|
async function isArticleUnlocked(token, articleId, secret) {
|
|
327
|
+
if (!validateArticleId(articleId)) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
263
330
|
const validation = await validateSession(token, secret);
|
|
264
331
|
if (!validation.valid || !validation.session) {
|
|
265
332
|
return false;
|
|
@@ -271,20 +338,52 @@ async function isArticleUnlocked(token, articleId, secret) {
|
|
|
271
338
|
}
|
|
272
339
|
|
|
273
340
|
// src/x402/config.ts
|
|
341
|
+
var WALLET_REGEX2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
342
|
+
function sanitizeDisplayString(str, maxLength = 200) {
|
|
343
|
+
if (!str || typeof str !== "string") return "";
|
|
344
|
+
return str.slice(0, maxLength).replace(/[<>"'&]/g, "");
|
|
345
|
+
}
|
|
346
|
+
function isValidUrl(url) {
|
|
347
|
+
try {
|
|
348
|
+
const parsed = new URL(url);
|
|
349
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
350
|
+
} catch {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
274
354
|
function buildPaymentRequirement(params) {
|
|
355
|
+
if (!WALLET_REGEX2.test(params.creatorWallet)) {
|
|
356
|
+
throw new Error("Invalid creator wallet address");
|
|
357
|
+
}
|
|
358
|
+
if (params.priceInLamports <= 0n) {
|
|
359
|
+
throw new Error("Price must be positive");
|
|
360
|
+
}
|
|
361
|
+
if (!isValidUrl(params.resourceUrl)) {
|
|
362
|
+
throw new Error("Invalid resource URL");
|
|
363
|
+
}
|
|
364
|
+
if (params.network !== "devnet" && params.network !== "mainnet-beta") {
|
|
365
|
+
throw new Error("Invalid network");
|
|
366
|
+
}
|
|
367
|
+
const timeout = params.maxTimeoutSeconds ?? 300;
|
|
368
|
+
if (timeout < 60 || timeout > 3600) {
|
|
369
|
+
throw new Error("Timeout must be between 60 and 3600 seconds");
|
|
370
|
+
}
|
|
275
371
|
const x402Network = toX402Network(params.network);
|
|
372
|
+
const safeTitle = sanitizeDisplayString(params.articleTitle, 200);
|
|
373
|
+
const safeArticleId = sanitizeDisplayString(params.articleId, 128);
|
|
276
374
|
return {
|
|
277
375
|
scheme: "exact",
|
|
278
376
|
network: x402Network,
|
|
279
377
|
maxAmountRequired: params.priceInLamports.toString(),
|
|
280
378
|
resource: params.resourceUrl,
|
|
281
|
-
description: `Unlock: ${
|
|
379
|
+
description: `Unlock: ${safeTitle}`,
|
|
282
380
|
mimeType: "text/html",
|
|
283
381
|
payTo: params.creatorWallet,
|
|
284
|
-
maxTimeoutSeconds:
|
|
382
|
+
maxTimeoutSeconds: timeout,
|
|
285
383
|
asset: "native",
|
|
286
384
|
extra: {
|
|
287
|
-
name:
|
|
385
|
+
name: safeTitle,
|
|
386
|
+
articleId: safeArticleId
|
|
288
387
|
}
|
|
289
388
|
};
|
|
290
389
|
}
|
|
@@ -292,7 +391,18 @@ function encodePaymentRequired(requirement) {
|
|
|
292
391
|
return Buffer.from(JSON.stringify(requirement)).toString("base64");
|
|
293
392
|
}
|
|
294
393
|
function decodePaymentRequired(encoded) {
|
|
295
|
-
|
|
394
|
+
if (!encoded || typeof encoded !== "string") {
|
|
395
|
+
throw new Error("Invalid encoded requirement");
|
|
396
|
+
}
|
|
397
|
+
if (encoded.length > 1e4) {
|
|
398
|
+
throw new Error("Encoded requirement too large");
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
const decoded = Buffer.from(encoded, "base64").toString("utf-8");
|
|
402
|
+
return JSON.parse(decoded);
|
|
403
|
+
} catch {
|
|
404
|
+
throw new Error("Failed to decode payment requirement");
|
|
405
|
+
}
|
|
296
406
|
}
|
|
297
407
|
var X402_HEADERS = {
|
|
298
408
|
PAYMENT_REQUIRED: "X-Payment-Required",
|
|
@@ -320,23 +430,47 @@ function create402Headers(requirement) {
|
|
|
320
430
|
}
|
|
321
431
|
|
|
322
432
|
// src/x402/verification.ts
|
|
433
|
+
var SIGNATURE_REGEX2 = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;
|
|
323
434
|
async function verifyX402Payment(payload, requirement, clientConfig) {
|
|
324
|
-
if (!payload
|
|
325
|
-
return {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
435
|
+
if (!payload || typeof payload !== "object") {
|
|
436
|
+
return { valid: false, invalidReason: "Invalid payload" };
|
|
437
|
+
}
|
|
438
|
+
const signature = payload.payload?.signature;
|
|
439
|
+
if (!signature || typeof signature !== "string") {
|
|
440
|
+
return { valid: false, invalidReason: "Missing transaction signature" };
|
|
441
|
+
}
|
|
442
|
+
if (!SIGNATURE_REGEX2.test(signature)) {
|
|
443
|
+
return { valid: false, invalidReason: "Invalid signature format" };
|
|
444
|
+
}
|
|
445
|
+
if (payload.x402Version !== 1) {
|
|
446
|
+
return { valid: false, invalidReason: "Unsupported x402 version" };
|
|
447
|
+
}
|
|
448
|
+
if (payload.scheme !== "exact") {
|
|
449
|
+
return { valid: false, invalidReason: "Unsupported payment scheme" };
|
|
329
450
|
}
|
|
330
451
|
if (payload.network !== requirement.network) {
|
|
331
452
|
return {
|
|
332
453
|
valid: false,
|
|
333
|
-
invalidReason: `Network mismatch: expected ${requirement.network}
|
|
454
|
+
invalidReason: `Network mismatch: expected ${requirement.network}`
|
|
334
455
|
};
|
|
335
456
|
}
|
|
457
|
+
const walletRegex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
458
|
+
if (!walletRegex.test(requirement.payTo)) {
|
|
459
|
+
return { valid: false, invalidReason: "Invalid recipient configuration" };
|
|
460
|
+
}
|
|
461
|
+
let expectedAmount;
|
|
462
|
+
try {
|
|
463
|
+
expectedAmount = BigInt(requirement.maxAmountRequired);
|
|
464
|
+
if (expectedAmount <= 0n) {
|
|
465
|
+
return { valid: false, invalidReason: "Invalid payment amount" };
|
|
466
|
+
}
|
|
467
|
+
} catch {
|
|
468
|
+
return { valid: false, invalidReason: "Invalid payment amount format" };
|
|
469
|
+
}
|
|
336
470
|
const verification = await verifyPayment({
|
|
337
|
-
signature
|
|
471
|
+
signature,
|
|
338
472
|
expectedRecipient: requirement.payTo,
|
|
339
|
-
expectedAmount
|
|
473
|
+
expectedAmount,
|
|
340
474
|
maxAgeSeconds: requirement.maxTimeoutSeconds,
|
|
341
475
|
clientConfig
|
|
342
476
|
});
|
|
@@ -357,9 +491,19 @@ async function verifyX402Payment(payload, requirement, clientConfig) {
|
|
|
357
491
|
};
|
|
358
492
|
}
|
|
359
493
|
function parsePaymentHeader(header) {
|
|
494
|
+
if (!header || typeof header !== "string") {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
if (header.length > 1e4) {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
360
500
|
try {
|
|
361
501
|
const decoded = Buffer.from(header, "base64").toString("utf-8");
|
|
362
|
-
|
|
502
|
+
const parsed = JSON.parse(decoded);
|
|
503
|
+
if (!parsed || typeof parsed !== "object") {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
return parsed;
|
|
363
507
|
} catch {
|
|
364
508
|
return null;
|
|
365
509
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/solana/client.ts","../src/solana/verification.ts","../src/session/core.ts","../src/x402/config.ts","../src/x402/verification.ts"],"names":["uuidv4"],"mappings":"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","file":"index.js","sourcesContent":["// Solana RPC client with multi-provider support\nimport { Connection, clusterApiUrl, type Cluster } from '@solana/web3.js';\nimport type { SolanaNetwork } from '../types';\n\n/** Configuration for Solana client */\nexport interface SolanaClientConfig {\n /** Network to connect to */\n network: SolanaNetwork;\n /** Custom RPC URL (optional) */\n rpcUrl?: string;\n /** Tatum.io API key for RPC (optional) */\n tatumApiKey?: string;\n}\n\n// Singleton state\nlet cachedConnection: Connection | null = null;\nlet cachedNetwork: SolanaNetwork | null = null;\n\n/**\n * Build RPC URL based on configuration priority:\n * 1. Custom RPC URL\n * 2. Tatum.io with API key\n * 3. Public RPC (rate limited)\n */\nfunction buildRpcUrl(config: SolanaClientConfig): string {\n const { network, rpcUrl, tatumApiKey } = config;\n\n if (rpcUrl) {\n // If Tatum URL without key, append key if available\n if (rpcUrl.includes('tatum.io') && tatumApiKey && !rpcUrl.includes(tatumApiKey)) {\n return rpcUrl.endsWith('/') ? `${rpcUrl}${tatumApiKey}` : `${rpcUrl}/${tatumApiKey}`;\n }\n return rpcUrl;\n }\n\n if (tatumApiKey) {\n const baseUrl = network === 'mainnet-beta'\n ? 'https://solana-mainnet.gateway.tatum.io'\n : 'https://solana-devnet.gateway.tatum.io';\n return `${baseUrl}/${tatumApiKey}`;\n }\n\n // Fallback to public RPC\n return clusterApiUrl(network as Cluster);\n}\n\n/**\n * Get or create a Solana connection\n * Uses singleton pattern with network-aware caching\n */\nexport function getConnection(config: SolanaClientConfig): Connection {\n const { network } = config;\n\n // Return cached if same network\n if (cachedConnection && cachedNetwork === network) {\n return cachedConnection;\n }\n\n const rpcUrl = buildRpcUrl(config);\n\n cachedConnection = new Connection(rpcUrl, {\n commitment: 'confirmed',\n confirmTransactionInitialTimeout: 60000,\n });\n cachedNetwork = network;\n\n return cachedConnection;\n}\n\n/**\n * Reset the cached connection\n * Useful for testing or network switching\n */\nexport function resetConnection(): void {\n cachedConnection = null;\n cachedNetwork = null;\n}\n\n/**\n * Check if network is mainnet\n */\nexport function isMainnet(network: SolanaNetwork): boolean {\n return network === 'mainnet-beta';\n}\n\n/**\n * Convert Solana network to x402 network identifier\n */\nexport function toX402Network(network: SolanaNetwork): 'solana-devnet' | 'solana-mainnet' {\n return network === 'mainnet-beta' ? 'solana-mainnet' : 'solana-devnet';\n}\n","// Transaction verification for SOL payments\nimport { PublicKey, LAMPORTS_PER_SOL, type ParsedTransactionWithMeta } from '@solana/web3.js';\nimport { getConnection, type SolanaClientConfig } from './client';\n\n/** Result of transaction verification */\nexport interface TransactionVerificationResult {\n /** Whether the transaction is valid for the payment */\n valid: boolean;\n /** Whether the transaction is confirmed on-chain */\n confirmed: boolean;\n /** Transaction signature */\n signature: string;\n /** Sender wallet address */\n from?: string;\n /** Recipient wallet address */\n to?: string;\n /** Amount transferred in lamports */\n amount?: bigint;\n /** Block time (Unix timestamp) */\n blockTime?: number;\n /** Slot number */\n slot?: number;\n /** Error message if verification failed */\n error?: string;\n}\n\n/** Parameters for verifying a payment */\nexport interface VerifyPaymentParams {\n /** Transaction signature to verify */\n signature: string;\n /** Expected recipient wallet address */\n expectedRecipient: string;\n /** Expected amount in lamports */\n expectedAmount: bigint;\n /** Maximum age of transaction in seconds (default: 300) */\n maxAgeSeconds?: number;\n /** Solana client configuration */\n clientConfig: SolanaClientConfig;\n}\n\n/**\n * Parse SOL transfer details from a transaction\n */\nfunction parseSOLTransfer(\n transaction: ParsedTransactionWithMeta,\n expectedRecipient: string\n): { from: string; to: string; amount: bigint } | null {\n const instructions = transaction.transaction.message.instructions;\n\n // Check main instructions\n for (const ix of instructions) {\n if ('parsed' in ix && ix.program === 'system') {\n const parsed = ix.parsed as {\n type: string;\n info: { source: string; destination: string; lamports: number }\n };\n\n if (parsed.type === 'transfer' && parsed.info.destination === expectedRecipient) {\n return {\n from: parsed.info.source,\n to: parsed.info.destination,\n amount: BigInt(parsed.info.lamports),\n };\n }\n }\n }\n\n // Check inner instructions\n if (transaction.meta?.innerInstructions) {\n for (const inner of transaction.meta.innerInstructions) {\n for (const ix of inner.instructions) {\n if ('parsed' in ix && ix.program === 'system') {\n const parsed = ix.parsed as {\n type: string;\n info: { source: string; destination: string; lamports: number }\n };\n\n if (parsed.type === 'transfer' && parsed.info.destination === expectedRecipient) {\n return {\n from: parsed.info.source,\n to: parsed.info.destination,\n amount: BigInt(parsed.info.lamports),\n };\n }\n }\n }\n }\n }\n\n return null;\n}\n\n/**\n * Verify a SOL transfer transaction\n */\nexport async function verifyPayment(\n params: VerifyPaymentParams\n): Promise<TransactionVerificationResult> {\n const {\n signature,\n expectedRecipient,\n expectedAmount,\n maxAgeSeconds = 300,\n clientConfig\n } = params;\n\n const connection = getConnection(clientConfig);\n\n try {\n const transaction = await connection.getParsedTransaction(signature, {\n commitment: 'confirmed',\n maxSupportedTransactionVersion: 0,\n });\n\n if (!transaction) {\n return { valid: false, confirmed: false, signature, error: 'Transaction not found' };\n }\n\n // Check for transaction errors\n if (transaction.meta?.err) {\n return {\n valid: false,\n confirmed: true,\n signature,\n error: `Transaction failed: ${JSON.stringify(transaction.meta.err)}`,\n };\n }\n\n // Validate transaction age\n if (transaction.blockTime) {\n const now = Math.floor(Date.now() / 1000);\n if (now - transaction.blockTime > maxAgeSeconds) {\n return { valid: false, confirmed: true, signature, error: 'Transaction too old' };\n }\n }\n\n // Parse transfer details\n const transferDetails = parseSOLTransfer(transaction, expectedRecipient);\n\n if (!transferDetails) {\n return {\n valid: false,\n confirmed: true,\n signature,\n error: 'No valid SOL transfer to recipient found',\n };\n }\n\n // Validate amount\n if (transferDetails.amount < expectedAmount) {\n return {\n valid: false,\n confirmed: true,\n signature,\n from: transferDetails.from,\n to: transferDetails.to,\n amount: transferDetails.amount,\n error: `Insufficient amount: expected ${expectedAmount}, got ${transferDetails.amount}`,\n };\n }\n\n return {\n valid: true,\n confirmed: true,\n signature,\n from: transferDetails.from,\n to: transferDetails.to,\n amount: transferDetails.amount,\n blockTime: transaction.blockTime ?? undefined,\n slot: transaction.slot,\n };\n } catch (error) {\n return {\n valid: false,\n confirmed: false,\n signature,\n error: error instanceof Error ? error.message : 'Unknown verification error',\n };\n }\n}\n\n/**\n * Wait for transaction confirmation\n */\nexport async function waitForConfirmation(\n signature: string,\n clientConfig: SolanaClientConfig\n): Promise<{ confirmed: boolean; slot?: number; error?: string }> {\n const connection = getConnection(clientConfig);\n\n try {\n const confirmation = await connection.confirmTransaction(signature, 'confirmed');\n\n if (confirmation.value.err) {\n return {\n confirmed: false,\n error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`,\n };\n }\n\n return { confirmed: true, slot: confirmation.context?.slot };\n } catch (error) {\n return {\n confirmed: false,\n error: error instanceof Error ? error.message : 'Confirmation timeout',\n };\n }\n}\n\n/**\n * Get recent transactions for a wallet\n */\nexport async function getWalletTransactions(\n walletAddress: string,\n clientConfig: SolanaClientConfig,\n limit: number = 20\n): Promise<Array<{ signature: string; blockTime?: number; slot: number }>> {\n const connection = getConnection(clientConfig);\n const pubkey = new PublicKey(walletAddress);\n\n try {\n const signatures = await connection.getSignaturesForAddress(pubkey, { limit });\n return signatures.map((sig) => ({\n signature: sig.signature,\n blockTime: sig.blockTime ?? undefined,\n slot: sig.slot,\n }));\n } catch {\n return [];\n }\n}\n\n/**\n * Convert lamports to SOL\n */\nexport function lamportsToSol(lamports: bigint | number): number {\n return Number(lamports) / LAMPORTS_PER_SOL;\n}\n\n/**\n * Convert SOL to lamports\n */\nexport function solToLamports(sol: number): bigint {\n return BigInt(Math.floor(sol * LAMPORTS_PER_SOL));\n}\n","// Session management with JWT (framework-agnostic core)\nimport { SignJWT, jwtVerify } from 'jose';\nimport { v4 as uuidv4 } from 'uuid';\nimport type { SessionData, SessionConfig, SessionValidation, SessionJWTPayload } from '../types';\n\n/**\n * Get the secret key for JWT signing\n */\nfunction getSecretKey(secret: string): Uint8Array {\n if (secret.length < 32) {\n throw new Error('Session secret must be at least 32 characters');\n }\n return new TextEncoder().encode(secret);\n}\n\n/**\n * Create a new session after successful payment\n */\nexport async function createSession(\n walletAddress: string,\n articleId: string,\n config: SessionConfig,\n siteWide: boolean = false\n): Promise<{ token: string; session: SessionData }> {\n const sessionId = uuidv4();\n const now = Math.floor(Date.now() / 1000);\n const expiresAt = now + (config.durationHours * 3600);\n\n const session: SessionData = {\n id: sessionId,\n walletAddress,\n unlockedArticles: [articleId],\n siteWideUnlock: siteWide,\n createdAt: now,\n expiresAt,\n };\n\n const payload: SessionJWTPayload = {\n sub: walletAddress,\n sid: sessionId,\n articles: session.unlockedArticles,\n siteWide,\n iat: now,\n exp: expiresAt,\n };\n\n const token = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(`${config.durationHours}h`)\n .sign(getSecretKey(config.secret));\n\n return { token, session };\n}\n\n/**\n * Validate an existing session token\n */\nexport async function validateSession(\n token: string,\n secret: string\n): Promise<SessionValidation> {\n try {\n const { payload } = await jwtVerify(token, getSecretKey(secret));\n const sessionPayload = payload as unknown as SessionJWTPayload;\n\n const now = Math.floor(Date.now() / 1000);\n if (sessionPayload.exp < now) {\n return { valid: false, reason: 'Session expired' };\n }\n\n const session: SessionData = {\n id: sessionPayload.sid,\n walletAddress: sessionPayload.sub,\n unlockedArticles: sessionPayload.articles,\n siteWideUnlock: sessionPayload.siteWide,\n createdAt: sessionPayload.iat,\n expiresAt: sessionPayload.exp,\n };\n\n return { valid: true, session };\n } catch (error) {\n return {\n valid: false,\n reason: error instanceof Error ? error.message : 'Invalid session',\n };\n }\n}\n\n/**\n * Add an article to an existing session\n */\nexport async function addArticleToSession(\n token: string,\n articleId: string,\n secret: string\n): Promise<{ token: string; session: SessionData } | null> {\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return null;\n }\n\n const session = validation.session;\n\n // Already unlocked\n if (session.unlockedArticles.includes(articleId)) {\n return { token, session };\n }\n\n const updatedArticles = [...session.unlockedArticles, articleId];\n\n const payload: SessionJWTPayload = {\n sub: session.walletAddress,\n sid: session.id,\n articles: updatedArticles,\n siteWide: session.siteWideUnlock,\n iat: session.createdAt,\n exp: session.expiresAt,\n };\n\n const newToken = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(getSecretKey(secret));\n\n return {\n token: newToken,\n session: { ...session, unlockedArticles: updatedArticles },\n };\n}\n\n/**\n * Check if an article is unlocked for a session\n */\nexport async function isArticleUnlocked(\n token: string,\n articleId: string,\n secret: string\n): Promise<boolean> {\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return false;\n }\n\n if (validation.session.siteWideUnlock) {\n return true;\n }\n\n return validation.session.unlockedArticles.includes(articleId);\n}\n","// x402 payment configuration and helpers\nimport type { PaymentRequirement, X402Network, SolanaNetwork } from '../types';\nimport { toX402Network } from '../solana';\n\n/** Parameters for building a payment requirement */\nexport interface BuildPaymentParams {\n /** Unique article identifier */\n articleId: string;\n /** Article title for display */\n articleTitle: string;\n /** Price in lamports */\n priceInLamports: bigint;\n /** Creator wallet address */\n creatorWallet: string;\n /** Full URL of the protected resource */\n resourceUrl: string;\n /** Solana network */\n network: SolanaNetwork;\n /** Max time to complete payment (default: 300s) */\n maxTimeoutSeconds?: number;\n}\n\n/**\n * Build a payment requirement for an article\n */\nexport function buildPaymentRequirement(params: BuildPaymentParams): PaymentRequirement {\n const x402Network = toX402Network(params.network);\n\n return {\n scheme: 'exact',\n network: x402Network,\n maxAmountRequired: params.priceInLamports.toString(),\n resource: params.resourceUrl,\n description: `Unlock: ${params.articleTitle}`,\n mimeType: 'text/html',\n payTo: params.creatorWallet,\n maxTimeoutSeconds: params.maxTimeoutSeconds ?? 300,\n asset: 'native',\n extra: {\n name: params.articleTitle,\n },\n };\n}\n\n/**\n * Encode payment requirement for x402 header\n */\nexport function encodePaymentRequired(requirement: PaymentRequirement): string {\n return Buffer.from(JSON.stringify(requirement)).toString('base64');\n}\n\n/**\n * Decode payment requirement from x402 header\n */\nexport function decodePaymentRequired(encoded: string): PaymentRequirement {\n return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'));\n}\n\n/**\n * x402 response header names\n */\nexport const X402_HEADERS = {\n PAYMENT_REQUIRED: 'X-Payment-Required',\n PAYMENT: 'X-Payment',\n PAYMENT_RESPONSE: 'X-Payment-Response',\n} as const;\n\n/**\n * Create 402 Payment Required response body\n */\nexport function create402ResponseBody(requirement: PaymentRequirement): {\n error: string;\n message: string;\n price: { amount: string; asset: string; network: X402Network };\n} {\n return {\n error: 'Payment Required',\n message: requirement.description,\n price: {\n amount: requirement.maxAmountRequired,\n asset: requirement.asset,\n network: requirement.network,\n },\n };\n}\n\n/**\n * Create headers for 402 response\n */\nexport function create402Headers(requirement: PaymentRequirement): Record<string, string> {\n const encoded = encodePaymentRequired(requirement);\n return {\n 'Content-Type': 'application/json',\n [X402_HEADERS.PAYMENT_REQUIRED]: encoded,\n 'Access-Control-Expose-Headers': X402_HEADERS.PAYMENT_REQUIRED,\n };\n}\n","// x402 payment verification service\nimport { verifyPayment, type SolanaClientConfig } from '../solana';\nimport type { PaymentPayload, PaymentRequirement, VerificationResponse } from '../types';\n\n/**\n * Verify a payment payload against requirements\n */\nexport async function verifyX402Payment(\n payload: PaymentPayload,\n requirement: PaymentRequirement,\n clientConfig: SolanaClientConfig\n): Promise<VerificationResponse> {\n // Validate payload structure\n if (!payload.payload?.signature) {\n return {\n valid: false,\n invalidReason: 'Missing transaction signature',\n };\n }\n\n // Validate network matches\n if (payload.network !== requirement.network) {\n return {\n valid: false,\n invalidReason: `Network mismatch: expected ${requirement.network}, got ${payload.network}`,\n };\n }\n\n // Verify on-chain\n const verification = await verifyPayment({\n signature: payload.payload.signature,\n expectedRecipient: requirement.payTo,\n expectedAmount: BigInt(requirement.maxAmountRequired),\n maxAgeSeconds: requirement.maxTimeoutSeconds,\n clientConfig,\n });\n\n if (!verification.valid) {\n return {\n valid: false,\n invalidReason: verification.error || 'Transaction verification failed',\n };\n }\n\n return {\n valid: true,\n settled: verification.confirmed,\n transaction: {\n signature: verification.signature,\n blockTime: verification.blockTime,\n slot: verification.slot,\n },\n };\n}\n\n/**\n * Parse payment payload from x402 header\n */\nexport function parsePaymentHeader(header: string): PaymentPayload | null {\n try {\n const decoded = Buffer.from(header, 'base64').toString('utf-8');\n return JSON.parse(decoded) as PaymentPayload;\n } catch {\n return null;\n }\n}\n\n/**\n * Encode payment response for header\n */\nexport function encodePaymentResponse(response: VerificationResponse): string {\n return Buffer.from(JSON.stringify(response)).toString('base64');\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/solana/client.ts","../src/solana/verification.ts","../src/session/core.ts","../src/x402/config.ts","../src/x402/verification.ts"],"names":["uuidv4","WALLET_REGEX","SIGNATURE_REGEX"],"mappings":"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","file":"index.js","sourcesContent":["// Solana RPC client with multi-provider support\nimport { Connection, clusterApiUrl, type Cluster } from '@solana/web3.js';\nimport type { SolanaNetwork } from '../types';\n\n/** Configuration for Solana client */\nexport interface SolanaClientConfig {\n /** Network to connect to */\n network: SolanaNetwork;\n /** Custom RPC URL (optional) */\n rpcUrl?: string;\n /** Tatum.io API key for RPC (optional) */\n tatumApiKey?: string;\n}\n\n// Singleton state\nlet cachedConnection: Connection | null = null;\nlet cachedNetwork: SolanaNetwork | null = null;\n\n/**\n * Build RPC URL based on configuration priority:\n * 1. Custom RPC URL\n * 2. Tatum.io with API key\n * 3. Public RPC (rate limited)\n */\nfunction buildRpcUrl(config: SolanaClientConfig): string {\n const { network, rpcUrl, tatumApiKey } = config;\n\n if (rpcUrl) {\n // If Tatum URL without key, append key if available\n if (rpcUrl.includes('tatum.io') && tatumApiKey && !rpcUrl.includes(tatumApiKey)) {\n return rpcUrl.endsWith('/') ? `${rpcUrl}${tatumApiKey}` : `${rpcUrl}/${tatumApiKey}`;\n }\n return rpcUrl;\n }\n\n if (tatumApiKey) {\n const baseUrl = network === 'mainnet-beta'\n ? 'https://solana-mainnet.gateway.tatum.io'\n : 'https://solana-devnet.gateway.tatum.io';\n return `${baseUrl}/${tatumApiKey}`;\n }\n\n // Fallback to public RPC\n return clusterApiUrl(network as Cluster);\n}\n\n/**\n * Get or create a Solana connection\n * Uses singleton pattern with network-aware caching\n */\nexport function getConnection(config: SolanaClientConfig): Connection {\n const { network } = config;\n\n // Return cached if same network\n if (cachedConnection && cachedNetwork === network) {\n return cachedConnection;\n }\n\n const rpcUrl = buildRpcUrl(config);\n\n cachedConnection = new Connection(rpcUrl, {\n commitment: 'confirmed',\n confirmTransactionInitialTimeout: 60000,\n });\n cachedNetwork = network;\n\n return cachedConnection;\n}\n\n/**\n * Reset the cached connection\n * Useful for testing or network switching\n */\nexport function resetConnection(): void {\n cachedConnection = null;\n cachedNetwork = null;\n}\n\n/**\n * Check if network is mainnet\n */\nexport function isMainnet(network: SolanaNetwork): boolean {\n return network === 'mainnet-beta';\n}\n\n/**\n * Convert Solana network to x402 network identifier\n */\nexport function toX402Network(network: SolanaNetwork): 'solana-devnet' | 'solana-mainnet' {\n return network === 'mainnet-beta' ? 'solana-mainnet' : 'solana-devnet';\n}\n","// Transaction verification for SOL payments\n// SECURITY: On-chain verification, signature validation, replay protection\nimport { PublicKey, LAMPORTS_PER_SOL, type ParsedTransactionWithMeta } from '@solana/web3.js';\nimport { getConnection, type SolanaClientConfig } from './client';\n\n/** Result of transaction verification */\nexport interface TransactionVerificationResult {\n /** Whether the transaction is valid for the payment */\n valid: boolean;\n /** Whether the transaction is confirmed on-chain */\n confirmed: boolean;\n /** Transaction signature */\n signature: string;\n /** Sender wallet address */\n from?: string;\n /** Recipient wallet address */\n to?: string;\n /** Amount transferred in lamports */\n amount?: bigint;\n /** Block time (Unix timestamp) */\n blockTime?: number;\n /** Slot number */\n slot?: number;\n /** Error message if verification failed */\n error?: string;\n}\n\n/** Parameters for verifying a payment */\nexport interface VerifyPaymentParams {\n /** Transaction signature to verify */\n signature: string;\n /** Expected recipient wallet address */\n expectedRecipient: string;\n /** Expected amount in lamports */\n expectedAmount: bigint;\n /** Maximum age of transaction in seconds (default: 300) */\n maxAgeSeconds?: number;\n /** Solana client configuration */\n clientConfig: SolanaClientConfig;\n}\n\n// Signature validation regex (base58, 87-88 chars)\nconst SIGNATURE_REGEX = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;\n\n// Wallet address validation regex\nconst WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\n\n/**\n * Validate transaction signature format\n * SECURITY: Prevents malformed signatures from reaching RPC\n */\nfunction isValidSignature(signature: string): boolean {\n if (!signature || typeof signature !== 'string') return false;\n return SIGNATURE_REGEX.test(signature);\n}\n\n/**\n * Validate wallet address format\n * SECURITY: Ensures valid base58 address\n */\nfunction isValidWalletAddress(address: string): boolean {\n if (!address || typeof address !== 'string') return false;\n return WALLET_REGEX.test(address);\n}\n\n/**\n * Parse SOL transfer details from a transaction\n */\nfunction parseSOLTransfer(\n transaction: ParsedTransactionWithMeta,\n expectedRecipient: string\n): { from: string; to: string; amount: bigint } | null {\n const instructions = transaction.transaction.message.instructions;\n\n // Check main instructions\n for (const ix of instructions) {\n if ('parsed' in ix && ix.program === 'system') {\n const parsed = ix.parsed as {\n type: string;\n info: { source: string; destination: string; lamports: number }\n };\n\n if (parsed.type === 'transfer' && parsed.info.destination === expectedRecipient) {\n return {\n from: parsed.info.source,\n to: parsed.info.destination,\n amount: BigInt(parsed.info.lamports),\n };\n }\n }\n }\n\n // Check inner instructions\n if (transaction.meta?.innerInstructions) {\n for (const inner of transaction.meta.innerInstructions) {\n for (const ix of inner.instructions) {\n if ('parsed' in ix && ix.program === 'system') {\n const parsed = ix.parsed as {\n type: string;\n info: { source: string; destination: string; lamports: number }\n };\n\n if (parsed.type === 'transfer' && parsed.info.destination === expectedRecipient) {\n return {\n from: parsed.info.source,\n to: parsed.info.destination,\n amount: BigInt(parsed.info.lamports),\n };\n }\n }\n }\n }\n }\n\n return null;\n}\n\n/**\n * Verify a SOL transfer transaction\n * SECURITY: Full on-chain verification with amount/recipient/age checks\n */\nexport async function verifyPayment(\n params: VerifyPaymentParams\n): Promise<TransactionVerificationResult> {\n const {\n signature,\n expectedRecipient,\n expectedAmount,\n maxAgeSeconds = 300,\n clientConfig\n } = params;\n\n // SECURITY: Validate signature format before RPC call\n if (!isValidSignature(signature)) {\n return { valid: false, confirmed: false, signature, error: 'Invalid signature format' };\n }\n\n // SECURITY: Validate recipient address format\n if (!isValidWalletAddress(expectedRecipient)) {\n return { valid: false, confirmed: false, signature, error: 'Invalid recipient address' };\n }\n\n // SECURITY: Validate expected amount is positive\n if (expectedAmount <= 0n) {\n return { valid: false, confirmed: false, signature, error: 'Invalid expected amount' };\n }\n\n // SECURITY: Enforce reasonable max age (prevent replay with very old transactions)\n const effectiveMaxAge = Math.min(Math.max(maxAgeSeconds, 60), 3600); // 1 min to 1 hour\n\n const connection = getConnection(clientConfig);\n\n try {\n const transaction = await connection.getParsedTransaction(signature, {\n commitment: 'confirmed',\n maxSupportedTransactionVersion: 0,\n });\n\n if (!transaction) {\n return { valid: false, confirmed: false, signature, error: 'Transaction not found' };\n }\n\n // Check for transaction errors\n if (transaction.meta?.err) {\n return {\n valid: false,\n confirmed: true,\n signature,\n error: 'Transaction failed on-chain',\n };\n }\n\n // SECURITY: Validate transaction age (replay protection)\n if (transaction.blockTime) {\n const now = Math.floor(Date.now() / 1000);\n if (now - transaction.blockTime > effectiveMaxAge) {\n return { valid: false, confirmed: true, signature, error: 'Transaction too old' };\n }\n // Also reject future-dated transactions (clock skew attack)\n if (transaction.blockTime > now + 60) {\n return { valid: false, confirmed: true, signature, error: 'Invalid transaction time' };\n }\n }\n\n // Parse transfer details\n const transferDetails = parseSOLTransfer(transaction, expectedRecipient);\n\n if (!transferDetails) {\n return {\n valid: false,\n confirmed: true,\n signature,\n error: 'No valid SOL transfer to recipient found',\n };\n }\n\n // SECURITY: Validate amount (must meet or exceed expected)\n if (transferDetails.amount < expectedAmount) {\n return {\n valid: false,\n confirmed: true,\n signature,\n from: transferDetails.from,\n to: transferDetails.to,\n amount: transferDetails.amount,\n error: 'Insufficient payment amount',\n };\n }\n\n return {\n valid: true,\n confirmed: true,\n signature,\n from: transferDetails.from,\n to: transferDetails.to,\n amount: transferDetails.amount,\n blockTime: transaction.blockTime ?? undefined,\n slot: transaction.slot,\n };\n } catch (error) {\n // SECURITY: Don't expose internal error details\n return {\n valid: false,\n confirmed: false,\n signature,\n error: 'Verification failed',\n };\n }\n}\n\n/**\n * Wait for transaction confirmation\n */\nexport async function waitForConfirmation(\n signature: string,\n clientConfig: SolanaClientConfig\n): Promise<{ confirmed: boolean; slot?: number; error?: string }> {\n if (!isValidSignature(signature)) {\n return { confirmed: false, error: 'Invalid signature format' };\n }\n\n const connection = getConnection(clientConfig);\n\n try {\n const confirmation = await connection.confirmTransaction(signature, 'confirmed');\n\n if (confirmation.value.err) {\n return { confirmed: false, error: 'Transaction failed' };\n }\n\n return { confirmed: true, slot: confirmation.context?.slot };\n } catch {\n return { confirmed: false, error: 'Confirmation timeout' };\n }\n}\n\n/**\n * Get recent transactions for a wallet\n */\nexport async function getWalletTransactions(\n walletAddress: string,\n clientConfig: SolanaClientConfig,\n limit: number = 20\n): Promise<Array<{ signature: string; blockTime?: number; slot: number }>> {\n if (!isValidWalletAddress(walletAddress)) {\n return [];\n }\n\n // SECURITY: Cap limit to prevent abuse\n const safeLimit = Math.min(Math.max(limit, 1), 100);\n\n const connection = getConnection(clientConfig);\n\n try {\n const pubkey = new PublicKey(walletAddress);\n const signatures = await connection.getSignaturesForAddress(pubkey, { limit: safeLimit });\n return signatures.map((sig) => ({\n signature: sig.signature,\n blockTime: sig.blockTime ?? undefined,\n slot: sig.slot,\n }));\n } catch {\n return [];\n }\n}\n\n/**\n * Convert lamports to SOL\n */\nexport function lamportsToSol(lamports: bigint | number): number {\n return Number(lamports) / LAMPORTS_PER_SOL;\n}\n\n/**\n * Convert SOL to lamports\n */\nexport function solToLamports(sol: number): bigint {\n if (!Number.isFinite(sol) || sol < 0) {\n throw new Error('Invalid SOL amount');\n }\n return BigInt(Math.floor(sol * LAMPORTS_PER_SOL));\n}\n","// Session management with JWT (framework-agnostic core)\n// SECURITY: Uses jose library with HS256, constant-time validation, input sanitization\nimport { SignJWT, jwtVerify } from 'jose';\nimport { v4 as uuidv4 } from 'uuid';\nimport type { SessionData, SessionConfig, SessionValidation, SessionJWTPayload } from '../types';\n\n// Maximum articles per session to prevent unbounded growth\nconst MAX_ARTICLES_PER_SESSION = 100;\n\n// Minimum secret length for security\nconst MIN_SECRET_LENGTH = 32;\n\n/**\n * Get the secret key for JWT signing\n * SECURITY: Enforces minimum secret length\n */\nfunction getSecretKey(secret: string): Uint8Array {\n if (!secret || typeof secret !== 'string') {\n throw new Error('Session secret is required');\n }\n if (secret.length < MIN_SECRET_LENGTH) {\n throw new Error(`Session secret must be at least ${MIN_SECRET_LENGTH} characters`);\n }\n return new TextEncoder().encode(secret);\n}\n\n/**\n * Validate wallet address format (base58, 32-44 chars)\n * SECURITY: Prevents injection via wallet address field\n */\nfunction validateWalletAddress(address: string): boolean {\n if (!address || typeof address !== 'string') return false;\n // Solana addresses are base58, typically 32-44 characters\n const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\n return base58Regex.test(address);\n}\n\n/**\n * Validate article ID format\n * SECURITY: Prevents injection via articleId field\n */\nfunction validateArticleId(articleId: string): boolean {\n if (!articleId || typeof articleId !== 'string') return false;\n // Allow alphanumeric, hyphens, underscores, max 128 chars\n if (articleId.length > 128) return false;\n const safeIdRegex = /^[a-zA-Z0-9_-]+$/;\n return safeIdRegex.test(articleId);\n}\n\n/**\n * Create a new session after successful payment\n * SECURITY: Validates inputs, enforces limits\n */\nexport async function createSession(\n walletAddress: string,\n articleId: string,\n config: SessionConfig,\n siteWide: boolean = false\n): Promise<{ token: string; session: SessionData }> {\n // Input validation\n if (!validateWalletAddress(walletAddress)) {\n throw new Error('Invalid wallet address format');\n }\n if (!validateArticleId(articleId)) {\n throw new Error('Invalid article ID format');\n }\n if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 720) {\n throw new Error('Session duration must be between 1 and 720 hours');\n }\n\n const sessionId = uuidv4();\n const now = Math.floor(Date.now() / 1000);\n const expiresAt = now + (config.durationHours * 3600);\n\n const session: SessionData = {\n id: sessionId,\n walletAddress,\n unlockedArticles: [articleId],\n siteWideUnlock: Boolean(siteWide),\n createdAt: now,\n expiresAt,\n };\n\n const payload: SessionJWTPayload = {\n sub: walletAddress,\n sid: sessionId,\n articles: session.unlockedArticles,\n siteWide: session.siteWideUnlock,\n iat: now,\n exp: expiresAt,\n };\n\n const token = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(`${config.durationHours}h`)\n .sign(getSecretKey(config.secret));\n\n return { token, session };\n}\n\n/**\n * Validate an existing session token\n * SECURITY: jose library handles timing-safe comparison internally\n */\nexport async function validateSession(\n token: string,\n secret: string\n): Promise<SessionValidation> {\n // Input validation\n if (!token || typeof token !== 'string') {\n return { valid: false, reason: 'Invalid token format' };\n }\n\n try {\n const { payload } = await jwtVerify(token, getSecretKey(secret));\n const sessionPayload = payload as unknown as SessionJWTPayload;\n\n // Validate required fields exist\n if (!sessionPayload.sub || !sessionPayload.sid || !sessionPayload.exp) {\n return { valid: false, reason: 'Malformed session payload' };\n }\n\n // Check expiration (jose already checks, but double-check)\n const now = Math.floor(Date.now() / 1000);\n if (sessionPayload.exp < now) {\n return { valid: false, reason: 'Session expired' };\n }\n\n // Validate wallet address format from token\n if (!validateWalletAddress(sessionPayload.sub)) {\n return { valid: false, reason: 'Invalid session data' };\n }\n\n const session: SessionData = {\n id: sessionPayload.sid,\n walletAddress: sessionPayload.sub,\n unlockedArticles: Array.isArray(sessionPayload.articles) ? sessionPayload.articles : [],\n siteWideUnlock: Boolean(sessionPayload.siteWide),\n createdAt: sessionPayload.iat ?? 0,\n expiresAt: sessionPayload.exp,\n };\n\n return { valid: true, session };\n } catch (error) {\n // SECURITY: Don't expose internal error details\n return { valid: false, reason: 'Invalid session' };\n }\n}\n\n/**\n * Add an article to an existing session\n * SECURITY: Enforces article limit to prevent token bloat\n */\nexport async function addArticleToSession(\n token: string,\n articleId: string,\n secret: string\n): Promise<{ token: string; session: SessionData } | null> {\n // Validate article ID\n if (!validateArticleId(articleId)) {\n return null;\n }\n\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return null;\n }\n\n const session = validation.session;\n\n // Already unlocked\n if (session.unlockedArticles.includes(articleId)) {\n return { token, session };\n }\n\n // SECURITY: Enforce maximum articles per session\n if (session.unlockedArticles.length >= MAX_ARTICLES_PER_SESSION) {\n return null;\n }\n\n const updatedArticles = [...session.unlockedArticles, articleId];\n\n const payload: SessionJWTPayload = {\n sub: session.walletAddress,\n sid: session.id,\n articles: updatedArticles,\n siteWide: session.siteWideUnlock,\n iat: session.createdAt,\n exp: session.expiresAt,\n };\n\n const newToken = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(getSecretKey(secret));\n\n return {\n token: newToken,\n session: { ...session, unlockedArticles: updatedArticles },\n };\n}\n\n/**\n * Check if an article is unlocked for a session\n */\nexport async function isArticleUnlocked(\n token: string,\n articleId: string,\n secret: string\n): Promise<boolean> {\n if (!validateArticleId(articleId)) {\n return false;\n }\n\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return false;\n }\n\n if (validation.session.siteWideUnlock) {\n return true;\n }\n\n return validation.session.unlockedArticles.includes(articleId);\n}\n","// x402 payment configuration and helpers\n// SECURITY: Input sanitization for payment requirements\nimport type { PaymentRequirement, X402Network, SolanaNetwork } from '../types';\nimport { toX402Network } from '../solana';\n\n// Wallet address validation regex\nconst WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\n\n/** Parameters for building a payment requirement */\nexport interface BuildPaymentParams {\n /** Unique article identifier */\n articleId: string;\n /** Article title for display */\n articleTitle: string;\n /** Price in lamports */\n priceInLamports: bigint;\n /** Creator wallet address */\n creatorWallet: string;\n /** Full URL of the protected resource */\n resourceUrl: string;\n /** Solana network */\n network: SolanaNetwork;\n /** Max time to complete payment (default: 300s) */\n maxTimeoutSeconds?: number;\n}\n\n/**\n * Sanitize string for safe display (prevent XSS in UIs)\n */\nfunction sanitizeDisplayString(str: string, maxLength: number = 200): string {\n if (!str || typeof str !== 'string') return '';\n return str.slice(0, maxLength).replace(/[<>\"'&]/g, '');\n}\n\n/**\n * Validate URL format\n */\nfunction isValidUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return parsed.protocol === 'http:' || parsed.protocol === 'https:';\n } catch {\n return false;\n }\n}\n\n/**\n * Build a payment requirement for an article\n * SECURITY: Validates all inputs before building requirement\n */\nexport function buildPaymentRequirement(params: BuildPaymentParams): PaymentRequirement {\n // Validate wallet address\n if (!WALLET_REGEX.test(params.creatorWallet)) {\n throw new Error('Invalid creator wallet address');\n }\n\n // Validate price\n if (params.priceInLamports <= 0n) {\n throw new Error('Price must be positive');\n }\n\n // Validate URL\n if (!isValidUrl(params.resourceUrl)) {\n throw new Error('Invalid resource URL');\n }\n\n // Validate network\n if (params.network !== 'devnet' && params.network !== 'mainnet-beta') {\n throw new Error('Invalid network');\n }\n\n // Validate timeout\n const timeout = params.maxTimeoutSeconds ?? 300;\n if (timeout < 60 || timeout > 3600) {\n throw new Error('Timeout must be between 60 and 3600 seconds');\n }\n\n const x402Network = toX402Network(params.network);\n\n // Sanitize display strings\n const safeTitle = sanitizeDisplayString(params.articleTitle, 200);\n const safeArticleId = sanitizeDisplayString(params.articleId, 128);\n\n return {\n scheme: 'exact',\n network: x402Network,\n maxAmountRequired: params.priceInLamports.toString(),\n resource: params.resourceUrl,\n description: `Unlock: ${safeTitle}`,\n mimeType: 'text/html',\n payTo: params.creatorWallet,\n maxTimeoutSeconds: timeout,\n asset: 'native',\n extra: {\n name: safeTitle,\n articleId: safeArticleId,\n },\n };\n}\n\n/**\n * Encode payment requirement for x402 header\n */\nexport function encodePaymentRequired(requirement: PaymentRequirement): string {\n return Buffer.from(JSON.stringify(requirement)).toString('base64');\n}\n\n/**\n * Decode payment requirement from x402 header\n * SECURITY: Safe parsing with size limit\n */\nexport function decodePaymentRequired(encoded: string): PaymentRequirement {\n if (!encoded || typeof encoded !== 'string') {\n throw new Error('Invalid encoded requirement');\n }\n\n // SECURITY: Limit size to prevent DoS\n if (encoded.length > 10000) {\n throw new Error('Encoded requirement too large');\n }\n\n try {\n const decoded = Buffer.from(encoded, 'base64').toString('utf-8');\n return JSON.parse(decoded);\n } catch {\n throw new Error('Failed to decode payment requirement');\n }\n}\n\n/**\n * x402 response header names\n */\nexport const X402_HEADERS = {\n PAYMENT_REQUIRED: 'X-Payment-Required',\n PAYMENT: 'X-Payment',\n PAYMENT_RESPONSE: 'X-Payment-Response',\n} as const;\n\n/**\n * Create 402 Payment Required response body\n */\nexport function create402ResponseBody(requirement: PaymentRequirement): {\n error: string;\n message: string;\n price: { amount: string; asset: string; network: X402Network };\n} {\n return {\n error: 'Payment Required',\n message: requirement.description,\n price: {\n amount: requirement.maxAmountRequired,\n asset: requirement.asset,\n network: requirement.network,\n },\n };\n}\n\n/**\n * Create headers for 402 response\n */\nexport function create402Headers(requirement: PaymentRequirement): Record<string, string> {\n const encoded = encodePaymentRequired(requirement);\n return {\n 'Content-Type': 'application/json',\n [X402_HEADERS.PAYMENT_REQUIRED]: encoded,\n 'Access-Control-Expose-Headers': X402_HEADERS.PAYMENT_REQUIRED,\n };\n}\n","// x402 payment verification service\n// SECURITY: Input validation, network verification, on-chain settlement check\nimport { verifyPayment, type SolanaClientConfig } from '../solana';\nimport type { PaymentPayload, PaymentRequirement, VerificationResponse } from '../types';\n\n// Signature validation regex (base58, 87-88 chars)\nconst SIGNATURE_REGEX = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;\n\n/**\n * Verify a payment payload against requirements\n * SECURITY: Full validation chain - format, network, on-chain\n */\nexport async function verifyX402Payment(\n payload: PaymentPayload,\n requirement: PaymentRequirement,\n clientConfig: SolanaClientConfig\n): Promise<VerificationResponse> {\n // SECURITY: Validate payload exists\n if (!payload || typeof payload !== 'object') {\n return { valid: false, invalidReason: 'Invalid payload' };\n }\n\n // SECURITY: Validate signature exists and format\n const signature = payload.payload?.signature;\n if (!signature || typeof signature !== 'string') {\n return { valid: false, invalidReason: 'Missing transaction signature' };\n }\n if (!SIGNATURE_REGEX.test(signature)) {\n return { valid: false, invalidReason: 'Invalid signature format' };\n }\n\n // SECURITY: Validate x402 version\n if (payload.x402Version !== 1) {\n return { valid: false, invalidReason: 'Unsupported x402 version' };\n }\n\n // SECURITY: Validate scheme\n if (payload.scheme !== 'exact') {\n return { valid: false, invalidReason: 'Unsupported payment scheme' };\n }\n\n // SECURITY: Validate network matches exactly\n if (payload.network !== requirement.network) {\n return {\n valid: false,\n invalidReason: `Network mismatch: expected ${requirement.network}`,\n };\n }\n\n // SECURITY: Validate requirement has valid payTo address\n const walletRegex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\n if (!walletRegex.test(requirement.payTo)) {\n return { valid: false, invalidReason: 'Invalid recipient configuration' };\n }\n\n // SECURITY: Validate amount is positive\n let expectedAmount: bigint;\n try {\n expectedAmount = BigInt(requirement.maxAmountRequired);\n if (expectedAmount <= 0n) {\n return { valid: false, invalidReason: 'Invalid payment amount' };\n }\n } catch {\n return { valid: false, invalidReason: 'Invalid payment amount format' };\n }\n\n // Verify on-chain\n const verification = await verifyPayment({\n signature,\n expectedRecipient: requirement.payTo,\n expectedAmount,\n maxAgeSeconds: requirement.maxTimeoutSeconds,\n clientConfig,\n });\n\n if (!verification.valid) {\n return {\n valid: false,\n invalidReason: verification.error || 'Transaction verification failed',\n };\n }\n\n return {\n valid: true,\n settled: verification.confirmed,\n transaction: {\n signature: verification.signature,\n blockTime: verification.blockTime,\n slot: verification.slot,\n },\n };\n}\n\n/**\n * Parse payment payload from x402 header\n * SECURITY: Safe JSON parsing with try-catch\n */\nexport function parsePaymentHeader(header: string): PaymentPayload | null {\n if (!header || typeof header !== 'string') {\n return null;\n }\n\n // SECURITY: Limit header size to prevent DoS\n if (header.length > 10000) {\n return null;\n }\n\n try {\n const decoded = Buffer.from(header, 'base64').toString('utf-8');\n const parsed = JSON.parse(decoded);\n\n // Basic structure validation\n if (!parsed || typeof parsed !== 'object') {\n return null;\n }\n\n return parsed as PaymentPayload;\n } catch {\n return null;\n }\n}\n\n/**\n * Encode payment response for header\n */\nexport function encodePaymentResponse(response: VerificationResponse): string {\n return Buffer.from(JSON.stringify(response)).toString('base64');\n}\n"]}
|