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