@alleyboss/micropay-solana-x402-paywall 1.0.0 → 2.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 (66) hide show
  1. package/README.md +100 -167
  2. package/dist/client/index.cjs +99 -0
  3. package/dist/client/index.cjs.map +1 -0
  4. package/dist/client/index.d.cts +112 -0
  5. package/dist/client/index.d.ts +112 -0
  6. package/dist/client/index.js +95 -0
  7. package/dist/client/index.js.map +1 -0
  8. package/dist/client-CSZHI8o8.d.ts +32 -0
  9. package/dist/client-vRr48m2x.d.cts +32 -0
  10. package/dist/index.cjs +803 -41
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +11 -3
  13. package/dist/index.d.ts +11 -3
  14. package/dist/index.js +783 -42
  15. package/dist/index.js.map +1 -1
  16. package/dist/memory-Daxkczti.d.cts +29 -0
  17. package/dist/memory-Daxkczti.d.ts +29 -0
  18. package/dist/middleware/index.cjs +261 -0
  19. package/dist/middleware/index.cjs.map +1 -0
  20. package/dist/middleware/index.d.cts +90 -0
  21. package/dist/middleware/index.d.ts +90 -0
  22. package/dist/middleware/index.js +255 -0
  23. package/dist/middleware/index.js.map +1 -0
  24. package/dist/nextjs-BK0pVb9Y.d.ts +78 -0
  25. package/dist/nextjs-Bm272Jkj.d.cts +78 -0
  26. package/dist/{client-kfCr7G-P.d.cts → payment-CTxdtqmc.d.cts} +23 -34
  27. package/dist/{client-kfCr7G-P.d.ts → payment-CTxdtqmc.d.ts} +23 -34
  28. package/dist/pricing/index.cjs +79 -0
  29. package/dist/pricing/index.cjs.map +1 -0
  30. package/dist/pricing/index.d.cts +67 -0
  31. package/dist/pricing/index.d.ts +67 -0
  32. package/dist/pricing/index.js +72 -0
  33. package/dist/pricing/index.js.map +1 -0
  34. package/dist/session/index.cjs +51 -11
  35. package/dist/session/index.cjs.map +1 -1
  36. package/dist/session/index.d.cts +29 -1
  37. package/dist/session/index.d.ts +29 -1
  38. package/dist/session/index.js +51 -11
  39. package/dist/session/index.js.map +1 -1
  40. package/dist/{index-DptevtnU.d.cts → session-D2IoWAWV.d.cts} +1 -24
  41. package/dist/{index-DptevtnU.d.ts → session-D2IoWAWV.d.ts} +1 -24
  42. package/dist/solana/index.cjs +235 -15
  43. package/dist/solana/index.cjs.map +1 -1
  44. package/dist/solana/index.d.cts +61 -3
  45. package/dist/solana/index.d.ts +61 -3
  46. package/dist/solana/index.js +232 -16
  47. package/dist/solana/index.js.map +1 -1
  48. package/dist/store/index.cjs +99 -0
  49. package/dist/store/index.cjs.map +1 -0
  50. package/dist/store/index.d.cts +38 -0
  51. package/dist/store/index.d.ts +38 -0
  52. package/dist/store/index.js +96 -0
  53. package/dist/store/index.js.map +1 -0
  54. package/dist/utils/index.cjs +68 -0
  55. package/dist/utils/index.cjs.map +1 -0
  56. package/dist/utils/index.d.cts +30 -0
  57. package/dist/utils/index.d.ts +30 -0
  58. package/dist/utils/index.js +65 -0
  59. package/dist/utils/index.js.map +1 -0
  60. package/dist/x402/index.cjs +119 -18
  61. package/dist/x402/index.cjs.map +1 -1
  62. package/dist/x402/index.d.cts +6 -1
  63. package/dist/x402/index.d.ts +6 -1
  64. package/dist/x402/index.js +119 -18
  65. package/dist/x402/index.js.map +1 -1
  66. package/package.json +56 -3
@@ -2,13 +2,38 @@ import { SignJWT, jwtVerify } from 'jose';
2
2
  import { v4 } from 'uuid';
3
3
 
4
4
  // src/session/core.ts
5
+ var MAX_ARTICLES_PER_SESSION = 100;
6
+ var MIN_SECRET_LENGTH = 32;
5
7
  function getSecretKey(secret) {
6
- if (secret.length < 32) {
7
- throw new Error("Session secret must be at least 32 characters");
8
+ if (!secret || typeof secret !== "string") {
9
+ throw new Error("Session secret is required");
10
+ }
11
+ if (secret.length < MIN_SECRET_LENGTH) {
12
+ throw new Error(`Session secret must be at least ${MIN_SECRET_LENGTH} characters`);
8
13
  }
9
14
  return new TextEncoder().encode(secret);
10
15
  }
16
+ function validateWalletAddress(address) {
17
+ if (!address || typeof address !== "string") return false;
18
+ const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
19
+ return base58Regex.test(address);
20
+ }
21
+ function validateArticleId(articleId) {
22
+ if (!articleId || typeof articleId !== "string") return false;
23
+ if (articleId.length > 128) return false;
24
+ const safeIdRegex = /^[a-zA-Z0-9_-]+$/;
25
+ return safeIdRegex.test(articleId);
26
+ }
11
27
  async function createSession(walletAddress, articleId, config, siteWide = false) {
28
+ if (!validateWalletAddress(walletAddress)) {
29
+ throw new Error("Invalid wallet address format");
30
+ }
31
+ if (!validateArticleId(articleId)) {
32
+ throw new Error("Invalid article ID format");
33
+ }
34
+ if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 720) {
35
+ throw new Error("Session duration must be between 1 and 720 hours");
36
+ }
12
37
  const sessionId = v4();
13
38
  const now = Math.floor(Date.now() / 1e3);
14
39
  const expiresAt = now + config.durationHours * 3600;
@@ -16,7 +41,7 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
16
41
  id: sessionId,
17
42
  walletAddress,
18
43
  unlockedArticles: [articleId],
19
- siteWideUnlock: siteWide,
44
+ siteWideUnlock: Boolean(siteWide),
20
45
  createdAt: now,
21
46
  expiresAt
22
47
  };
@@ -24,7 +49,7 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
24
49
  sub: walletAddress,
25
50
  sid: sessionId,
26
51
  articles: session.unlockedArticles,
27
- siteWide,
52
+ siteWide: session.siteWideUnlock,
28
53
  iat: now,
29
54
  exp: expiresAt
30
55
  };
@@ -32,30 +57,39 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
32
57
  return { token, session };
33
58
  }
34
59
  async function validateSession(token, secret) {
60
+ if (!token || typeof token !== "string") {
61
+ return { valid: false, reason: "Invalid token format" };
62
+ }
35
63
  try {
36
64
  const { payload } = await jwtVerify(token, getSecretKey(secret));
37
65
  const sessionPayload = payload;
66
+ if (!sessionPayload.sub || !sessionPayload.sid || !sessionPayload.exp) {
67
+ return { valid: false, reason: "Malformed session payload" };
68
+ }
38
69
  const now = Math.floor(Date.now() / 1e3);
39
70
  if (sessionPayload.exp < now) {
40
71
  return { valid: false, reason: "Session expired" };
41
72
  }
73
+ if (!validateWalletAddress(sessionPayload.sub)) {
74
+ return { valid: false, reason: "Invalid session data" };
75
+ }
42
76
  const session = {
43
77
  id: sessionPayload.sid,
44
78
  walletAddress: sessionPayload.sub,
45
- unlockedArticles: sessionPayload.articles,
46
- siteWideUnlock: sessionPayload.siteWide,
47
- createdAt: sessionPayload.iat,
79
+ unlockedArticles: Array.isArray(sessionPayload.articles) ? sessionPayload.articles : [],
80
+ siteWideUnlock: Boolean(sessionPayload.siteWide),
81
+ createdAt: sessionPayload.iat ?? 0,
48
82
  expiresAt: sessionPayload.exp
49
83
  };
50
84
  return { valid: true, session };
51
85
  } catch (error) {
52
- return {
53
- valid: false,
54
- reason: error instanceof Error ? error.message : "Invalid session"
55
- };
86
+ return { valid: false, reason: "Invalid session" };
56
87
  }
57
88
  }
58
89
  async function addArticleToSession(token, articleId, secret) {
90
+ if (!validateArticleId(articleId)) {
91
+ return null;
92
+ }
59
93
  const validation = await validateSession(token, secret);
60
94
  if (!validation.valid || !validation.session) {
61
95
  return null;
@@ -64,6 +98,9 @@ async function addArticleToSession(token, articleId, secret) {
64
98
  if (session.unlockedArticles.includes(articleId)) {
65
99
  return { token, session };
66
100
  }
101
+ if (session.unlockedArticles.length >= MAX_ARTICLES_PER_SESSION) {
102
+ return null;
103
+ }
67
104
  const updatedArticles = [...session.unlockedArticles, articleId];
68
105
  const payload = {
69
106
  sub: session.walletAddress,
@@ -80,6 +117,9 @@ async function addArticleToSession(token, articleId, secret) {
80
117
  };
81
118
  }
82
119
  async function isArticleUnlocked(token, articleId, secret) {
120
+ if (!validateArticleId(articleId)) {
121
+ return false;
122
+ }
83
123
  const validation = await validateSession(token, secret);
84
124
  if (!validation.valid || !validation.session) {
85
125
  return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/session/core.ts"],"names":["uuidv4"],"mappings":"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","file":"index.js","sourcesContent":["// Session management with JWT (framework-agnostic core)\nimport { SignJWT, jwtVerify } from 'jose';\nimport { v4 as uuidv4 } from 'uuid';\nimport type { SessionData, SessionConfig, SessionValidation, SessionJWTPayload } from '../types';\n\n/**\n * Get the secret key for JWT signing\n */\nfunction getSecretKey(secret: string): Uint8Array {\n if (secret.length < 32) {\n throw new Error('Session secret must be at least 32 characters');\n }\n return new TextEncoder().encode(secret);\n}\n\n/**\n * Create a new session after successful payment\n */\nexport async function createSession(\n walletAddress: string,\n articleId: string,\n config: SessionConfig,\n siteWide: boolean = false\n): Promise<{ token: string; session: SessionData }> {\n const sessionId = uuidv4();\n const now = Math.floor(Date.now() / 1000);\n const expiresAt = now + (config.durationHours * 3600);\n\n const session: SessionData = {\n id: sessionId,\n walletAddress,\n unlockedArticles: [articleId],\n siteWideUnlock: siteWide,\n createdAt: now,\n expiresAt,\n };\n\n const payload: SessionJWTPayload = {\n sub: walletAddress,\n sid: sessionId,\n articles: session.unlockedArticles,\n siteWide,\n iat: now,\n exp: expiresAt,\n };\n\n const token = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(`${config.durationHours}h`)\n .sign(getSecretKey(config.secret));\n\n return { token, session };\n}\n\n/**\n * Validate an existing session token\n */\nexport async function validateSession(\n token: string,\n secret: string\n): Promise<SessionValidation> {\n try {\n const { payload } = await jwtVerify(token, getSecretKey(secret));\n const sessionPayload = payload as unknown as SessionJWTPayload;\n\n const now = Math.floor(Date.now() / 1000);\n if (sessionPayload.exp < now) {\n return { valid: false, reason: 'Session expired' };\n }\n\n const session: SessionData = {\n id: sessionPayload.sid,\n walletAddress: sessionPayload.sub,\n unlockedArticles: sessionPayload.articles,\n siteWideUnlock: sessionPayload.siteWide,\n createdAt: sessionPayload.iat,\n expiresAt: sessionPayload.exp,\n };\n\n return { valid: true, session };\n } catch (error) {\n return {\n valid: false,\n reason: error instanceof Error ? error.message : 'Invalid session',\n };\n }\n}\n\n/**\n * Add an article to an existing session\n */\nexport async function addArticleToSession(\n token: string,\n articleId: string,\n secret: string\n): Promise<{ token: string; session: SessionData } | null> {\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return null;\n }\n\n const session = validation.session;\n\n // Already unlocked\n if (session.unlockedArticles.includes(articleId)) {\n return { token, session };\n }\n\n const updatedArticles = [...session.unlockedArticles, articleId];\n\n const payload: SessionJWTPayload = {\n sub: session.walletAddress,\n sid: session.id,\n articles: updatedArticles,\n siteWide: session.siteWideUnlock,\n iat: session.createdAt,\n exp: session.expiresAt,\n };\n\n const newToken = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(getSecretKey(secret));\n\n return {\n token: newToken,\n session: { ...session, unlockedArticles: updatedArticles },\n };\n}\n\n/**\n * Check if an article is unlocked for a session\n */\nexport async function isArticleUnlocked(\n token: string,\n articleId: string,\n secret: string\n): Promise<boolean> {\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return false;\n }\n\n if (validation.session.siteWideUnlock) {\n return true;\n }\n\n return validation.session.unlockedArticles.includes(articleId);\n}\n"]}
1
+ {"version":3,"sources":["../../src/session/core.ts"],"names":["uuidv4"],"mappings":"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","file":"index.js","sourcesContent":["// Session management with JWT (framework-agnostic core)\n// SECURITY: Uses jose library with HS256, constant-time validation, input sanitization\nimport { SignJWT, jwtVerify } from 'jose';\nimport { v4 as uuidv4 } from 'uuid';\nimport type { SessionData, SessionConfig, SessionValidation, SessionJWTPayload } from '../types';\n\n// Maximum articles per session to prevent unbounded growth\nconst MAX_ARTICLES_PER_SESSION = 100;\n\n// Minimum secret length for security\nconst MIN_SECRET_LENGTH = 32;\n\n/**\n * Get the secret key for JWT signing\n * SECURITY: Enforces minimum secret length\n */\nfunction getSecretKey(secret: string): Uint8Array {\n if (!secret || typeof secret !== 'string') {\n throw new Error('Session secret is required');\n }\n if (secret.length < MIN_SECRET_LENGTH) {\n throw new Error(`Session secret must be at least ${MIN_SECRET_LENGTH} characters`);\n }\n return new TextEncoder().encode(secret);\n}\n\n/**\n * Validate wallet address format (base58, 32-44 chars)\n * SECURITY: Prevents injection via wallet address field\n */\nfunction validateWalletAddress(address: string): boolean {\n if (!address || typeof address !== 'string') return false;\n // Solana addresses are base58, typically 32-44 characters\n const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\n return base58Regex.test(address);\n}\n\n/**\n * Validate article ID format\n * SECURITY: Prevents injection via articleId field\n */\nfunction validateArticleId(articleId: string): boolean {\n if (!articleId || typeof articleId !== 'string') return false;\n // Allow alphanumeric, hyphens, underscores, max 128 chars\n if (articleId.length > 128) return false;\n const safeIdRegex = /^[a-zA-Z0-9_-]+$/;\n return safeIdRegex.test(articleId);\n}\n\n/**\n * Create a new session after successful payment\n * SECURITY: Validates inputs, enforces limits\n */\nexport async function createSession(\n walletAddress: string,\n articleId: string,\n config: SessionConfig,\n siteWide: boolean = false\n): Promise<{ token: string; session: SessionData }> {\n // Input validation\n if (!validateWalletAddress(walletAddress)) {\n throw new Error('Invalid wallet address format');\n }\n if (!validateArticleId(articleId)) {\n throw new Error('Invalid article ID format');\n }\n if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 720) {\n throw new Error('Session duration must be between 1 and 720 hours');\n }\n\n const sessionId = uuidv4();\n const now = Math.floor(Date.now() / 1000);\n const expiresAt = now + (config.durationHours * 3600);\n\n const session: SessionData = {\n id: sessionId,\n walletAddress,\n unlockedArticles: [articleId],\n siteWideUnlock: Boolean(siteWide),\n createdAt: now,\n expiresAt,\n };\n\n const payload: SessionJWTPayload = {\n sub: walletAddress,\n sid: sessionId,\n articles: session.unlockedArticles,\n siteWide: session.siteWideUnlock,\n iat: now,\n exp: expiresAt,\n };\n\n const token = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(`${config.durationHours}h`)\n .sign(getSecretKey(config.secret));\n\n return { token, session };\n}\n\n/**\n * Validate an existing session token\n * SECURITY: jose library handles timing-safe comparison internally\n */\nexport async function validateSession(\n token: string,\n secret: string\n): Promise<SessionValidation> {\n // Input validation\n if (!token || typeof token !== 'string') {\n return { valid: false, reason: 'Invalid token format' };\n }\n\n try {\n const { payload } = await jwtVerify(token, getSecretKey(secret));\n const sessionPayload = payload as unknown as SessionJWTPayload;\n\n // Validate required fields exist\n if (!sessionPayload.sub || !sessionPayload.sid || !sessionPayload.exp) {\n return { valid: false, reason: 'Malformed session payload' };\n }\n\n // Check expiration (jose already checks, but double-check)\n const now = Math.floor(Date.now() / 1000);\n if (sessionPayload.exp < now) {\n return { valid: false, reason: 'Session expired' };\n }\n\n // Validate wallet address format from token\n if (!validateWalletAddress(sessionPayload.sub)) {\n return { valid: false, reason: 'Invalid session data' };\n }\n\n const session: SessionData = {\n id: sessionPayload.sid,\n walletAddress: sessionPayload.sub,\n unlockedArticles: Array.isArray(sessionPayload.articles) ? sessionPayload.articles : [],\n siteWideUnlock: Boolean(sessionPayload.siteWide),\n createdAt: sessionPayload.iat ?? 0,\n expiresAt: sessionPayload.exp,\n };\n\n return { valid: true, session };\n } catch (error) {\n // SECURITY: Don't expose internal error details\n return { valid: false, reason: 'Invalid session' };\n }\n}\n\n/**\n * Add an article to an existing session\n * SECURITY: Enforces article limit to prevent token bloat\n */\nexport async function addArticleToSession(\n token: string,\n articleId: string,\n secret: string\n): Promise<{ token: string; session: SessionData } | null> {\n // Validate article ID\n if (!validateArticleId(articleId)) {\n return null;\n }\n\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return null;\n }\n\n const session = validation.session;\n\n // Already unlocked\n if (session.unlockedArticles.includes(articleId)) {\n return { token, session };\n }\n\n // SECURITY: Enforce maximum articles per session\n if (session.unlockedArticles.length >= MAX_ARTICLES_PER_SESSION) {\n return null;\n }\n\n const updatedArticles = [...session.unlockedArticles, articleId];\n\n const payload: SessionJWTPayload = {\n sub: session.walletAddress,\n sid: session.id,\n articles: updatedArticles,\n siteWide: session.siteWideUnlock,\n iat: session.createdAt,\n exp: session.expiresAt,\n };\n\n const newToken = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(getSecretKey(secret));\n\n return {\n token: newToken,\n session: { ...session, unlockedArticles: updatedArticles },\n };\n}\n\n/**\n * Check if an article is unlocked for a session\n */\nexport async function isArticleUnlocked(\n token: string,\n articleId: string,\n secret: string\n): Promise<boolean> {\n if (!validateArticleId(articleId)) {\n return false;\n }\n\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return false;\n }\n\n if (validation.session.siteWideUnlock) {\n return true;\n }\n\n return validation.session.unlockedArticles.includes(articleId);\n}\n"]}
@@ -45,27 +45,4 @@ interface SessionJWTPayload {
45
45
  exp: number;
46
46
  }
47
47
 
48
- /**
49
- * Create a new session after successful payment
50
- */
51
- declare function createSession(walletAddress: string, articleId: string, config: SessionConfig, siteWide?: boolean): Promise<{
52
- token: string;
53
- session: SessionData;
54
- }>;
55
- /**
56
- * Validate an existing session token
57
- */
58
- declare function validateSession(token: string, secret: string): Promise<SessionValidation>;
59
- /**
60
- * Add an article to an existing session
61
- */
62
- declare function addArticleToSession(token: string, articleId: string, secret: string): Promise<{
63
- token: string;
64
- session: SessionData;
65
- } | null>;
66
- /**
67
- * Check if an article is unlocked for a session
68
- */
69
- declare function isArticleUnlocked(token: string, articleId: string, secret: string): Promise<boolean>;
70
-
71
- export { type SessionData as S, type SessionConfig as a, type SessionValidation as b, type SessionJWTPayload as c, createSession as d, addArticleToSession as e, isArticleUnlocked as i, validateSession as v };
48
+ export type { SessionData as S, SessionConfig as a, SessionValidation as b, SessionJWTPayload as c };
@@ -45,27 +45,4 @@ interface SessionJWTPayload {
45
45
  exp: number;
46
46
  }
47
47
 
48
- /**
49
- * Create a new session after successful payment
50
- */
51
- declare function createSession(walletAddress: string, articleId: string, config: SessionConfig, siteWide?: boolean): Promise<{
52
- token: string;
53
- session: SessionData;
54
- }>;
55
- /**
56
- * Validate an existing session token
57
- */
58
- declare function validateSession(token: string, secret: string): Promise<SessionValidation>;
59
- /**
60
- * Add an article to an existing session
61
- */
62
- declare function addArticleToSession(token: string, articleId: string, secret: string): Promise<{
63
- token: string;
64
- session: SessionData;
65
- } | null>;
66
- /**
67
- * Check if an article is unlocked for a session
68
- */
69
- declare function isArticleUnlocked(token: string, articleId: string, secret: string): Promise<boolean>;
70
-
71
- export { type SessionData as S, type SessionConfig as a, type SessionValidation as b, type SessionJWTPayload as c, createSession as d, addArticleToSession as e, isArticleUnlocked as i, validateSession as v };
48
+ export type { SessionData as S, SessionConfig as a, SessionValidation as b, SessionJWTPayload as c };
@@ -42,6 +42,16 @@ function isMainnet(network) {
42
42
  function toX402Network(network) {
43
43
  return network === "mainnet-beta" ? "solana-mainnet" : "solana-devnet";
44
44
  }
45
+ var SIGNATURE_REGEX = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;
46
+ var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
47
+ function isValidSignature(signature) {
48
+ if (!signature || typeof signature !== "string") return false;
49
+ return SIGNATURE_REGEX.test(signature);
50
+ }
51
+ function isValidWalletAddress(address) {
52
+ if (!address || typeof address !== "string") return false;
53
+ return WALLET_REGEX.test(address);
54
+ }
45
55
  function parseSOLTransfer(transaction, expectedRecipient) {
46
56
  const instructions = transaction.transaction.message.instructions;
47
57
  for (const ix of instructions) {
@@ -82,6 +92,16 @@ async function verifyPayment(params) {
82
92
  maxAgeSeconds = 300,
83
93
  clientConfig
84
94
  } = params;
95
+ if (!isValidSignature(signature)) {
96
+ return { valid: false, confirmed: false, signature, error: "Invalid signature format" };
97
+ }
98
+ if (!isValidWalletAddress(expectedRecipient)) {
99
+ return { valid: false, confirmed: false, signature, error: "Invalid recipient address" };
100
+ }
101
+ if (expectedAmount <= 0n) {
102
+ return { valid: false, confirmed: false, signature, error: "Invalid expected amount" };
103
+ }
104
+ const effectiveMaxAge = Math.min(Math.max(maxAgeSeconds, 60), 3600);
85
105
  const connection = getConnection(clientConfig);
86
106
  try {
87
107
  const transaction = await connection.getParsedTransaction(signature, {
@@ -96,14 +116,17 @@ async function verifyPayment(params) {
96
116
  valid: false,
97
117
  confirmed: true,
98
118
  signature,
99
- error: `Transaction failed: ${JSON.stringify(transaction.meta.err)}`
119
+ error: "Transaction failed on-chain"
100
120
  };
101
121
  }
102
122
  if (transaction.blockTime) {
103
123
  const now = Math.floor(Date.now() / 1e3);
104
- if (now - transaction.blockTime > maxAgeSeconds) {
124
+ if (now - transaction.blockTime > effectiveMaxAge) {
105
125
  return { valid: false, confirmed: true, signature, error: "Transaction too old" };
106
126
  }
127
+ if (transaction.blockTime > now + 60) {
128
+ return { valid: false, confirmed: true, signature, error: "Invalid transaction time" };
129
+ }
107
130
  }
108
131
  const transferDetails = parseSOLTransfer(transaction, expectedRecipient);
109
132
  if (!transferDetails) {
@@ -122,7 +145,7 @@ async function verifyPayment(params) {
122
145
  from: transferDetails.from,
123
146
  to: transferDetails.to,
124
147
  amount: transferDetails.amount,
125
- error: `Insufficient amount: expected ${expectedAmount}, got ${transferDetails.amount}`
148
+ error: "Insufficient payment amount"
126
149
  };
127
150
  }
128
151
  return {
@@ -140,33 +163,34 @@ async function verifyPayment(params) {
140
163
  valid: false,
141
164
  confirmed: false,
142
165
  signature,
143
- error: error instanceof Error ? error.message : "Unknown verification error"
166
+ error: "Verification failed"
144
167
  };
145
168
  }
146
169
  }
147
170
  async function waitForConfirmation(signature, clientConfig) {
171
+ if (!isValidSignature(signature)) {
172
+ return { confirmed: false, error: "Invalid signature format" };
173
+ }
148
174
  const connection = getConnection(clientConfig);
149
175
  try {
150
176
  const confirmation = await connection.confirmTransaction(signature, "confirmed");
151
177
  if (confirmation.value.err) {
152
- return {
153
- confirmed: false,
154
- error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`
155
- };
178
+ return { confirmed: false, error: "Transaction failed" };
156
179
  }
157
180
  return { confirmed: true, slot: confirmation.context?.slot };
158
- } catch (error) {
159
- return {
160
- confirmed: false,
161
- error: error instanceof Error ? error.message : "Confirmation timeout"
162
- };
181
+ } catch {
182
+ return { confirmed: false, error: "Confirmation timeout" };
163
183
  }
164
184
  }
165
185
  async function getWalletTransactions(walletAddress, clientConfig, limit = 20) {
186
+ if (!isValidWalletAddress(walletAddress)) {
187
+ return [];
188
+ }
189
+ const safeLimit = Math.min(Math.max(limit, 1), 100);
166
190
  const connection = getConnection(clientConfig);
167
- const pubkey = new web3_js.PublicKey(walletAddress);
168
191
  try {
169
- const signatures = await connection.getSignaturesForAddress(pubkey, { limit });
192
+ const pubkey = new web3_js.PublicKey(walletAddress);
193
+ const signatures = await connection.getSignaturesForAddress(pubkey, { limit: safeLimit });
170
194
  return signatures.map((sig) => ({
171
195
  signature: sig.signature,
172
196
  blockTime: sig.blockTime ?? void 0,
@@ -180,17 +204,213 @@ function lamportsToSol(lamports) {
180
204
  return Number(lamports) / web3_js.LAMPORTS_PER_SOL;
181
205
  }
182
206
  function solToLamports(sol) {
207
+ if (!Number.isFinite(sol) || sol < 0) {
208
+ throw new Error("Invalid SOL amount");
209
+ }
183
210
  return BigInt(Math.floor(sol * web3_js.LAMPORTS_PER_SOL));
184
211
  }
185
212
 
213
+ // src/types/payment.ts
214
+ var TOKEN_MINTS = {
215
+ /** USDC on mainnet */
216
+ USDC_MAINNET: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
217
+ /** USDC on devnet */
218
+ USDC_DEVNET: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
219
+ /** USDT on mainnet */
220
+ USDT_MAINNET: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
221
+ };
222
+
223
+ // src/solana/spl.ts
224
+ var SIGNATURE_REGEX2 = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;
225
+ var WALLET_REGEX2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
226
+ function resolveMintAddress(asset, network) {
227
+ if (asset === "native") return null;
228
+ if (asset === "usdc") {
229
+ return network === "mainnet-beta" ? TOKEN_MINTS.USDC_MAINNET : TOKEN_MINTS.USDC_DEVNET;
230
+ }
231
+ if (asset === "usdt") {
232
+ return TOKEN_MINTS.USDT_MAINNET;
233
+ }
234
+ if (typeof asset === "object" && "mint" in asset) {
235
+ return asset.mint;
236
+ }
237
+ return null;
238
+ }
239
+ function getTokenDecimals(asset) {
240
+ if (asset === "native") return 9;
241
+ if (asset === "usdc" || asset === "usdt") return 6;
242
+ if (typeof asset === "object" && "decimals" in asset) {
243
+ return asset.decimals ?? 6;
244
+ }
245
+ return 6;
246
+ }
247
+ function parseSPLTransfer(transaction, expectedRecipient, expectedMint) {
248
+ const instructions = transaction.transaction.message.instructions;
249
+ for (const ix of instructions) {
250
+ if ("parsed" in ix && (ix.program === "spl-token" || ix.program === "spl-token-2022")) {
251
+ const parsed = ix.parsed;
252
+ if (parsed.type === "transfer" || parsed.type === "transferChecked") {
253
+ const amount = parsed.info.amount || parsed.info.tokenAmount?.amount;
254
+ if (amount && parsed.info.destination) {
255
+ return {
256
+ from: parsed.info.authority || parsed.info.source || "",
257
+ to: parsed.info.destination,
258
+ amount: BigInt(amount),
259
+ mint: parsed.info.mint || expectedMint
260
+ };
261
+ }
262
+ }
263
+ }
264
+ }
265
+ if (transaction.meta?.innerInstructions) {
266
+ for (const inner of transaction.meta.innerInstructions) {
267
+ for (const ix of inner.instructions) {
268
+ if ("parsed" in ix && (ix.program === "spl-token" || ix.program === "spl-token-2022")) {
269
+ const parsed = ix.parsed;
270
+ if (parsed.type === "transfer" || parsed.type === "transferChecked") {
271
+ const amount = parsed.info.amount || parsed.info.tokenAmount?.amount;
272
+ if (amount) {
273
+ return {
274
+ from: parsed.info.authority || parsed.info.source || "",
275
+ to: parsed.info.destination || "",
276
+ amount: BigInt(amount),
277
+ mint: parsed.info.mint || expectedMint
278
+ };
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }
285
+ if (transaction.meta?.postTokenBalances && transaction.meta?.preTokenBalances) {
286
+ const preBalances = transaction.meta.preTokenBalances;
287
+ const postBalances = transaction.meta.postTokenBalances;
288
+ for (const post of postBalances) {
289
+ if (post.mint === expectedMint && post.owner === expectedRecipient) {
290
+ const pre = preBalances.find(
291
+ (p) => p.accountIndex === post.accountIndex
292
+ );
293
+ const preAmount = BigInt(pre?.uiTokenAmount?.amount || "0");
294
+ const postAmount = BigInt(post.uiTokenAmount?.amount || "0");
295
+ const transferred = postAmount - preAmount;
296
+ if (transferred > 0n) {
297
+ return {
298
+ from: "",
299
+ // Can't determine from balance changes
300
+ to: expectedRecipient,
301
+ amount: transferred,
302
+ mint: expectedMint
303
+ };
304
+ }
305
+ }
306
+ }
307
+ }
308
+ return null;
309
+ }
310
+ async function verifySPLPayment(params) {
311
+ const {
312
+ signature,
313
+ expectedRecipient,
314
+ expectedAmount,
315
+ asset,
316
+ clientConfig,
317
+ maxAgeSeconds = 300
318
+ } = params;
319
+ if (!SIGNATURE_REGEX2.test(signature)) {
320
+ return { valid: false, confirmed: false, signature, error: "Invalid signature format" };
321
+ }
322
+ if (!WALLET_REGEX2.test(expectedRecipient)) {
323
+ return { valid: false, confirmed: false, signature, error: "Invalid recipient address" };
324
+ }
325
+ const mintAddress = resolveMintAddress(asset, clientConfig.network);
326
+ if (!mintAddress) {
327
+ return { valid: false, confirmed: false, signature, error: "Invalid asset configuration" };
328
+ }
329
+ if (expectedAmount <= 0n) {
330
+ return { valid: false, confirmed: false, signature, error: "Invalid expected amount" };
331
+ }
332
+ const effectiveMaxAge = Math.min(Math.max(maxAgeSeconds, 60), 3600);
333
+ const connection = getConnection(clientConfig);
334
+ try {
335
+ const transaction = await connection.getParsedTransaction(signature, {
336
+ commitment: "confirmed",
337
+ maxSupportedTransactionVersion: 0
338
+ });
339
+ if (!transaction) {
340
+ return { valid: false, confirmed: false, signature, error: "Transaction not found" };
341
+ }
342
+ if (transaction.meta?.err) {
343
+ return { valid: false, confirmed: true, signature, error: "Transaction failed on-chain" };
344
+ }
345
+ if (transaction.blockTime) {
346
+ const now = Math.floor(Date.now() / 1e3);
347
+ if (now - transaction.blockTime > effectiveMaxAge) {
348
+ return { valid: false, confirmed: true, signature, error: "Transaction too old" };
349
+ }
350
+ if (transaction.blockTime > now + 60) {
351
+ return { valid: false, confirmed: true, signature, error: "Invalid transaction time" };
352
+ }
353
+ }
354
+ const transfer = parseSPLTransfer(transaction, expectedRecipient, mintAddress);
355
+ if (!transfer) {
356
+ return {
357
+ valid: false,
358
+ confirmed: true,
359
+ signature,
360
+ error: "No valid token transfer to recipient found"
361
+ };
362
+ }
363
+ if (transfer.mint !== mintAddress) {
364
+ return {
365
+ valid: false,
366
+ confirmed: true,
367
+ signature,
368
+ error: "Token mint mismatch"
369
+ };
370
+ }
371
+ if (transfer.amount < expectedAmount) {
372
+ return {
373
+ valid: false,
374
+ confirmed: true,
375
+ signature,
376
+ from: transfer.from,
377
+ to: transfer.to,
378
+ mint: transfer.mint,
379
+ amount: transfer.amount,
380
+ error: "Insufficient payment amount"
381
+ };
382
+ }
383
+ return {
384
+ valid: true,
385
+ confirmed: true,
386
+ signature,
387
+ from: transfer.from,
388
+ to: transfer.to,
389
+ mint: transfer.mint,
390
+ amount: transfer.amount,
391
+ blockTime: transaction.blockTime ?? void 0,
392
+ slot: transaction.slot
393
+ };
394
+ } catch {
395
+ return { valid: false, confirmed: false, signature, error: "Verification failed" };
396
+ }
397
+ }
398
+ function isNativeAsset(asset) {
399
+ return asset === "native";
400
+ }
401
+
186
402
  exports.getConnection = getConnection;
403
+ exports.getTokenDecimals = getTokenDecimals;
187
404
  exports.getWalletTransactions = getWalletTransactions;
188
405
  exports.isMainnet = isMainnet;
406
+ exports.isNativeAsset = isNativeAsset;
189
407
  exports.lamportsToSol = lamportsToSol;
190
408
  exports.resetConnection = resetConnection;
409
+ exports.resolveMintAddress = resolveMintAddress;
191
410
  exports.solToLamports = solToLamports;
192
411
  exports.toX402Network = toX402Network;
193
412
  exports.verifyPayment = verifyPayment;
413
+ exports.verifySPLPayment = verifySPLPayment;
194
414
  exports.waitForConfirmation = waitForConfirmation;
195
415
  //# sourceMappingURL=index.cjs.map
196
416
  //# sourceMappingURL=index.cjs.map