@alleyboss/micropay-solana-x402-paywall 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +72 -116
  2. package/dist/agent/index.cjs +358 -0
  3. package/dist/agent/index.cjs.map +1 -0
  4. package/dist/agent/index.d.cts +221 -0
  5. package/dist/agent/index.d.ts +221 -0
  6. package/dist/agent/index.js +347 -0
  7. package/dist/agent/index.js.map +1 -0
  8. package/dist/client/index.cjs +1 -1
  9. package/dist/client/index.cjs.map +1 -1
  10. package/dist/client/index.d.cts +10 -1
  11. package/dist/client/index.d.ts +10 -1
  12. package/dist/client/index.js +1 -1
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/express/index.cjs +79 -0
  15. package/dist/express/index.cjs.map +1 -0
  16. package/dist/express/index.d.cts +40 -0
  17. package/dist/express/index.d.ts +40 -0
  18. package/dist/express/index.js +76 -0
  19. package/dist/express/index.js.map +1 -0
  20. package/dist/index.cjs +315 -1116
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +6 -10
  23. package/dist/index.d.ts +6 -10
  24. package/dist/index.js +283 -1074
  25. package/dist/index.js.map +1 -1
  26. package/dist/session/index.cjs.map +1 -1
  27. package/dist/session/index.d.cts +1 -1
  28. package/dist/session/index.d.ts +1 -1
  29. package/dist/session/index.js.map +1 -1
  30. package/dist/{session-D2IoWAWV.d.cts → types-BWYQMw03.d.cts} +1 -16
  31. package/dist/{session-D2IoWAWV.d.ts → types-BWYQMw03.d.ts} +1 -16
  32. package/package.json +29 -59
  33. package/dist/client-D-dteoJw.d.cts +0 -63
  34. package/dist/client-DfCIRrNG.d.ts +0 -63
  35. package/dist/memory-Daxkczti.d.cts +0 -29
  36. package/dist/memory-Daxkczti.d.ts +0 -29
  37. package/dist/middleware/index.cjs +0 -273
  38. package/dist/middleware/index.cjs.map +0 -1
  39. package/dist/middleware/index.d.cts +0 -91
  40. package/dist/middleware/index.d.ts +0 -91
  41. package/dist/middleware/index.js +0 -267
  42. package/dist/middleware/index.js.map +0 -1
  43. package/dist/nextjs-BDyOqGAq.d.cts +0 -81
  44. package/dist/nextjs-CbX8_9yK.d.ts +0 -81
  45. package/dist/payment-BGp7eMQl.d.cts +0 -103
  46. package/dist/payment-BGp7eMQl.d.ts +0 -103
  47. package/dist/solana/index.cjs +0 -589
  48. package/dist/solana/index.cjs.map +0 -1
  49. package/dist/solana/index.d.cts +0 -240
  50. package/dist/solana/index.d.ts +0 -240
  51. package/dist/solana/index.js +0 -567
  52. package/dist/solana/index.js.map +0 -1
  53. package/dist/store/index.cjs +0 -99
  54. package/dist/store/index.cjs.map +0 -1
  55. package/dist/store/index.d.cts +0 -38
  56. package/dist/store/index.d.ts +0 -38
  57. package/dist/store/index.js +0 -96
  58. package/dist/store/index.js.map +0 -1
  59. package/dist/utils/index.cjs +0 -68
  60. package/dist/utils/index.cjs.map +0 -1
  61. package/dist/utils/index.d.cts +0 -30
  62. package/dist/utils/index.d.ts +0 -30
  63. package/dist/utils/index.js +0 -65
  64. package/dist/utils/index.js.map +0 -1
  65. package/dist/x402/index.cjs +0 -387
  66. package/dist/x402/index.cjs.map +0 -1
  67. package/dist/x402/index.d.cts +0 -96
  68. package/dist/x402/index.d.ts +0 -96
  69. package/dist/x402/index.js +0 -375
  70. package/dist/x402/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,8 +1,14 @@
1
- import { PublicKey, LAMPORTS_PER_SOL, ComputeBudgetProgram, TransactionMessage, VersionedTransaction, clusterApiUrl, Connection } from '@solana/web3.js';
1
+ export * from '@x402/core';
2
+ export * from '@x402/core/types';
3
+ export * from '@x402/core/client';
4
+ export * from '@x402/svm';
5
+ import { PublicKey, SystemProgram, LAMPORTS_PER_SOL, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
2
6
  import { SignJWT, jwtVerify } from 'jose';
3
7
  import { v4 } from 'uuid';
4
8
 
5
- // src/types/payment.ts
9
+ // src/index.ts
10
+
11
+ // src/client/types.ts
6
12
  var TOKEN_MINTS = {
7
13
  /** USDC on mainnet */
8
14
  USDC_MAINNET: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
@@ -11,471 +17,87 @@ var TOKEN_MINTS = {
11
17
  /** USDT on mainnet */
12
18
  USDT_MAINNET: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
13
19
  };
14
- var cachedConnection = null;
15
- var cachedNetwork = null;
16
- var cachedFallbacks = [];
17
- var cachedFallbackEnabled = false;
18
- function buildRpcUrl(config2) {
19
- const { network, rpcUrl, tatumApiKey } = config2;
20
- if (rpcUrl) {
21
- if (rpcUrl.includes("tatum.io") && tatumApiKey && !rpcUrl.includes(tatumApiKey)) {
22
- return rpcUrl.endsWith("/") ? `${rpcUrl}${tatumApiKey}` : `${rpcUrl}/${tatumApiKey}`;
23
- }
24
- return rpcUrl;
25
- }
26
- if (tatumApiKey) {
27
- const baseUrl = network === "mainnet-beta" ? "https://solana-mainnet.gateway.tatum.io" : "https://solana-devnet.gateway.tatum.io";
28
- return `${baseUrl}/${tatumApiKey}`;
29
- }
30
- return clusterApiUrl(network);
31
- }
32
- function createConnection(rpcUrl) {
33
- return new Connection(rpcUrl, {
34
- commitment: "confirmed",
35
- confirmTransactionInitialTimeout: 6e4
36
- });
37
- }
38
- function getConnection(config2) {
39
- const { network } = config2;
40
- if (cachedConnection && cachedNetwork === network) {
41
- return cachedConnection;
42
- }
43
- const rpcUrl = buildRpcUrl(config2);
44
- cachedConnection = createConnection(rpcUrl);
45
- cachedNetwork = network;
46
- cachedFallbackEnabled = config2.enableFallback ?? false;
47
- cachedFallbacks = [];
48
- if (cachedFallbackEnabled && config2.fallbackRpcUrls?.length) {
49
- cachedFallbacks = config2.fallbackRpcUrls.map(createConnection);
50
- }
51
- return cachedConnection;
52
- }
53
- function getConnectionWithFallback(config2) {
54
- const connection = getConnection(config2);
55
- return {
56
- connection,
57
- fallbacks: cachedFallbacks,
58
- fallbackEnabled: cachedFallbackEnabled
59
- };
60
- }
61
- async function withFallback(config2, operation) {
62
- const { connection, fallbacks, fallbackEnabled } = getConnectionWithFallback(config2);
63
- try {
64
- return await operation(connection);
65
- } catch (error) {
66
- if (!fallbackEnabled || fallbacks.length === 0) {
67
- throw error;
68
- }
69
- if (!isRetryableError(error)) {
70
- throw error;
71
- }
72
- for (let i = 0; i < fallbacks.length; i++) {
73
- try {
74
- return await operation(fallbacks[i]);
75
- } catch (fallbackError) {
76
- if (i === fallbacks.length - 1) {
77
- throw fallbackError;
78
- }
79
- }
80
- }
81
- throw error;
82
- }
83
- }
84
- function isRetryableError(error) {
85
- if (error instanceof Error) {
86
- const message = error.message.toLowerCase();
87
- return message.includes("429") || message.includes("503") || message.includes("502") || message.includes("timeout") || message.includes("econnrefused") || message.includes("enotfound") || message.includes("rate limit");
88
- }
89
- return false;
90
- }
91
- function resetConnection() {
92
- cachedConnection = null;
93
- cachedNetwork = null;
94
- }
95
- function isMainnet(network) {
96
- return network === "mainnet-beta";
97
- }
98
- function toX402Network(network) {
99
- return network === "mainnet-beta" ? "solana-mainnet" : "solana-devnet";
100
- }
101
- var SIGNATURE_REGEX = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;
102
- var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
103
- function isValidSignature(signature) {
104
- if (!signature || typeof signature !== "string") return false;
105
- return SIGNATURE_REGEX.test(signature);
106
- }
107
- function isValidWalletAddress(address) {
108
- if (!address || typeof address !== "string") return false;
109
- return WALLET_REGEX.test(address);
110
- }
111
- function parseSOLTransfer(transaction, expectedRecipient) {
112
- const instructions = transaction.transaction.message.instructions;
113
- for (const ix of instructions) {
114
- if ("parsed" in ix && ix.program === "system") {
115
- const parsed = ix.parsed;
116
- if (parsed.type === "transfer" && parsed.info.destination === expectedRecipient) {
117
- return {
118
- from: parsed.info.source,
119
- to: parsed.info.destination,
120
- amount: BigInt(parsed.info.lamports)
121
- };
122
- }
123
- }
124
- }
125
- if (transaction.meta?.innerInstructions) {
126
- for (const inner of transaction.meta.innerInstructions) {
127
- for (const ix of inner.instructions) {
128
- if ("parsed" in ix && ix.program === "system") {
129
- const parsed = ix.parsed;
130
- if (parsed.type === "transfer" && parsed.info.destination === expectedRecipient) {
131
- return {
132
- from: parsed.info.source,
133
- to: parsed.info.destination,
134
- amount: BigInt(parsed.info.lamports)
135
- };
136
- }
137
- }
138
- }
139
- }
140
- }
141
- return null;
142
- }
143
- async function verifyPayment(params) {
144
- const {
145
- signature,
146
- expectedRecipient,
147
- expectedAmount,
148
- maxAgeSeconds = 300,
149
- clientConfig,
150
- signatureStore
151
- } = params;
152
- if (!isValidSignature(signature)) {
153
- return { valid: false, confirmed: false, signature, error: "Invalid signature format" };
154
- }
155
- if (!isValidWalletAddress(expectedRecipient)) {
156
- return { valid: false, confirmed: false, signature, error: "Invalid recipient address" };
157
- }
158
- if (expectedAmount <= 0n) {
159
- return { valid: false, confirmed: false, signature, error: "Invalid expected amount" };
160
- }
161
- if (signatureStore) {
162
- const isUsed = await signatureStore.hasBeenUsed(signature);
163
- if (isUsed) {
164
- return { valid: false, confirmed: true, signature, error: "Signature already used" };
165
- }
166
- }
167
- const effectiveMaxAge = Math.min(Math.max(maxAgeSeconds, 60), 3600);
168
- const connection = getConnection(clientConfig);
169
- try {
170
- const transaction = await connection.getParsedTransaction(signature, {
171
- commitment: "confirmed",
172
- maxSupportedTransactionVersion: 0
173
- });
174
- if (!transaction) {
175
- return { valid: false, confirmed: false, signature, error: "Transaction not found" };
176
- }
177
- if (transaction.meta?.err) {
178
- return {
179
- valid: false,
180
- confirmed: true,
181
- signature,
182
- error: "Transaction failed on-chain"
183
- };
184
- }
185
- if (transaction.blockTime) {
186
- const now = Math.floor(Date.now() / 1e3);
187
- if (now - transaction.blockTime > effectiveMaxAge) {
188
- return { valid: false, confirmed: true, signature, error: "Transaction too old" };
189
- }
190
- if (transaction.blockTime > now + 60) {
191
- return { valid: false, confirmed: true, signature, error: "Invalid transaction time" };
192
- }
193
- }
194
- const transferDetails = parseSOLTransfer(transaction, expectedRecipient);
195
- if (!transferDetails) {
196
- return {
197
- valid: false,
198
- confirmed: true,
199
- signature,
200
- error: "No valid SOL transfer to recipient found"
201
- };
202
- }
203
- if (transferDetails.amount < expectedAmount) {
204
- return {
205
- valid: false,
206
- confirmed: true,
207
- signature,
208
- from: transferDetails.from,
209
- to: transferDetails.to,
210
- amount: transferDetails.amount,
211
- error: "Insufficient payment amount"
212
- };
213
- }
214
- return {
215
- valid: true,
216
- confirmed: true,
217
- signature,
218
- from: transferDetails.from,
219
- to: transferDetails.to,
220
- amount: transferDetails.amount,
221
- blockTime: transaction.blockTime ?? void 0,
222
- slot: transaction.slot
223
- };
224
- } catch (error) {
225
- return {
226
- valid: false,
227
- confirmed: false,
228
- signature,
229
- error: "Verification failed"
230
- };
231
- }
232
- }
233
- async function waitForConfirmation(signature, clientConfig) {
234
- if (!isValidSignature(signature)) {
235
- return { confirmed: false, error: "Invalid signature format" };
20
+
21
+ // src/client/payment.ts
22
+ function buildSolanaPayUrl(params) {
23
+ const { recipient, amount, splToken, reference, label, message } = params;
24
+ const url = new URL(`solana:${recipient}`);
25
+ if (amount !== void 0) {
26
+ url.searchParams.set("amount", amount.toString());
236
27
  }
237
- const connection = getConnection(clientConfig);
238
- try {
239
- const confirmation = await connection.confirmTransaction(signature, "confirmed");
240
- if (confirmation.value.err) {
241
- return { confirmed: false, error: "Transaction failed" };
242
- }
243
- return { confirmed: true, slot: confirmation.context?.slot };
244
- } catch {
245
- return { confirmed: false, error: "Confirmation timeout" };
28
+ if (splToken) {
29
+ url.searchParams.set("spl-token", splToken);
246
30
  }
247
- }
248
- async function getWalletTransactions(walletAddress, clientConfig, limit = 20) {
249
- if (!isValidWalletAddress(walletAddress)) {
250
- return [];
31
+ if (reference) {
32
+ url.searchParams.set("reference", reference);
251
33
  }
252
- const safeLimit = Math.min(Math.max(limit, 1), 100);
253
- const connection = getConnection(clientConfig);
254
- try {
255
- const pubkey = new PublicKey(walletAddress);
256
- const signatures = await connection.getSignaturesForAddress(pubkey, { limit: safeLimit });
257
- return signatures.map((sig) => ({
258
- signature: sig.signature,
259
- blockTime: sig.blockTime ?? void 0,
260
- slot: sig.slot
261
- }));
262
- } catch {
263
- return [];
34
+ if (label) {
35
+ url.searchParams.set("label", label);
264
36
  }
265
- }
266
- function lamportsToSol(lamports) {
267
- return Number(lamports) / LAMPORTS_PER_SOL;
268
- }
269
- function solToLamports(sol) {
270
- if (!Number.isFinite(sol) || sol < 0) {
271
- throw new Error("Invalid SOL amount");
37
+ if (message) {
38
+ url.searchParams.set("message", message);
272
39
  }
273
- return BigInt(Math.floor(sol * LAMPORTS_PER_SOL));
40
+ return url.toString();
274
41
  }
275
- var SIGNATURE_REGEX2 = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;
276
- var WALLET_REGEX2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
277
- function resolveMintAddress(asset, network) {
278
- if (asset === "native") return null;
42
+ function createPaymentFlow(config2) {
43
+ const { network, recipientWallet, amount, asset = "native", memo } = config2;
44
+ let decimals = 9;
45
+ let mintAddress;
279
46
  if (asset === "usdc") {
280
- return network === "mainnet-beta" ? TOKEN_MINTS.USDC_MAINNET : TOKEN_MINTS.USDC_DEVNET;
281
- }
282
- if (asset === "usdt") {
283
- return TOKEN_MINTS.USDT_MAINNET;
284
- }
285
- if (typeof asset === "object" && "mint" in asset) {
286
- return asset.mint;
287
- }
288
- return null;
289
- }
290
- function getTokenDecimals(asset) {
291
- if (asset === "native") return 9;
292
- if (asset === "usdc" || asset === "usdt") return 6;
293
- if (typeof asset === "object" && "decimals" in asset) {
294
- return asset.decimals ?? 6;
295
- }
296
- return 6;
297
- }
298
- function parseSPLTransfer(transaction, expectedRecipient, expectedMint) {
299
- const instructions = transaction.transaction.message.instructions;
300
- for (const ix of instructions) {
301
- if ("parsed" in ix && (ix.program === "spl-token" || ix.program === "spl-token-2022")) {
302
- const parsed = ix.parsed;
303
- if (parsed.type === "transfer" || parsed.type === "transferChecked") {
304
- const amount = parsed.info.amount || parsed.info.tokenAmount?.amount;
305
- if (amount && parsed.info.destination) {
306
- return {
307
- from: parsed.info.authority || parsed.info.source || "",
308
- to: parsed.info.destination,
309
- amount: BigInt(amount),
310
- mint: parsed.info.mint || expectedMint
311
- };
312
- }
313
- }
314
- }
315
- }
316
- if (transaction.meta?.innerInstructions) {
317
- for (const inner of transaction.meta.innerInstructions) {
318
- for (const ix of inner.instructions) {
319
- if ("parsed" in ix && (ix.program === "spl-token" || ix.program === "spl-token-2022")) {
320
- const parsed = ix.parsed;
321
- if (parsed.type === "transfer" || parsed.type === "transferChecked") {
322
- const amount = parsed.info.amount || parsed.info.tokenAmount?.amount;
323
- if (amount) {
324
- return {
325
- from: parsed.info.authority || parsed.info.source || "",
326
- to: parsed.info.destination || "",
327
- amount: BigInt(amount),
328
- mint: parsed.info.mint || expectedMint
329
- };
330
- }
331
- }
332
- }
333
- }
334
- }
47
+ decimals = 6;
48
+ mintAddress = network === "mainnet-beta" ? TOKEN_MINTS.USDC_MAINNET : TOKEN_MINTS.USDC_DEVNET;
49
+ } else if (asset === "usdt") {
50
+ decimals = 6;
51
+ mintAddress = TOKEN_MINTS.USDT_MAINNET;
52
+ } else if (typeof asset === "object" && "mint" in asset) {
53
+ decimals = asset.decimals ?? 6;
54
+ mintAddress = asset.mint;
335
55
  }
336
- if (transaction.meta?.postTokenBalances && transaction.meta?.preTokenBalances) {
337
- const preBalances = transaction.meta.preTokenBalances;
338
- const postBalances = transaction.meta.postTokenBalances;
339
- for (const post of postBalances) {
340
- if (post.mint === expectedMint && post.owner === expectedRecipient) {
341
- const pre = preBalances.find(
342
- (p) => p.accountIndex === post.accountIndex
343
- );
344
- const preAmount = BigInt(pre?.uiTokenAmount?.amount || "0");
345
- const postAmount = BigInt(post.uiTokenAmount?.amount || "0");
346
- const transferred = postAmount - preAmount;
347
- if (transferred > 0n) {
348
- return {
349
- from: "",
350
- // Can't determine from balance changes
351
- to: expectedRecipient,
352
- amount: transferred,
353
- mint: expectedMint
354
- };
355
- }
356
- }
56
+ const naturalAmount = Number(amount) / Math.pow(10, decimals);
57
+ return {
58
+ /** Get the payment configuration */
59
+ getConfig: () => ({ ...config2 }),
60
+ /** Get amount in natural display units (e.g., 0.01 SOL) */
61
+ getDisplayAmount: () => naturalAmount,
62
+ /** Get amount formatted with symbol */
63
+ getFormattedAmount: () => {
64
+ const symbol = asset === "native" ? "SOL" : asset === "usdc" ? "USDC" : asset === "usdt" ? "USDT" : "tokens";
65
+ return `${naturalAmount.toFixed(decimals > 6 ? 4 : 2)} ${symbol}`;
66
+ },
67
+ /** Generate Solana Pay URL for QR codes */
68
+ getSolanaPayUrl: (options = {}) => {
69
+ return buildSolanaPayUrl({
70
+ recipient: recipientWallet,
71
+ amount: naturalAmount,
72
+ splToken: mintAddress,
73
+ label: options.label,
74
+ reference: options.reference,
75
+ message: memo
76
+ });
77
+ },
78
+ /** Get the token mint address (undefined for native SOL) */
79
+ getMintAddress: () => mintAddress,
80
+ /** Check if this is a native SOL payment */
81
+ isNativePayment: () => asset === "native",
82
+ /** Get network information */
83
+ getNetworkInfo: () => ({
84
+ network,
85
+ isMainnet: network === "mainnet-beta",
86
+ explorerUrl: network === "mainnet-beta" ? "https://explorer.solana.com" : "https://explorer.solana.com?cluster=devnet"
87
+ }),
88
+ /** Build explorer URL for a transaction */
89
+ getExplorerUrl: (signature) => {
90
+ const baseUrl = "https://explorer.solana.com/tx";
91
+ const cluster = network === "mainnet-beta" ? "" : "?cluster=devnet";
92
+ return `${baseUrl}/${signature}${cluster}`;
357
93
  }
358
- }
359
- return null;
94
+ };
360
95
  }
361
- async function verifySPLPayment(params) {
362
- const {
363
- signature,
364
- expectedRecipient,
365
- expectedAmount,
366
- asset,
367
- clientConfig,
368
- maxAgeSeconds = 300,
369
- signatureStore
370
- } = params;
371
- if (signatureStore) {
372
- const isUsed = await signatureStore.hasBeenUsed(signature);
373
- if (isUsed) {
374
- return { valid: false, confirmed: true, signature, error: "Signature already used" };
375
- }
376
- }
377
- if (!SIGNATURE_REGEX2.test(signature)) {
378
- return { valid: false, confirmed: false, signature, error: "Invalid signature format" };
379
- }
380
- if (!WALLET_REGEX2.test(expectedRecipient)) {
381
- return { valid: false, confirmed: false, signature, error: "Invalid recipient address" };
382
- }
383
- const mintAddress = resolveMintAddress(asset, clientConfig.network);
384
- if (!mintAddress) {
385
- return { valid: false, confirmed: false, signature, error: "Invalid asset configuration" };
386
- }
387
- if (expectedAmount <= 0n) {
388
- return { valid: false, confirmed: false, signature, error: "Invalid expected amount" };
389
- }
390
- const effectiveMaxAge = Math.min(Math.max(maxAgeSeconds, 60), 3600);
391
- const connection = getConnection(clientConfig);
392
- try {
393
- const transaction = await connection.getParsedTransaction(signature, {
394
- commitment: "confirmed",
395
- maxSupportedTransactionVersion: 0
396
- });
397
- if (!transaction) {
398
- return { valid: false, confirmed: false, signature, error: "Transaction not found" };
399
- }
400
- if (transaction.meta?.err) {
401
- return { valid: false, confirmed: true, signature, error: "Transaction failed on-chain" };
402
- }
403
- if (transaction.blockTime) {
404
- const now = Math.floor(Date.now() / 1e3);
405
- if (now - transaction.blockTime > effectiveMaxAge) {
406
- return { valid: false, confirmed: true, signature, error: "Transaction too old" };
407
- }
408
- if (transaction.blockTime > now + 60) {
409
- return { valid: false, confirmed: true, signature, error: "Invalid transaction time" };
410
- }
411
- }
412
- const transfer = parseSPLTransfer(transaction, expectedRecipient, mintAddress);
413
- if (!transfer) {
414
- return {
415
- valid: false,
416
- confirmed: true,
417
- signature,
418
- error: "No valid token transfer to recipient found"
419
- };
420
- }
421
- if (transfer.to) {
422
- try {
423
- const destinationInfo = await connection.getParsedAccountInfo(new PublicKey(transfer.to));
424
- const owner = destinationInfo.value?.data?.parsed?.info?.owner;
425
- if (owner && owner !== expectedRecipient) {
426
- return {
427
- valid: false,
428
- confirmed: true,
429
- signature,
430
- error: "Recipient mismatch: Token account not owned by merchant"
431
- };
432
- }
433
- } catch (e) {
434
- return {
435
- valid: false,
436
- confirmed: true,
437
- signature,
438
- error: "Could not verify token account owner"
439
- };
440
- }
441
- }
442
- if (transfer.mint !== mintAddress) {
443
- return {
444
- valid: false,
445
- confirmed: true,
446
- signature,
447
- error: "Token mint mismatch"
448
- };
449
- }
450
- if (transfer.amount < expectedAmount) {
451
- return {
452
- valid: false,
453
- confirmed: true,
454
- signature,
455
- from: transfer.from,
456
- to: transfer.to,
457
- mint: transfer.mint,
458
- amount: transfer.amount,
459
- error: "Insufficient payment amount"
460
- };
461
- }
462
- return {
463
- valid: true,
464
- confirmed: true,
465
- signature,
466
- from: transfer.from,
467
- to: transfer.to,
468
- mint: transfer.mint,
469
- amount: transfer.amount,
470
- blockTime: transaction.blockTime ?? void 0,
471
- slot: transaction.slot
472
- };
473
- } catch {
474
- return { valid: false, confirmed: false, signature, error: "Verification failed" };
96
+ function createPaymentReference() {
97
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
98
+ return crypto.randomUUID();
475
99
  }
476
- }
477
- function isNativeAsset(asset) {
478
- return asset === "native";
100
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
479
101
  }
480
102
  var DEFAULT_COMPUTE_UNITS = 2e5;
481
103
  var DEFAULT_MICRO_LAMPORTS = 1e3;
@@ -491,33 +113,11 @@ function createPriorityFeeInstructions(config2 = {}) {
491
113
  instructions.push(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: price }));
492
114
  return instructions;
493
115
  }
494
- async function estimatePriorityFee(connection, accounts = []) {
495
- try {
496
- const fees = await connection.getRecentPrioritizationFees({
497
- lockedWritableAccounts: accounts
498
- });
499
- if (fees.length === 0) {
500
- return DEFAULT_MICRO_LAMPORTS;
501
- }
502
- const sortedFees = fees.map((f) => f.prioritizationFee).filter((f) => f > 0).sort((a, b) => a - b);
503
- if (sortedFees.length === 0) {
504
- return DEFAULT_MICRO_LAMPORTS;
505
- }
506
- const medianIndex = Math.floor(sortedFees.length / 2);
507
- return sortedFees[medianIndex];
508
- } catch {
509
- return DEFAULT_MICRO_LAMPORTS;
510
- }
511
- }
512
- function calculatePriorityFeeCost(microLamportsPerCU, computeUnits) {
513
- return Math.ceil(microLamportsPerCU * computeUnits / 1e6);
514
- }
515
116
  async function buildVersionedTransaction(config2) {
516
117
  const {
517
118
  connection,
518
119
  payer,
519
120
  instructions,
520
- lookupTables = [],
521
121
  priorityFee,
522
122
  recentBlockhash
523
123
  } = config2;
@@ -538,7 +138,7 @@ async function buildVersionedTransaction(config2) {
538
138
  payerKey: payer,
539
139
  recentBlockhash: blockhash,
540
140
  instructions: allInstructions
541
- }).compileToV0Message(lookupTables);
141
+ }).compileToV0Message([]);
542
142
  const transaction = new VersionedTransaction(message);
543
143
  return {
544
144
  transaction,
@@ -546,20 +146,129 @@ async function buildVersionedTransaction(config2) {
546
146
  lastValidBlockHeight
547
147
  };
548
148
  }
549
- async function fetchLookupTables(connection, addresses) {
550
- const tables = [];
551
- for (const address of addresses) {
552
- const result = await connection.getAddressLookupTable(address);
553
- if (result.value) {
554
- tables.push(result.value);
149
+
150
+ // src/agent/agentPayment.ts
151
+ var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
152
+ function isValidWalletAddress(address) {
153
+ if (!address || typeof address !== "string") return false;
154
+ return WALLET_REGEX.test(address);
155
+ }
156
+ async function executeAgentPayment(params) {
157
+ const {
158
+ connection,
159
+ agentKeypair,
160
+ recipientAddress,
161
+ amountLamports,
162
+ priorityFee,
163
+ confirmationTimeout = 6e4
164
+ } = params;
165
+ if (!isValidWalletAddress(recipientAddress)) {
166
+ return {
167
+ success: false,
168
+ error: "Invalid recipient address format"
169
+ };
170
+ }
171
+ if (amountLamports <= 0n) {
172
+ return {
173
+ success: false,
174
+ error: "Amount must be greater than 0"
175
+ };
176
+ }
177
+ try {
178
+ const recipientPubkey = new PublicKey(recipientAddress);
179
+ const transferInstruction = SystemProgram.transfer({
180
+ fromPubkey: agentKeypair.publicKey,
181
+ toPubkey: recipientPubkey,
182
+ lamports: amountLamports
183
+ });
184
+ const { transaction, lastValidBlockHeight } = await buildVersionedTransaction({
185
+ connection,
186
+ payer: agentKeypair.publicKey,
187
+ instructions: [transferInstruction],
188
+ priorityFee
189
+ });
190
+ transaction.sign([agentKeypair]);
191
+ const signature = await connection.sendTransaction(transaction, {
192
+ maxRetries: 3,
193
+ skipPreflight: false
194
+ });
195
+ const confirmationPromise = connection.confirmTransaction(
196
+ {
197
+ signature,
198
+ lastValidBlockHeight,
199
+ blockhash: transaction.message.recentBlockhash
200
+ },
201
+ "confirmed"
202
+ );
203
+ const timeoutPromise = new Promise((_, reject) => {
204
+ setTimeout(() => reject(new Error("Confirmation timeout")), confirmationTimeout);
205
+ });
206
+ const confirmation = await Promise.race([confirmationPromise, timeoutPromise]);
207
+ if (confirmation.value.err) {
208
+ return {
209
+ success: false,
210
+ signature,
211
+ error: "Transaction failed on-chain"
212
+ };
213
+ }
214
+ const txDetails = await connection.getTransaction(signature, {
215
+ commitment: "confirmed",
216
+ maxSupportedTransactionVersion: 0
217
+ });
218
+ return {
219
+ success: true,
220
+ signature,
221
+ confirmedAt: txDetails?.blockTime ?? Math.floor(Date.now() / 1e3),
222
+ slot: txDetails?.slot ?? confirmation.context.slot,
223
+ amountLamports,
224
+ amountSol: Number(amountLamports) / LAMPORTS_PER_SOL
225
+ };
226
+ } catch (error) {
227
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
228
+ return {
229
+ success: false,
230
+ error: errorMessage
231
+ };
232
+ }
233
+ }
234
+ async function getAgentBalance(connection, agentKeypair) {
235
+ const balance = await connection.getBalance(agentKeypair.publicKey);
236
+ return {
237
+ balance: BigInt(balance),
238
+ balanceSol: balance / LAMPORTS_PER_SOL
239
+ };
240
+ }
241
+ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports) {
242
+ const { balance } = await getAgentBalance(connection, agentKeypair);
243
+ const totalRequired = requiredLamports + 10000n;
244
+ return {
245
+ sufficient: balance >= totalRequired,
246
+ balance,
247
+ required: totalRequired
248
+ };
249
+ }
250
+ function keypairFromBase58(base58Secret) {
251
+ const bytes = Buffer.from(base58Secret, "base64");
252
+ if (bytes.length !== 64) {
253
+ const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
254
+ if (parts.length === 64) {
255
+ return Keypair.fromSecretKey(Uint8Array.from(parts));
555
256
  }
257
+ throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
556
258
  }
557
- return tables;
259
+ return Keypair.fromSecretKey(bytes);
558
260
  }
559
- function isVersionedTransaction(tx) {
560
- return tx instanceof VersionedTransaction;
261
+ function generateAgentKeypair() {
262
+ const keypair = Keypair.generate();
263
+ const secretBytes = Array.from(keypair.secretKey);
264
+ return {
265
+ keypair,
266
+ secretBase58: secretBytes.join(","),
267
+ // Comma-separated for easy storage
268
+ publicKey: keypair.publicKey.toBase58()
269
+ };
561
270
  }
562
- var MAX_ARTICLES_PER_SESSION = 100;
271
+ var MAX_CREDITS = 1e3;
563
272
  var MIN_SECRET_LENGTH = 32;
564
273
  function getSecretKey(secret) {
565
274
  if (!secret || typeof secret !== "string") {
@@ -575,659 +284,159 @@ function validateWalletAddress(address) {
575
284
  const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
576
285
  return base58Regex.test(address);
577
286
  }
578
- function validateArticleId(articleId) {
579
- if (!articleId || typeof articleId !== "string") return false;
580
- if (articleId.length > 128) return false;
581
- const safeIdRegex = /^[a-zA-Z0-9_-]+$/;
582
- return safeIdRegex.test(articleId);
583
- }
584
- async function createSession(walletAddress, articleId, config2, siteWide = false) {
287
+ async function createCreditSession(walletAddress, purchaseId, config2) {
585
288
  if (!validateWalletAddress(walletAddress)) {
586
289
  throw new Error("Invalid wallet address format");
587
290
  }
588
- if (!validateArticleId(articleId)) {
589
- throw new Error("Invalid article ID format");
291
+ if (config2.initialCredits <= 0 || config2.initialCredits > MAX_CREDITS) {
292
+ throw new Error(`Credits must be between 1 and ${MAX_CREDITS}`);
590
293
  }
591
- if (!config2.durationHours || config2.durationHours <= 0 || config2.durationHours > 720) {
592
- throw new Error("Session duration must be between 1 and 720 hours");
294
+ if (!config2.durationHours || config2.durationHours <= 0 || config2.durationHours > 8760) {
295
+ throw new Error("Session duration must be between 1 and 8760 hours (1 year)");
593
296
  }
594
297
  const sessionId = v4();
595
298
  const now = Math.floor(Date.now() / 1e3);
596
299
  const expiresAt = now + config2.durationHours * 3600;
300
+ const bundleExpiry = config2.bundleExpiryHours ? now + config2.bundleExpiryHours * 3600 : expiresAt;
597
301
  const session = {
598
302
  id: sessionId,
599
303
  walletAddress,
600
- unlockedArticles: [articleId],
601
- siteWideUnlock: Boolean(siteWide),
304
+ unlockedArticles: [purchaseId],
305
+ siteWideUnlock: false,
602
306
  createdAt: now,
603
- expiresAt
307
+ expiresAt,
308
+ credits: config2.initialCredits,
309
+ bundleExpiry,
310
+ bundleType: config2.bundleType
604
311
  };
605
312
  const payload = {
606
313
  sub: walletAddress,
607
314
  sid: sessionId,
608
315
  articles: session.unlockedArticles,
609
- siteWide: session.siteWideUnlock,
316
+ siteWide: false,
317
+ credits: config2.initialCredits,
318
+ bundleExpiry,
319
+ bundleType: config2.bundleType,
610
320
  iat: now,
611
321
  exp: expiresAt
612
322
  };
613
323
  const token = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(`${config2.durationHours}h`).sign(getSecretKey(config2.secret));
614
324
  return { token, session };
615
325
  }
616
- async function validateSession(token, secret) {
326
+ async function validateCreditSession(token, secret) {
617
327
  if (!token || typeof token !== "string") {
618
328
  return { valid: false, reason: "Invalid token format" };
619
329
  }
620
330
  try {
621
331
  const { payload } = await jwtVerify(token, getSecretKey(secret));
622
- const sessionPayload = payload;
623
- if (!sessionPayload.sub || !sessionPayload.sid || !sessionPayload.exp) {
332
+ const creditPayload = payload;
333
+ if (!creditPayload.sub || !creditPayload.sid || !creditPayload.exp) {
624
334
  return { valid: false, reason: "Malformed session payload" };
625
335
  }
626
336
  const now = Math.floor(Date.now() / 1e3);
627
- if (sessionPayload.exp < now) {
337
+ if (creditPayload.exp < now) {
628
338
  return { valid: false, reason: "Session expired" };
629
339
  }
630
- if (!validateWalletAddress(sessionPayload.sub)) {
340
+ if (creditPayload.bundleExpiry && creditPayload.bundleExpiry < now) {
341
+ return { valid: false, reason: "Bundle expired" };
342
+ }
343
+ if (!validateWalletAddress(creditPayload.sub)) {
631
344
  return { valid: false, reason: "Invalid session data" };
632
345
  }
633
346
  const session = {
634
- id: sessionPayload.sid,
635
- walletAddress: sessionPayload.sub,
636
- unlockedArticles: Array.isArray(sessionPayload.articles) ? sessionPayload.articles : [],
637
- siteWideUnlock: Boolean(sessionPayload.siteWide),
638
- createdAt: sessionPayload.iat ?? 0,
639
- expiresAt: sessionPayload.exp
347
+ id: creditPayload.sid,
348
+ walletAddress: creditPayload.sub,
349
+ unlockedArticles: Array.isArray(creditPayload.articles) ? creditPayload.articles : [],
350
+ siteWideUnlock: Boolean(creditPayload.siteWide),
351
+ createdAt: creditPayload.iat ?? 0,
352
+ expiresAt: creditPayload.exp,
353
+ credits: creditPayload.credits ?? 0,
354
+ bundleExpiry: creditPayload.bundleExpiry,
355
+ bundleType: creditPayload.bundleType
640
356
  };
641
357
  return { valid: true, session };
642
- } catch (error) {
358
+ } catch {
643
359
  return { valid: false, reason: "Invalid session" };
644
360
  }
645
361
  }
646
- async function addArticleToSession(token, articleId, secret) {
647
- if (!validateArticleId(articleId)) {
648
- return null;
362
+ async function useCredit(token, secret, creditsToUse = 1) {
363
+ if (creditsToUse <= 0) {
364
+ return { success: false, remainingCredits: 0, error: "Invalid credit amount" };
649
365
  }
650
- const validation = await validateSession(token, secret);
366
+ const validation = await validateCreditSession(token, secret);
651
367
  if (!validation.valid || !validation.session) {
652
- return null;
368
+ return {
369
+ success: false,
370
+ remainingCredits: 0,
371
+ error: validation.reason || "Invalid session"
372
+ };
653
373
  }
654
374
  const session = validation.session;
655
- if (session.unlockedArticles.includes(articleId)) {
656
- return { token, session };
657
- }
658
- if (session.unlockedArticles.length >= MAX_ARTICLES_PER_SESSION) {
659
- return null;
375
+ if (session.credits < creditsToUse) {
376
+ return {
377
+ success: false,
378
+ remainingCredits: session.credits,
379
+ error: "Insufficient credits"
380
+ };
660
381
  }
661
- const updatedArticles = [...session.unlockedArticles, articleId];
382
+ const newCredits = session.credits - creditsToUse;
662
383
  const payload = {
663
384
  sub: session.walletAddress,
664
385
  sid: session.id,
665
- articles: updatedArticles,
386
+ articles: session.unlockedArticles,
666
387
  siteWide: session.siteWideUnlock,
388
+ credits: newCredits,
389
+ bundleExpiry: session.bundleExpiry,
390
+ bundleType: session.bundleType,
667
391
  iat: session.createdAt,
668
392
  exp: session.expiresAt
669
393
  };
670
394
  const newToken = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).sign(getSecretKey(secret));
671
395
  return {
672
- token: newToken,
673
- session: { ...session, unlockedArticles: updatedArticles }
396
+ success: true,
397
+ remainingCredits: newCredits,
398
+ newToken
674
399
  };
675
400
  }
676
- async function isArticleUnlocked(token, articleId, secret) {
677
- if (!validateArticleId(articleId)) {
678
- return false;
401
+ async function addCredits(token, secret, creditsToAdd) {
402
+ if (creditsToAdd <= 0 || creditsToAdd > MAX_CREDITS) {
403
+ return { success: false, error: "Invalid credit amount" };
679
404
  }
680
- const validation = await validateSession(token, secret);
405
+ const validation = await validateCreditSession(token, secret);
681
406
  if (!validation.valid || !validation.session) {
682
- return false;
683
- }
684
- if (validation.session.siteWideUnlock) {
685
- return true;
686
- }
687
- return validation.session.unlockedArticles.includes(articleId);
688
- }
689
-
690
- // src/x402/config.ts
691
- var WALLET_REGEX3 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
692
- function sanitizeDisplayString(str, maxLength = 200) {
693
- if (!str || typeof str !== "string") return "";
694
- return str.slice(0, maxLength).replace(/[<>"'&]/g, "");
695
- }
696
- function isValidUrl(url) {
697
- try {
698
- const parsed = new URL(url);
699
- return parsed.protocol === "http:" || parsed.protocol === "https:";
700
- } catch {
701
- return false;
702
- }
703
- }
704
- function buildPaymentRequirement(params) {
705
- if (!WALLET_REGEX3.test(params.creatorWallet)) {
706
- throw new Error("Invalid creator wallet address");
707
- }
708
- if (params.priceInLamports <= 0n) {
709
- throw new Error("Price must be positive");
710
- }
711
- if (!isValidUrl(params.resourceUrl)) {
712
- throw new Error("Invalid resource URL");
713
- }
714
- if (params.network !== "devnet" && params.network !== "mainnet-beta") {
715
- throw new Error("Invalid network");
716
- }
717
- const timeout = params.maxTimeoutSeconds ?? 300;
718
- if (timeout < 60 || timeout > 3600) {
719
- throw new Error("Timeout must be between 60 and 3600 seconds");
720
- }
721
- const x402Network = toX402Network(params.network);
722
- const safeTitle = sanitizeDisplayString(params.articleTitle, 200);
723
- const safeArticleId = sanitizeDisplayString(params.articleId, 128);
724
- return {
725
- scheme: "exact",
726
- network: x402Network,
727
- maxAmountRequired: params.priceInLamports.toString(),
728
- resource: params.resourceUrl,
729
- description: `Unlock: ${safeTitle}`,
730
- mimeType: "text/html",
731
- payTo: params.creatorWallet,
732
- maxTimeoutSeconds: timeout,
733
- asset: "native",
734
- extra: {
735
- name: safeTitle,
736
- articleId: safeArticleId
737
- }
738
- };
739
- }
740
- function encodePaymentRequired(requirement) {
741
- return Buffer.from(JSON.stringify(requirement)).toString("base64");
742
- }
743
- function decodePaymentRequired(encoded) {
744
- if (!encoded || typeof encoded !== "string") {
745
- throw new Error("Invalid encoded requirement");
746
- }
747
- if (encoded.length > 1e4) {
748
- throw new Error("Encoded requirement too large");
749
- }
750
- try {
751
- const decoded = Buffer.from(encoded, "base64").toString("utf-8");
752
- return JSON.parse(decoded);
753
- } catch {
754
- throw new Error("Failed to decode payment requirement");
755
- }
756
- }
757
- var X402_HEADERS = {
758
- PAYMENT_REQUIRED: "X-Payment-Required",
759
- PAYMENT: "X-Payment",
760
- PAYMENT_RESPONSE: "X-Payment-Response"
761
- };
762
- function create402ResponseBody(requirement) {
763
- const assetStr = typeof requirement.asset === "string" ? requirement.asset : requirement.asset.mint;
764
- return {
765
- error: "Payment Required",
766
- message: requirement.description,
767
- price: {
768
- amount: requirement.maxAmountRequired,
769
- asset: assetStr,
770
- network: requirement.network
771
- }
772
- };
773
- }
774
- function create402Headers(requirement) {
775
- const encoded = encodePaymentRequired(requirement);
776
- return {
777
- "Content-Type": "application/json",
778
- [X402_HEADERS.PAYMENT_REQUIRED]: encoded,
779
- "Access-Control-Expose-Headers": X402_HEADERS.PAYMENT_REQUIRED
780
- };
781
- }
782
-
783
- // src/x402/verification.ts
784
- var SIGNATURE_REGEX3 = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;
785
- async function verifyX402Payment(payload, requirement, clientConfig) {
786
- if (!payload || typeof payload !== "object") {
787
- return { valid: false, invalidReason: "Invalid payload" };
788
- }
789
- const signature = payload.payload?.signature;
790
- if (!signature || typeof signature !== "string") {
791
- return { valid: false, invalidReason: "Missing transaction signature" };
792
- }
793
- if (!SIGNATURE_REGEX3.test(signature)) {
794
- return { valid: false, invalidReason: "Invalid signature format" };
795
- }
796
- if (payload.x402Version !== 1) {
797
- return { valid: false, invalidReason: "Unsupported x402 version" };
407
+ return { success: false, error: validation.reason || "Invalid session" };
798
408
  }
799
- if (payload.scheme !== "exact") {
800
- return { valid: false, invalidReason: "Unsupported payment scheme" };
801
- }
802
- if (payload.network !== requirement.network) {
803
- return {
804
- valid: false,
805
- invalidReason: `Network mismatch: expected ${requirement.network}`
806
- };
807
- }
808
- const walletRegex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
809
- if (!walletRegex.test(requirement.payTo)) {
810
- return { valid: false, invalidReason: "Invalid recipient configuration" };
811
- }
812
- let expectedAmount;
813
- try {
814
- expectedAmount = BigInt(requirement.maxAmountRequired);
815
- if (expectedAmount <= 0n) {
816
- return { valid: false, invalidReason: "Invalid payment amount" };
817
- }
818
- } catch {
819
- return { valid: false, invalidReason: "Invalid payment amount format" };
820
- }
821
- const verification = await verifyPayment({
822
- signature,
823
- expectedRecipient: requirement.payTo,
824
- expectedAmount,
825
- maxAgeSeconds: requirement.maxTimeoutSeconds,
826
- clientConfig
827
- });
828
- if (!verification.valid) {
829
- return {
830
- valid: false,
831
- invalidReason: verification.error || "Transaction verification failed"
832
- };
833
- }
834
- return {
835
- valid: true,
836
- settled: verification.confirmed,
837
- from: verification.from,
838
- transaction: {
839
- signature: verification.signature,
840
- blockTime: verification.blockTime,
841
- slot: verification.slot
842
- }
843
- };
844
- }
845
- function parsePaymentHeader(header) {
846
- if (!header || typeof header !== "string") {
847
- return null;
848
- }
849
- if (header.length > 1e4) {
850
- return null;
851
- }
852
- try {
853
- const decoded = Buffer.from(header, "base64").toString("utf-8");
854
- const parsed = JSON.parse(decoded);
855
- if (!parsed || typeof parsed !== "object") {
856
- return null;
857
- }
858
- return parsed;
859
- } catch {
860
- return null;
861
- }
862
- }
863
- function encodePaymentRequirement(requirement) {
864
- return Buffer.from(JSON.stringify(requirement)).toString("base64");
865
- }
866
- function encodePaymentResponse(response) {
867
- return Buffer.from(JSON.stringify(response)).toString("base64");
868
- }
869
- function create402Response(requirement, body) {
870
- const headers = new Headers({
871
- "Content-Type": "application/json",
872
- "X-Payment-Required": encodePaymentRequirement(requirement)
873
- });
874
- const responseBody = body || {
875
- error: "Payment Required",
876
- message: "This resource requires payment to access",
877
- x402Version: 1,
878
- accepts: [requirement]
879
- };
880
- return new Response(JSON.stringify(responseBody), {
881
- status: 402,
882
- headers
883
- });
884
- }
885
-
886
- // src/store/memory.ts
887
- function createMemoryStore(options = {}) {
888
- const { cleanupInterval = 6e4 } = options;
889
- const store = /* @__PURE__ */ new Map();
890
- const cleanupTimer = setInterval(() => {
891
- const now = Date.now();
892
- for (const [key, record] of store.entries()) {
893
- if (record.expiresAt < now) {
894
- store.delete(key);
895
- }
896
- }
897
- }, cleanupInterval);
898
- return {
899
- async hasBeenUsed(signature) {
900
- const record = store.get(signature);
901
- if (!record) return false;
902
- if (record.expiresAt < Date.now()) {
903
- store.delete(signature);
904
- return false;
905
- }
906
- return true;
907
- },
908
- async markAsUsed(signature, resourceId, expiresAt) {
909
- store.set(signature, {
910
- resourceId,
911
- usedAt: Date.now(),
912
- expiresAt: expiresAt.getTime()
913
- });
914
- },
915
- async getUsage(signature) {
916
- const record = store.get(signature);
917
- if (!record) return null;
918
- if (record.expiresAt < Date.now()) {
919
- store.delete(signature);
920
- return null;
921
- }
922
- return {
923
- signature,
924
- resourceId: record.resourceId,
925
- usedAt: new Date(record.usedAt),
926
- expiresAt: new Date(record.expiresAt),
927
- walletAddress: record.walletAddress
928
- };
929
- },
930
- /** Stop cleanup timer (for graceful shutdown) */
931
- close() {
932
- clearInterval(cleanupTimer);
933
- store.clear();
934
- }
409
+ const session = validation.session;
410
+ const newCredits = Math.min(session.credits + creditsToAdd, MAX_CREDITS);
411
+ const payload = {
412
+ sub: session.walletAddress,
413
+ sid: session.id,
414
+ articles: session.unlockedArticles,
415
+ siteWide: session.siteWideUnlock,
416
+ credits: newCredits,
417
+ bundleExpiry: session.bundleExpiry,
418
+ bundleType: session.bundleType,
419
+ iat: session.createdAt,
420
+ exp: session.expiresAt
935
421
  };
936
- }
937
-
938
- // src/store/redis.ts
939
- function createRedisStore(options) {
940
- const { client, keyPrefix = "micropay:sig:" } = options;
941
- const buildKey = (signature) => `${keyPrefix}${signature}`;
422
+ const newToken = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).sign(getSecretKey(secret));
942
423
  return {
943
- async hasBeenUsed(signature) {
944
- const exists = await client.exists(buildKey(signature));
945
- return exists > 0;
946
- },
947
- async markAsUsed(signature, resourceId, expiresAt) {
948
- const key = buildKey(signature);
949
- const ttl = Math.max(1, Math.floor((expiresAt.getTime() - Date.now()) / 1e3));
950
- const record = {
951
- signature,
952
- resourceId,
953
- usedAt: /* @__PURE__ */ new Date(),
954
- expiresAt
955
- };
956
- if (client.setex) {
957
- await client.setex(key, ttl, JSON.stringify(record));
958
- } else {
959
- await client.set(key, JSON.stringify(record), { EX: ttl });
960
- }
961
- },
962
- async getUsage(signature) {
963
- const data = await client.get(buildKey(signature));
964
- if (!data) return null;
965
- try {
966
- const record = JSON.parse(data);
967
- return {
968
- ...record,
969
- usedAt: new Date(record.usedAt),
970
- expiresAt: new Date(record.expiresAt)
971
- };
972
- } catch {
973
- return null;
974
- }
975
- }
424
+ success: true,
425
+ newToken,
426
+ totalCredits: newCredits
976
427
  };
977
428
  }
978
-
979
- // src/middleware/nextjs.ts
980
- function matchesProtectedPath(path, patterns) {
981
- for (const pattern of patterns) {
982
- const regexPattern = pattern.replace(/\*\*/g, "{{DOUBLE_STAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLE_STAR}}/g, ".*");
983
- const regex = new RegExp(`^${regexPattern}$`);
984
- if (regex.test(path)) {
985
- return true;
986
- }
987
- }
988
- return false;
989
- }
990
- async function checkPaywallAccess(path, sessionToken, config2) {
991
- if (!matchesProtectedPath(path, config2.protectedPaths)) {
992
- return { allowed: true };
993
- }
994
- if (!sessionToken) {
995
- return {
996
- allowed: false,
997
- reason: "No session token",
998
- requiresPayment: true
999
- };
1000
- }
1001
- const validation = await validateSession(sessionToken, config2.sessionSecret);
429
+ async function getRemainingCredits(token, secret) {
430
+ const validation = await validateCreditSession(token, secret);
1002
431
  if (!validation.valid || !validation.session) {
1003
- return {
1004
- allowed: false,
1005
- reason: validation.reason || "Invalid session",
1006
- requiresPayment: true
1007
- };
1008
- }
1009
- return {
1010
- allowed: true,
1011
- session: validation.session
1012
- };
1013
- }
1014
- function createPaywallMiddleware(config2) {
1015
- const { cookieName = "x402_session" } = config2;
1016
- return async function middleware(request) {
1017
- const url = new URL(request.url);
1018
- const path = url.pathname;
1019
- const cookieHeader = request.headers.get("cookie") || "";
1020
- const cookies = Object.fromEntries(
1021
- cookieHeader.split(";").map((c) => {
1022
- const [key, ...vals] = c.trim().split("=");
1023
- return [key, vals.join("=")];
1024
- })
1025
- );
1026
- const sessionToken = cookies[cookieName];
1027
- const result = await checkPaywallAccess(path, sessionToken, config2);
1028
- if (!result.allowed && result.requiresPayment) {
1029
- const headers = {
1030
- "Content-Type": "application/json"
1031
- };
1032
- if (config2.paymentRequirement) {
1033
- const requirement = typeof config2.paymentRequirement === "function" ? config2.paymentRequirement(path) : config2.paymentRequirement;
1034
- headers["X-Payment-Required"] = encodePaymentRequirement(requirement);
1035
- }
1036
- const body = config2.custom402Response ? config2.custom402Response(path) : {
1037
- error: "Payment Required",
1038
- message: "This resource requires payment to access",
1039
- x402Version: 1,
1040
- path
1041
- };
1042
- return new Response(JSON.stringify(body), {
1043
- status: 402,
1044
- headers
1045
- });
1046
- }
1047
- return null;
1048
- };
1049
- }
1050
- function withPaywall(handler, options) {
1051
- const { sessionSecret, cookieName = "x402_session", articleId } = options;
1052
- return async function protectedHandler(request) {
1053
- const cookieHeader = request.headers.get("cookie") || "";
1054
- const cookies = Object.fromEntries(
1055
- cookieHeader.split(";").map((c) => {
1056
- const [key, ...vals] = c.trim().split("=");
1057
- return [key, vals.join("=")];
1058
- })
1059
- );
1060
- const sessionToken = cookies[cookieName];
1061
- if (!sessionToken) {
1062
- return new Response(
1063
- JSON.stringify({ error: "Payment Required", message: "No session token" }),
1064
- { status: 402, headers: { "Content-Type": "application/json" } }
1065
- );
1066
- }
1067
- const validation = await validateSession(sessionToken, sessionSecret);
1068
- if (!validation.valid || !validation.session) {
1069
- return new Response(
1070
- JSON.stringify({ error: "Payment Required", message: validation.reason }),
1071
- { status: 402, headers: { "Content-Type": "application/json" } }
1072
- );
1073
- }
1074
- if (articleId) {
1075
- const { session } = validation;
1076
- const hasAccess = session.siteWideUnlock || session.unlockedArticles.includes(articleId);
1077
- if (!hasAccess) {
1078
- return new Response(
1079
- JSON.stringify({ error: "Payment Required", message: "Article not unlocked" }),
1080
- { status: 402, headers: { "Content-Type": "application/json" } }
1081
- );
1082
- }
1083
- }
1084
- return handler(request, validation.session);
1085
- };
1086
- }
1087
-
1088
- // src/utils/retry.ts
1089
- function sleep(ms) {
1090
- return new Promise((resolve) => setTimeout(resolve, ms));
1091
- }
1092
- function calculateDelay(attempt, options) {
1093
- const { baseDelay, maxDelay, jitter } = options;
1094
- let delay = baseDelay * Math.pow(2, attempt);
1095
- delay = Math.min(delay, maxDelay);
1096
- if (jitter) {
1097
- const jitterAmount = delay * 0.25;
1098
- delay += Math.random() * jitterAmount * 2 - jitterAmount;
1099
- }
1100
- return Math.floor(delay);
1101
- }
1102
- async function withRetry(fn, options = {}) {
1103
- const {
1104
- maxAttempts = 3,
1105
- baseDelay = 500,
1106
- maxDelay = 1e4,
1107
- jitter = true,
1108
- retryOn = () => true
1109
- } = options;
1110
- let lastError;
1111
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1112
- try {
1113
- return await fn();
1114
- } catch (error) {
1115
- lastError = error;
1116
- if (!retryOn(error)) {
1117
- throw error;
1118
- }
1119
- if (attempt < maxAttempts - 1) {
1120
- const delay = calculateDelay(attempt, {
1121
- baseDelay,
1122
- maxDelay,
1123
- jitter
1124
- });
1125
- await sleep(delay);
1126
- }
1127
- }
1128
- }
1129
- throw lastError;
1130
- }
1131
- function isRetryableRPCError(error) {
1132
- if (error instanceof Error) {
1133
- const message = error.message.toLowerCase();
1134
- if (message.includes("429") || message.includes("rate limit")) {
1135
- return true;
1136
- }
1137
- if (message.includes("timeout") || message.includes("econnreset")) {
1138
- return true;
1139
- }
1140
- if (message.includes("503") || message.includes("502") || message.includes("500")) {
1141
- return true;
1142
- }
1143
- if (message.includes("blockhash not found") || message.includes("slot skipped")) {
1144
- return true;
1145
- }
1146
- }
1147
- return false;
1148
- }
1149
-
1150
- // src/client/payment.ts
1151
- function buildSolanaPayUrl(params) {
1152
- const { recipient, amount, splToken, reference, label, message } = params;
1153
- const url = new URL(`solana:${recipient}`);
1154
- if (amount !== void 0) {
1155
- url.searchParams.set("amount", amount.toString());
1156
- }
1157
- if (splToken) {
1158
- url.searchParams.set("spl-token", splToken);
1159
- }
1160
- if (reference) {
1161
- url.searchParams.set("reference", reference);
1162
- }
1163
- if (label) {
1164
- url.searchParams.set("label", label);
1165
- }
1166
- if (message) {
1167
- url.searchParams.set("message", message);
432
+ return { credits: 0, valid: false };
1168
433
  }
1169
- return url.toString();
1170
- }
1171
- function createPaymentFlow(config2) {
1172
- const { network, recipientWallet, amount, asset = "native", memo } = config2;
1173
- let decimals = 9;
1174
- let mintAddress;
1175
- if (asset === "usdc") {
1176
- decimals = 6;
1177
- mintAddress = network === "mainnet-beta" ? TOKEN_MINTS.USDC_MAINNET : TOKEN_MINTS.USDC_DEVNET;
1178
- } else if (asset === "usdt") {
1179
- decimals = 6;
1180
- mintAddress = TOKEN_MINTS.USDT_MAINNET;
1181
- } else if (typeof asset === "object" && "mint" in asset) {
1182
- decimals = asset.decimals ?? 6;
1183
- mintAddress = asset.mint;
1184
- }
1185
- const naturalAmount = Number(amount) / Math.pow(10, decimals);
1186
434
  return {
1187
- /** Get the payment configuration */
1188
- getConfig: () => ({ ...config2 }),
1189
- /** Get amount in natural display units (e.g., 0.01 SOL) */
1190
- getDisplayAmount: () => naturalAmount,
1191
- /** Get amount formatted with symbol */
1192
- getFormattedAmount: () => {
1193
- const symbol = asset === "native" ? "SOL" : asset === "usdc" ? "USDC" : asset === "usdt" ? "USDT" : "tokens";
1194
- return `${naturalAmount.toFixed(decimals > 6 ? 4 : 2)} ${symbol}`;
1195
- },
1196
- /** Generate Solana Pay URL for QR codes */
1197
- getSolanaPayUrl: (options = {}) => {
1198
- return buildSolanaPayUrl({
1199
- recipient: recipientWallet,
1200
- amount: naturalAmount,
1201
- splToken: mintAddress,
1202
- label: options.label,
1203
- reference: options.reference,
1204
- message: memo
1205
- });
1206
- },
1207
- /** Get the token mint address (undefined for native SOL) */
1208
- getMintAddress: () => mintAddress,
1209
- /** Check if this is a native SOL payment */
1210
- isNativePayment: () => asset === "native",
1211
- /** Get network information */
1212
- getNetworkInfo: () => ({
1213
- network,
1214
- isMainnet: network === "mainnet-beta",
1215
- explorerUrl: network === "mainnet-beta" ? "https://explorer.solana.com" : "https://explorer.solana.com?cluster=devnet"
1216
- }),
1217
- /** Build explorer URL for a transaction */
1218
- getExplorerUrl: (signature) => {
1219
- const baseUrl = "https://explorer.solana.com/tx";
1220
- const cluster = network === "mainnet-beta" ? "" : "?cluster=devnet";
1221
- return `${baseUrl}/${signature}${cluster}`;
1222
- }
435
+ credits: validation.session.credits,
436
+ valid: true,
437
+ bundleExpiry: validation.session.bundleExpiry
1223
438
  };
1224
439
  }
1225
- function createPaymentReference() {
1226
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
1227
- return crypto.randomUUID();
1228
- }
1229
- return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
1230
- }
1231
440
 
1232
441
  // src/pricing/index.ts
1233
442
  var cachedPrice = null;
@@ -1359,6 +568,6 @@ function getProviders() {
1359
568
  return PROVIDERS.map((p) => ({ name: p.name, url: p.url }));
1360
569
  }
1361
570
 
1362
- export { TOKEN_MINTS, X402_HEADERS, addArticleToSession, buildPaymentRequirement, buildSolanaPayUrl, buildVersionedTransaction, calculatePriorityFeeCost, checkPaywallAccess, clearPriceCache, configurePricing, create402Headers, create402Response, create402ResponseBody, createMemoryStore, createPaymentFlow, createPaymentReference, createPaywallMiddleware, createPriorityFeeInstructions, createRedisStore, createSession, decodePaymentRequired, encodePaymentRequired, encodePaymentRequirement, encodePaymentResponse, estimatePriorityFee, fetchLookupTables, formatPriceDisplay, formatPriceSync, getConnection, getConnectionWithFallback, getProviders, getSolPrice, getTokenDecimals, getWalletTransactions, isArticleUnlocked, isMainnet, isNativeAsset, isRetryableRPCError, isVersionedTransaction, lamportsToSol, lamportsToUsd, parsePaymentHeader, resetConnection, resolveMintAddress, solToLamports, toX402Network, usdToLamports, validateSession, verifyPayment, verifySPLPayment, verifyX402Payment, waitForConfirmation, withFallback, withPaywall, withRetry };
571
+ export { addCredits, buildSolanaPayUrl, clearPriceCache, configurePricing, createCreditSession, createPaymentFlow, createPaymentReference, executeAgentPayment, formatPriceDisplay, formatPriceSync, generateAgentKeypair, getAgentBalance, getProviders, getRemainingCredits, getSolPrice, hasAgentSufficientBalance, keypairFromBase58, lamportsToUsd, usdToLamports, useCredit, validateCreditSession };
1363
572
  //# sourceMappingURL=index.js.map
1364
573
  //# sourceMappingURL=index.js.map