@alleyboss/micropay-solana-x402-paywall 1.0.1 → 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 (62) 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 +624 -6
  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 +604 -7
  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.d.cts +29 -1
  35. package/dist/session/index.d.ts +29 -1
  36. package/dist/{index-uxMb72hH.d.cts → session-D2IoWAWV.d.cts} +1 -27
  37. package/dist/{index-uxMb72hH.d.ts → session-D2IoWAWV.d.ts} +1 -27
  38. package/dist/solana/index.cjs +193 -0
  39. package/dist/solana/index.cjs.map +1 -1
  40. package/dist/solana/index.d.cts +60 -3
  41. package/dist/solana/index.d.ts +60 -3
  42. package/dist/solana/index.js +190 -1
  43. package/dist/solana/index.js.map +1 -1
  44. package/dist/store/index.cjs +99 -0
  45. package/dist/store/index.cjs.map +1 -0
  46. package/dist/store/index.d.cts +38 -0
  47. package/dist/store/index.d.ts +38 -0
  48. package/dist/store/index.js +96 -0
  49. package/dist/store/index.js.map +1 -0
  50. package/dist/utils/index.cjs +68 -0
  51. package/dist/utils/index.cjs.map +1 -0
  52. package/dist/utils/index.d.cts +30 -0
  53. package/dist/utils/index.d.ts +30 -0
  54. package/dist/utils/index.js +65 -0
  55. package/dist/utils/index.js.map +1 -0
  56. package/dist/x402/index.cjs +2 -1
  57. package/dist/x402/index.cjs.map +1 -1
  58. package/dist/x402/index.d.cts +2 -1
  59. package/dist/x402/index.d.ts +2 -1
  60. package/dist/x402/index.js +2 -1
  61. package/dist/x402/index.js.map +1 -1
  62. package/package.json +56 -3
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ // src/store/memory.ts
4
+ function createMemoryStore(options = {}) {
5
+ const { cleanupInterval = 6e4 } = options;
6
+ const store = /* @__PURE__ */ new Map();
7
+ const cleanupTimer = setInterval(() => {
8
+ const now = Date.now();
9
+ for (const [key, record] of store.entries()) {
10
+ if (record.expiresAt < now) {
11
+ store.delete(key);
12
+ }
13
+ }
14
+ }, cleanupInterval);
15
+ return {
16
+ async hasBeenUsed(signature) {
17
+ const record = store.get(signature);
18
+ if (!record) return false;
19
+ if (record.expiresAt < Date.now()) {
20
+ store.delete(signature);
21
+ return false;
22
+ }
23
+ return true;
24
+ },
25
+ async markAsUsed(signature, resourceId, expiresAt) {
26
+ store.set(signature, {
27
+ resourceId,
28
+ usedAt: Date.now(),
29
+ expiresAt: expiresAt.getTime()
30
+ });
31
+ },
32
+ async getUsage(signature) {
33
+ const record = store.get(signature);
34
+ if (!record) return null;
35
+ if (record.expiresAt < Date.now()) {
36
+ store.delete(signature);
37
+ return null;
38
+ }
39
+ return {
40
+ signature,
41
+ resourceId: record.resourceId,
42
+ usedAt: new Date(record.usedAt),
43
+ expiresAt: new Date(record.expiresAt),
44
+ walletAddress: record.walletAddress
45
+ };
46
+ },
47
+ /** Stop cleanup timer (for graceful shutdown) */
48
+ close() {
49
+ clearInterval(cleanupTimer);
50
+ store.clear();
51
+ }
52
+ };
53
+ }
54
+
55
+ // src/store/redis.ts
56
+ function createRedisStore(options) {
57
+ const { client, keyPrefix = "micropay:sig:" } = options;
58
+ const buildKey = (signature) => `${keyPrefix}${signature}`;
59
+ return {
60
+ async hasBeenUsed(signature) {
61
+ const exists = await client.exists(buildKey(signature));
62
+ return exists > 0;
63
+ },
64
+ async markAsUsed(signature, resourceId, expiresAt) {
65
+ const key = buildKey(signature);
66
+ const ttl = Math.max(1, Math.floor((expiresAt.getTime() - Date.now()) / 1e3));
67
+ const record = {
68
+ signature,
69
+ resourceId,
70
+ usedAt: /* @__PURE__ */ new Date(),
71
+ expiresAt
72
+ };
73
+ if (client.setex) {
74
+ await client.setex(key, ttl, JSON.stringify(record));
75
+ } else {
76
+ await client.set(key, JSON.stringify(record), { EX: ttl });
77
+ }
78
+ },
79
+ async getUsage(signature) {
80
+ const data = await client.get(buildKey(signature));
81
+ if (!data) return null;
82
+ try {
83
+ const record = JSON.parse(data);
84
+ return {
85
+ ...record,
86
+ usedAt: new Date(record.usedAt),
87
+ expiresAt: new Date(record.expiresAt)
88
+ };
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+ };
94
+ }
95
+
96
+ exports.createMemoryStore = createMemoryStore;
97
+ exports.createRedisStore = createRedisStore;
98
+ //# sourceMappingURL=index.cjs.map
99
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/store/memory.ts","../../src/store/redis.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// In-Memory Signature Store\n// For development, testing, and single-instance deployments\n\n/** Signature usage record */\nexport interface SignatureUsage {\n signature: string;\n resourceId: string;\n usedAt: Date;\n expiresAt: Date;\n walletAddress?: string;\n}\n\n/** Interface for tracking payment signature usage */\nexport interface SignatureStore {\n hasBeenUsed(signature: string): Promise<boolean>;\n markAsUsed(signature: string, resourceId: string, expiresAt: Date): Promise<void>;\n getUsage?(signature: string): Promise<SignatureUsage | null>;\n}\n\nexport interface MemoryStoreOptions {\n /** Default TTL in seconds */\n defaultTTL?: number;\n /** Cleanup interval in ms (default: 60000) */\n cleanupInterval?: number;\n}\n\ninterface StoredRecord {\n resourceId: string;\n usedAt: number;\n expiresAt: number;\n walletAddress?: string;\n}\n\n/**\n * Create an in-memory signature store\n * ⚠️ Not suitable for multi-instance deployments (use Redis instead)\n */\nexport function createMemoryStore(options: MemoryStoreOptions = {}): SignatureStore & { close: () => void } {\n const { cleanupInterval = 60000 } = options;\n const store = new Map<string, StoredRecord>();\n\n // Periodic cleanup of expired entries\n const cleanupTimer = setInterval(() => {\n const now = Date.now();\n for (const [key, record] of store.entries()) {\n if (record.expiresAt < now) {\n store.delete(key);\n }\n }\n }, cleanupInterval);\n\n return {\n async hasBeenUsed(signature: string): Promise<boolean> {\n const record = store.get(signature);\n if (!record) return false;\n\n // Check if expired\n if (record.expiresAt < Date.now()) {\n store.delete(signature);\n return false;\n }\n\n return true;\n },\n\n async markAsUsed(signature: string, resourceId: string, expiresAt: Date): Promise<void> {\n store.set(signature, {\n resourceId,\n usedAt: Date.now(),\n expiresAt: expiresAt.getTime(),\n });\n },\n\n async getUsage(signature: string): Promise<SignatureUsage | null> {\n const record = store.get(signature);\n if (!record) return null;\n\n // Check expiration\n if (record.expiresAt < Date.now()) {\n store.delete(signature);\n return null;\n }\n\n return {\n signature,\n resourceId: record.resourceId,\n usedAt: new Date(record.usedAt),\n expiresAt: new Date(record.expiresAt),\n walletAddress: record.walletAddress,\n };\n },\n\n /** Stop cleanup timer (for graceful shutdown) */\n close(): void {\n clearInterval(cleanupTimer);\n store.clear();\n },\n };\n}\n","// Redis Signature Store Adapter\n// For production multi-instance deployments\n\nimport type { SignatureStore, SignatureUsage } from './memory';\n\n/**\n * Minimal Redis client interface\n * Compatible with ioredis, redis, and similar clients\n */\nexport interface RedisClient {\n get(key: string): Promise<string | null>;\n set(key: string, value: string, options?: { EX?: number }): Promise<string | null>;\n setex?(key: string, seconds: number, value: string): Promise<string>;\n exists(key: string): Promise<number>;\n del(key: string): Promise<number>;\n}\n\nexport interface RedisStoreOptions {\n /** Redis client instance */\n client: RedisClient;\n /** Key prefix */\n keyPrefix?: string;\n /** Default TTL in seconds */\n defaultTTL?: number;\n}\n\n/**\n * Create a Redis-backed signature store\n * Production-ready for distributed deployments\n */\nexport function createRedisStore(options: RedisStoreOptions): SignatureStore {\n const { client, keyPrefix = 'micropay:sig:' } = options;\n\n const buildKey = (signature: string) => `${keyPrefix}${signature}`;\n\n return {\n async hasBeenUsed(signature: string): Promise<boolean> {\n const exists = await client.exists(buildKey(signature));\n return exists > 0;\n },\n\n async markAsUsed(signature: string, resourceId: string, expiresAt: Date): Promise<void> {\n const key = buildKey(signature);\n const ttl = Math.max(1, Math.floor((expiresAt.getTime() - Date.now()) / 1000));\n\n const record: SignatureUsage = {\n signature,\n resourceId,\n usedAt: new Date(),\n expiresAt,\n };\n\n // Support both `setex` (ioredis) and `set` with EX (node-redis)\n if (client.setex) {\n await client.setex(key, ttl, JSON.stringify(record));\n } else {\n await client.set(key, JSON.stringify(record), { EX: ttl });\n }\n },\n\n async getUsage(signature: string): Promise<SignatureUsage | null> {\n const data = await client.get(buildKey(signature));\n if (!data) return null;\n\n try {\n const record = JSON.parse(data) as SignatureUsage;\n // Convert date strings back to Date objects\n return {\n ...record,\n usedAt: new Date(record.usedAt),\n expiresAt: new Date(record.expiresAt),\n };\n } catch {\n return null;\n }\n },\n };\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import { S as SignatureStore } from '../memory-Daxkczti.cjs';
2
+ export { M as MemoryStoreOptions, a as SignatureUsage, c as createMemoryStore } from '../memory-Daxkczti.cjs';
3
+
4
+ /**
5
+ * Minimal Redis client interface
6
+ * Compatible with ioredis, redis, and similar clients
7
+ */
8
+ interface RedisClient {
9
+ get(key: string): Promise<string | null>;
10
+ set(key: string, value: string, options?: {
11
+ EX?: number;
12
+ }): Promise<string | null>;
13
+ setex?(key: string, seconds: number, value: string): Promise<string>;
14
+ exists(key: string): Promise<number>;
15
+ del(key: string): Promise<number>;
16
+ }
17
+ interface RedisStoreOptions {
18
+ /** Redis client instance */
19
+ client: RedisClient;
20
+ /** Key prefix */
21
+ keyPrefix?: string;
22
+ /** Default TTL in seconds */
23
+ defaultTTL?: number;
24
+ }
25
+ /**
26
+ * Create a Redis-backed signature store
27
+ * Production-ready for distributed deployments
28
+ */
29
+ declare function createRedisStore(options: RedisStoreOptions): SignatureStore;
30
+
31
+ interface StoreConfig {
32
+ /** Default TTL in seconds for signature records */
33
+ defaultTTL?: number;
34
+ /** Prefix for keys (useful for Redis) */
35
+ keyPrefix?: string;
36
+ }
37
+
38
+ export { type RedisClient, type RedisStoreOptions, SignatureStore, type StoreConfig, createRedisStore };
@@ -0,0 +1,38 @@
1
+ import { S as SignatureStore } from '../memory-Daxkczti.js';
2
+ export { M as MemoryStoreOptions, a as SignatureUsage, c as createMemoryStore } from '../memory-Daxkczti.js';
3
+
4
+ /**
5
+ * Minimal Redis client interface
6
+ * Compatible with ioredis, redis, and similar clients
7
+ */
8
+ interface RedisClient {
9
+ get(key: string): Promise<string | null>;
10
+ set(key: string, value: string, options?: {
11
+ EX?: number;
12
+ }): Promise<string | null>;
13
+ setex?(key: string, seconds: number, value: string): Promise<string>;
14
+ exists(key: string): Promise<number>;
15
+ del(key: string): Promise<number>;
16
+ }
17
+ interface RedisStoreOptions {
18
+ /** Redis client instance */
19
+ client: RedisClient;
20
+ /** Key prefix */
21
+ keyPrefix?: string;
22
+ /** Default TTL in seconds */
23
+ defaultTTL?: number;
24
+ }
25
+ /**
26
+ * Create a Redis-backed signature store
27
+ * Production-ready for distributed deployments
28
+ */
29
+ declare function createRedisStore(options: RedisStoreOptions): SignatureStore;
30
+
31
+ interface StoreConfig {
32
+ /** Default TTL in seconds for signature records */
33
+ defaultTTL?: number;
34
+ /** Prefix for keys (useful for Redis) */
35
+ keyPrefix?: string;
36
+ }
37
+
38
+ export { type RedisClient, type RedisStoreOptions, SignatureStore, type StoreConfig, createRedisStore };
@@ -0,0 +1,96 @@
1
+ // src/store/memory.ts
2
+ function createMemoryStore(options = {}) {
3
+ const { cleanupInterval = 6e4 } = options;
4
+ const store = /* @__PURE__ */ new Map();
5
+ const cleanupTimer = setInterval(() => {
6
+ const now = Date.now();
7
+ for (const [key, record] of store.entries()) {
8
+ if (record.expiresAt < now) {
9
+ store.delete(key);
10
+ }
11
+ }
12
+ }, cleanupInterval);
13
+ return {
14
+ async hasBeenUsed(signature) {
15
+ const record = store.get(signature);
16
+ if (!record) return false;
17
+ if (record.expiresAt < Date.now()) {
18
+ store.delete(signature);
19
+ return false;
20
+ }
21
+ return true;
22
+ },
23
+ async markAsUsed(signature, resourceId, expiresAt) {
24
+ store.set(signature, {
25
+ resourceId,
26
+ usedAt: Date.now(),
27
+ expiresAt: expiresAt.getTime()
28
+ });
29
+ },
30
+ async getUsage(signature) {
31
+ const record = store.get(signature);
32
+ if (!record) return null;
33
+ if (record.expiresAt < Date.now()) {
34
+ store.delete(signature);
35
+ return null;
36
+ }
37
+ return {
38
+ signature,
39
+ resourceId: record.resourceId,
40
+ usedAt: new Date(record.usedAt),
41
+ expiresAt: new Date(record.expiresAt),
42
+ walletAddress: record.walletAddress
43
+ };
44
+ },
45
+ /** Stop cleanup timer (for graceful shutdown) */
46
+ close() {
47
+ clearInterval(cleanupTimer);
48
+ store.clear();
49
+ }
50
+ };
51
+ }
52
+
53
+ // src/store/redis.ts
54
+ function createRedisStore(options) {
55
+ const { client, keyPrefix = "micropay:sig:" } = options;
56
+ const buildKey = (signature) => `${keyPrefix}${signature}`;
57
+ return {
58
+ async hasBeenUsed(signature) {
59
+ const exists = await client.exists(buildKey(signature));
60
+ return exists > 0;
61
+ },
62
+ async markAsUsed(signature, resourceId, expiresAt) {
63
+ const key = buildKey(signature);
64
+ const ttl = Math.max(1, Math.floor((expiresAt.getTime() - Date.now()) / 1e3));
65
+ const record = {
66
+ signature,
67
+ resourceId,
68
+ usedAt: /* @__PURE__ */ new Date(),
69
+ expiresAt
70
+ };
71
+ if (client.setex) {
72
+ await client.setex(key, ttl, JSON.stringify(record));
73
+ } else {
74
+ await client.set(key, JSON.stringify(record), { EX: ttl });
75
+ }
76
+ },
77
+ async getUsage(signature) {
78
+ const data = await client.get(buildKey(signature));
79
+ if (!data) return null;
80
+ try {
81
+ const record = JSON.parse(data);
82
+ return {
83
+ ...record,
84
+ usedAt: new Date(record.usedAt),
85
+ expiresAt: new Date(record.expiresAt)
86
+ };
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
91
+ };
92
+ }
93
+
94
+ export { createMemoryStore, createRedisStore };
95
+ //# sourceMappingURL=index.js.map
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/store/memory.ts","../../src/store/redis.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// In-Memory Signature Store\n// For development, testing, and single-instance deployments\n\n/** Signature usage record */\nexport interface SignatureUsage {\n signature: string;\n resourceId: string;\n usedAt: Date;\n expiresAt: Date;\n walletAddress?: string;\n}\n\n/** Interface for tracking payment signature usage */\nexport interface SignatureStore {\n hasBeenUsed(signature: string): Promise<boolean>;\n markAsUsed(signature: string, resourceId: string, expiresAt: Date): Promise<void>;\n getUsage?(signature: string): Promise<SignatureUsage | null>;\n}\n\nexport interface MemoryStoreOptions {\n /** Default TTL in seconds */\n defaultTTL?: number;\n /** Cleanup interval in ms (default: 60000) */\n cleanupInterval?: number;\n}\n\ninterface StoredRecord {\n resourceId: string;\n usedAt: number;\n expiresAt: number;\n walletAddress?: string;\n}\n\n/**\n * Create an in-memory signature store\n * ⚠️ Not suitable for multi-instance deployments (use Redis instead)\n */\nexport function createMemoryStore(options: MemoryStoreOptions = {}): SignatureStore & { close: () => void } {\n const { cleanupInterval = 60000 } = options;\n const store = new Map<string, StoredRecord>();\n\n // Periodic cleanup of expired entries\n const cleanupTimer = setInterval(() => {\n const now = Date.now();\n for (const [key, record] of store.entries()) {\n if (record.expiresAt < now) {\n store.delete(key);\n }\n }\n }, cleanupInterval);\n\n return {\n async hasBeenUsed(signature: string): Promise<boolean> {\n const record = store.get(signature);\n if (!record) return false;\n\n // Check if expired\n if (record.expiresAt < Date.now()) {\n store.delete(signature);\n return false;\n }\n\n return true;\n },\n\n async markAsUsed(signature: string, resourceId: string, expiresAt: Date): Promise<void> {\n store.set(signature, {\n resourceId,\n usedAt: Date.now(),\n expiresAt: expiresAt.getTime(),\n });\n },\n\n async getUsage(signature: string): Promise<SignatureUsage | null> {\n const record = store.get(signature);\n if (!record) return null;\n\n // Check expiration\n if (record.expiresAt < Date.now()) {\n store.delete(signature);\n return null;\n }\n\n return {\n signature,\n resourceId: record.resourceId,\n usedAt: new Date(record.usedAt),\n expiresAt: new Date(record.expiresAt),\n walletAddress: record.walletAddress,\n };\n },\n\n /** Stop cleanup timer (for graceful shutdown) */\n close(): void {\n clearInterval(cleanupTimer);\n store.clear();\n },\n };\n}\n","// Redis Signature Store Adapter\n// For production multi-instance deployments\n\nimport type { SignatureStore, SignatureUsage } from './memory';\n\n/**\n * Minimal Redis client interface\n * Compatible with ioredis, redis, and similar clients\n */\nexport interface RedisClient {\n get(key: string): Promise<string | null>;\n set(key: string, value: string, options?: { EX?: number }): Promise<string | null>;\n setex?(key: string, seconds: number, value: string): Promise<string>;\n exists(key: string): Promise<number>;\n del(key: string): Promise<number>;\n}\n\nexport interface RedisStoreOptions {\n /** Redis client instance */\n client: RedisClient;\n /** Key prefix */\n keyPrefix?: string;\n /** Default TTL in seconds */\n defaultTTL?: number;\n}\n\n/**\n * Create a Redis-backed signature store\n * Production-ready for distributed deployments\n */\nexport function createRedisStore(options: RedisStoreOptions): SignatureStore {\n const { client, keyPrefix = 'micropay:sig:' } = options;\n\n const buildKey = (signature: string) => `${keyPrefix}${signature}`;\n\n return {\n async hasBeenUsed(signature: string): Promise<boolean> {\n const exists = await client.exists(buildKey(signature));\n return exists > 0;\n },\n\n async markAsUsed(signature: string, resourceId: string, expiresAt: Date): Promise<void> {\n const key = buildKey(signature);\n const ttl = Math.max(1, Math.floor((expiresAt.getTime() - Date.now()) / 1000));\n\n const record: SignatureUsage = {\n signature,\n resourceId,\n usedAt: new Date(),\n expiresAt,\n };\n\n // Support both `setex` (ioredis) and `set` with EX (node-redis)\n if (client.setex) {\n await client.setex(key, ttl, JSON.stringify(record));\n } else {\n await client.set(key, JSON.stringify(record), { EX: ttl });\n }\n },\n\n async getUsage(signature: string): Promise<SignatureUsage | null> {\n const data = await client.get(buildKey(signature));\n if (!data) return null;\n\n try {\n const record = JSON.parse(data) as SignatureUsage;\n // Convert date strings back to Date objects\n return {\n ...record,\n usedAt: new Date(record.usedAt),\n expiresAt: new Date(record.expiresAt),\n };\n } catch {\n return null;\n }\n },\n };\n}\n"]}
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ // src/utils/retry.ts
4
+ function sleep(ms) {
5
+ return new Promise((resolve) => setTimeout(resolve, ms));
6
+ }
7
+ function calculateDelay(attempt, options) {
8
+ const { baseDelay, maxDelay, jitter } = options;
9
+ let delay = baseDelay * Math.pow(2, attempt);
10
+ delay = Math.min(delay, maxDelay);
11
+ if (jitter) {
12
+ const jitterAmount = delay * 0.25;
13
+ delay += Math.random() * jitterAmount * 2 - jitterAmount;
14
+ }
15
+ return Math.floor(delay);
16
+ }
17
+ async function withRetry(fn, options = {}) {
18
+ const {
19
+ maxAttempts = 3,
20
+ baseDelay = 500,
21
+ maxDelay = 1e4,
22
+ jitter = true,
23
+ retryOn = () => true
24
+ } = options;
25
+ let lastError;
26
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
27
+ try {
28
+ return await fn();
29
+ } catch (error) {
30
+ lastError = error;
31
+ if (!retryOn(error)) {
32
+ throw error;
33
+ }
34
+ if (attempt < maxAttempts - 1) {
35
+ const delay = calculateDelay(attempt, {
36
+ baseDelay,
37
+ maxDelay,
38
+ jitter
39
+ });
40
+ await sleep(delay);
41
+ }
42
+ }
43
+ }
44
+ throw lastError;
45
+ }
46
+ function isRetryableRPCError(error) {
47
+ if (error instanceof Error) {
48
+ const message = error.message.toLowerCase();
49
+ if (message.includes("429") || message.includes("rate limit")) {
50
+ return true;
51
+ }
52
+ if (message.includes("timeout") || message.includes("econnreset")) {
53
+ return true;
54
+ }
55
+ if (message.includes("503") || message.includes("502") || message.includes("500")) {
56
+ return true;
57
+ }
58
+ if (message.includes("blockhash not found") || message.includes("slot skipped")) {
59
+ return true;
60
+ }
61
+ }
62
+ return false;
63
+ }
64
+
65
+ exports.isRetryableRPCError = isRetryableRPCError;
66
+ exports.withRetry = withRetry;
67
+ //# sourceMappingURL=index.cjs.map
68
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/retry.ts"],"names":[],"mappings":";;;AAmBA,SAAS,MAAM,EAAA,EAA2B;AACtC,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AACzD;AAKA,SAAS,cAAA,CAAe,SAAiB,OAAA,EAA0D;AAC/F,EAAA,MAAM,EAAE,SAAA,EAAW,QAAA,EAAU,MAAA,EAAO,GAAI,OAAA;AAGxC,EAAA,IAAI,KAAA,GAAQ,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,GAAG,OAAO,CAAA;AAG3C,EAAA,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,QAAQ,CAAA;AAGhC,EAAA,IAAI,MAAA,EAAQ;AACR,IAAA,MAAM,eAAe,KAAA,GAAQ,IAAA;AAC7B,IAAA,KAAA,IAAU,IAAA,CAAK,MAAA,EAAO,GAAI,YAAA,GAAe,CAAA,GAAK,YAAA;AAAA,EAClD;AAEA,EAAA,OAAO,IAAA,CAAK,MAAM,KAAK,CAAA;AAC3B;AAaA,eAAsB,SAAA,CAClB,EAAA,EACA,OAAA,GAAwB,EAAC,EACf;AACV,EAAA,MAAM;AAAA,IACF,WAAA,GAAc,CAAA;AAAA,IACd,SAAA,GAAY,GAAA;AAAA,IACZ,QAAA,GAAW,GAAA;AAAA,IACX,MAAA,GAAS,IAAA;AAAA,IACT,UAAU,MAAM;AAAA,GACpB,GAAI,OAAA;AAEJ,EAAA,IAAI,SAAA;AAEJ,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,GAAU,WAAA,EAAa,OAAA,EAAA,EAAW;AACpD,IAAA,IAAI;AACA,MAAA,OAAO,MAAM,EAAA,EAAG;AAAA,IACpB,SAAS,KAAA,EAAO;AACZ,MAAA,SAAA,GAAY,KAAA;AAGZ,MAAA,IAAI,CAAC,OAAA,CAAQ,KAAK,CAAA,EAAG;AACjB,QAAA,MAAM,KAAA;AAAA,MACV;AAGA,MAAA,IAAI,OAAA,GAAU,cAAc,CAAA,EAAG;AAC3B,QAAA,MAAM,KAAA,GAAQ,eAAe,OAAA,EAAS;AAAA,UAElC,SAAA;AAAA,UACA,QAAA;AAAA,UACA;AAAA,SACH,CAAA;AACD,QAAA,MAAM,MAAM,KAAK,CAAA;AAAA,MACrB;AAAA,IACJ;AAAA,EACJ;AAEA,EAAA,MAAM,SAAA;AACV;AAKO,SAAS,oBAAoB,KAAA,EAAyB;AACzD,EAAA,IAAI,iBAAiB,KAAA,EAAO;AACxB,IAAA,MAAM,OAAA,GAAU,KAAA,CAAM,OAAA,CAAQ,WAAA,EAAY;AAG1C,IAAA,IAAI,QAAQ,QAAA,CAAS,KAAK,KAAK,OAAA,CAAQ,QAAA,CAAS,YAAY,CAAA,EAAG;AAC3D,MAAA,OAAO,IAAA;AAAA,IACX;AAGA,IAAA,IAAI,QAAQ,QAAA,CAAS,SAAS,KAAK,OAAA,CAAQ,QAAA,CAAS,YAAY,CAAA,EAAG;AAC/D,MAAA,OAAO,IAAA;AAAA,IACX;AAGA,IAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA,IAAK,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA,IAAK,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA,EAAG;AAC/E,MAAA,OAAO,IAAA;AAAA,IACX;AAGA,IAAA,IAAI,QAAQ,QAAA,CAAS,qBAAqB,KAAK,OAAA,CAAQ,QAAA,CAAS,cAAc,CAAA,EAAG;AAC7E,MAAA,OAAO,IAAA;AAAA,IACX;AAAA,EACJ;AAEA,EAAA,OAAO,KAAA;AACX","file":"index.cjs","sourcesContent":["// Utility functions\n// Retry logic with exponential backoff for RPC resilience\n\nexport interface RetryOptions {\n /** Maximum number of attempts */\n maxAttempts?: number;\n /** Base delay in milliseconds */\n baseDelay?: number;\n /** Maximum delay in milliseconds */\n maxDelay?: number;\n /** Whether to add jitter to delay */\n jitter?: boolean;\n /** Errors to retry on (default: all) */\n retryOn?: (error: unknown) => boolean;\n}\n\n/**\n * Sleep for a given duration\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Calculate delay with exponential backoff and optional jitter\n */\nfunction calculateDelay(attempt: number, options: Required<Omit<RetryOptions, 'retryOn'>>): number {\n const { baseDelay, maxDelay, jitter } = options;\n\n // Exponential backoff: baseDelay * 2^attempt\n let delay = baseDelay * Math.pow(2, attempt);\n\n // Cap at maxDelay\n delay = Math.min(delay, maxDelay);\n\n // Add jitter (±25%)\n if (jitter) {\n const jitterAmount = delay * 0.25;\n delay += (Math.random() * jitterAmount * 2) - jitterAmount;\n }\n\n return Math.floor(delay);\n}\n\n/**\n * Execute a function with retry logic and exponential backoff\n * \n * @example\n * ```typescript\n * const result = await withRetry(\n * () => connection.getBalance(publicKey),\n * { maxAttempts: 3, baseDelay: 500 }\n * );\n * ```\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: RetryOptions = {}\n): Promise<T> {\n const {\n maxAttempts = 3,\n baseDelay = 500,\n maxDelay = 10000,\n jitter = true,\n retryOn = () => true,\n } = options;\n\n let lastError: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n // Check if we should retry this error\n if (!retryOn(error)) {\n throw error;\n }\n\n // Don't sleep after the last attempt\n if (attempt < maxAttempts - 1) {\n const delay = calculateDelay(attempt, {\n maxAttempts,\n baseDelay,\n maxDelay,\n jitter\n });\n await sleep(delay);\n }\n }\n }\n\n throw lastError;\n}\n\n/**\n * Check if error is a transient RPC error that should be retried\n */\nexport function isRetryableRPCError(error: unknown): boolean {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n\n // Rate limiting\n if (message.includes('429') || message.includes('rate limit')) {\n return true;\n }\n\n // Network errors\n if (message.includes('timeout') || message.includes('econnreset')) {\n return true;\n }\n\n // Server errors\n if (message.includes('503') || message.includes('502') || message.includes('500')) {\n return true;\n }\n\n // Solana-specific transient errors\n if (message.includes('blockhash not found') || message.includes('slot skipped')) {\n return true;\n }\n }\n\n return false;\n}\n"]}
@@ -0,0 +1,30 @@
1
+ interface RetryOptions {
2
+ /** Maximum number of attempts */
3
+ maxAttempts?: number;
4
+ /** Base delay in milliseconds */
5
+ baseDelay?: number;
6
+ /** Maximum delay in milliseconds */
7
+ maxDelay?: number;
8
+ /** Whether to add jitter to delay */
9
+ jitter?: boolean;
10
+ /** Errors to retry on (default: all) */
11
+ retryOn?: (error: unknown) => boolean;
12
+ }
13
+ /**
14
+ * Execute a function with retry logic and exponential backoff
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const result = await withRetry(
19
+ * () => connection.getBalance(publicKey),
20
+ * { maxAttempts: 3, baseDelay: 500 }
21
+ * );
22
+ * ```
23
+ */
24
+ declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
25
+ /**
26
+ * Check if error is a transient RPC error that should be retried
27
+ */
28
+ declare function isRetryableRPCError(error: unknown): boolean;
29
+
30
+ export { type RetryOptions, isRetryableRPCError, withRetry };
@@ -0,0 +1,30 @@
1
+ interface RetryOptions {
2
+ /** Maximum number of attempts */
3
+ maxAttempts?: number;
4
+ /** Base delay in milliseconds */
5
+ baseDelay?: number;
6
+ /** Maximum delay in milliseconds */
7
+ maxDelay?: number;
8
+ /** Whether to add jitter to delay */
9
+ jitter?: boolean;
10
+ /** Errors to retry on (default: all) */
11
+ retryOn?: (error: unknown) => boolean;
12
+ }
13
+ /**
14
+ * Execute a function with retry logic and exponential backoff
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const result = await withRetry(
19
+ * () => connection.getBalance(publicKey),
20
+ * { maxAttempts: 3, baseDelay: 500 }
21
+ * );
22
+ * ```
23
+ */
24
+ declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
25
+ /**
26
+ * Check if error is a transient RPC error that should be retried
27
+ */
28
+ declare function isRetryableRPCError(error: unknown): boolean;
29
+
30
+ export { type RetryOptions, isRetryableRPCError, withRetry };
@@ -0,0 +1,65 @@
1
+ // src/utils/retry.ts
2
+ function sleep(ms) {
3
+ return new Promise((resolve) => setTimeout(resolve, ms));
4
+ }
5
+ function calculateDelay(attempt, options) {
6
+ const { baseDelay, maxDelay, jitter } = options;
7
+ let delay = baseDelay * Math.pow(2, attempt);
8
+ delay = Math.min(delay, maxDelay);
9
+ if (jitter) {
10
+ const jitterAmount = delay * 0.25;
11
+ delay += Math.random() * jitterAmount * 2 - jitterAmount;
12
+ }
13
+ return Math.floor(delay);
14
+ }
15
+ async function withRetry(fn, options = {}) {
16
+ const {
17
+ maxAttempts = 3,
18
+ baseDelay = 500,
19
+ maxDelay = 1e4,
20
+ jitter = true,
21
+ retryOn = () => true
22
+ } = options;
23
+ let lastError;
24
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
25
+ try {
26
+ return await fn();
27
+ } catch (error) {
28
+ lastError = error;
29
+ if (!retryOn(error)) {
30
+ throw error;
31
+ }
32
+ if (attempt < maxAttempts - 1) {
33
+ const delay = calculateDelay(attempt, {
34
+ baseDelay,
35
+ maxDelay,
36
+ jitter
37
+ });
38
+ await sleep(delay);
39
+ }
40
+ }
41
+ }
42
+ throw lastError;
43
+ }
44
+ function isRetryableRPCError(error) {
45
+ if (error instanceof Error) {
46
+ const message = error.message.toLowerCase();
47
+ if (message.includes("429") || message.includes("rate limit")) {
48
+ return true;
49
+ }
50
+ if (message.includes("timeout") || message.includes("econnreset")) {
51
+ return true;
52
+ }
53
+ if (message.includes("503") || message.includes("502") || message.includes("500")) {
54
+ return true;
55
+ }
56
+ if (message.includes("blockhash not found") || message.includes("slot skipped")) {
57
+ return true;
58
+ }
59
+ }
60
+ return false;
61
+ }
62
+
63
+ export { isRetryableRPCError, withRetry };
64
+ //# sourceMappingURL=index.js.map
65
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/retry.ts"],"names":[],"mappings":";AAmBA,SAAS,MAAM,EAAA,EAA2B;AACtC,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AACzD;AAKA,SAAS,cAAA,CAAe,SAAiB,OAAA,EAA0D;AAC/F,EAAA,MAAM,EAAE,SAAA,EAAW,QAAA,EAAU,MAAA,EAAO,GAAI,OAAA;AAGxC,EAAA,IAAI,KAAA,GAAQ,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,GAAG,OAAO,CAAA;AAG3C,EAAA,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,QAAQ,CAAA;AAGhC,EAAA,IAAI,MAAA,EAAQ;AACR,IAAA,MAAM,eAAe,KAAA,GAAQ,IAAA;AAC7B,IAAA,KAAA,IAAU,IAAA,CAAK,MAAA,EAAO,GAAI,YAAA,GAAe,CAAA,GAAK,YAAA;AAAA,EAClD;AAEA,EAAA,OAAO,IAAA,CAAK,MAAM,KAAK,CAAA;AAC3B;AAaA,eAAsB,SAAA,CAClB,EAAA,EACA,OAAA,GAAwB,EAAC,EACf;AACV,EAAA,MAAM;AAAA,IACF,WAAA,GAAc,CAAA;AAAA,IACd,SAAA,GAAY,GAAA;AAAA,IACZ,QAAA,GAAW,GAAA;AAAA,IACX,MAAA,GAAS,IAAA;AAAA,IACT,UAAU,MAAM;AAAA,GACpB,GAAI,OAAA;AAEJ,EAAA,IAAI,SAAA;AAEJ,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,GAAU,WAAA,EAAa,OAAA,EAAA,EAAW;AACpD,IAAA,IAAI;AACA,MAAA,OAAO,MAAM,EAAA,EAAG;AAAA,IACpB,SAAS,KAAA,EAAO;AACZ,MAAA,SAAA,GAAY,KAAA;AAGZ,MAAA,IAAI,CAAC,OAAA,CAAQ,KAAK,CAAA,EAAG;AACjB,QAAA,MAAM,KAAA;AAAA,MACV;AAGA,MAAA,IAAI,OAAA,GAAU,cAAc,CAAA,EAAG;AAC3B,QAAA,MAAM,KAAA,GAAQ,eAAe,OAAA,EAAS;AAAA,UAElC,SAAA;AAAA,UACA,QAAA;AAAA,UACA;AAAA,SACH,CAAA;AACD,QAAA,MAAM,MAAM,KAAK,CAAA;AAAA,MACrB;AAAA,IACJ;AAAA,EACJ;AAEA,EAAA,MAAM,SAAA;AACV;AAKO,SAAS,oBAAoB,KAAA,EAAyB;AACzD,EAAA,IAAI,iBAAiB,KAAA,EAAO;AACxB,IAAA,MAAM,OAAA,GAAU,KAAA,CAAM,OAAA,CAAQ,WAAA,EAAY;AAG1C,IAAA,IAAI,QAAQ,QAAA,CAAS,KAAK,KAAK,OAAA,CAAQ,QAAA,CAAS,YAAY,CAAA,EAAG;AAC3D,MAAA,OAAO,IAAA;AAAA,IACX;AAGA,IAAA,IAAI,QAAQ,QAAA,CAAS,SAAS,KAAK,OAAA,CAAQ,QAAA,CAAS,YAAY,CAAA,EAAG;AAC/D,MAAA,OAAO,IAAA;AAAA,IACX;AAGA,IAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA,IAAK,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA,IAAK,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA,EAAG;AAC/E,MAAA,OAAO,IAAA;AAAA,IACX;AAGA,IAAA,IAAI,QAAQ,QAAA,CAAS,qBAAqB,KAAK,OAAA,CAAQ,QAAA,CAAS,cAAc,CAAA,EAAG;AAC7E,MAAA,OAAO,IAAA;AAAA,IACX;AAAA,EACJ;AAEA,EAAA,OAAO,KAAA;AACX","file":"index.js","sourcesContent":["// Utility functions\n// Retry logic with exponential backoff for RPC resilience\n\nexport interface RetryOptions {\n /** Maximum number of attempts */\n maxAttempts?: number;\n /** Base delay in milliseconds */\n baseDelay?: number;\n /** Maximum delay in milliseconds */\n maxDelay?: number;\n /** Whether to add jitter to delay */\n jitter?: boolean;\n /** Errors to retry on (default: all) */\n retryOn?: (error: unknown) => boolean;\n}\n\n/**\n * Sleep for a given duration\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Calculate delay with exponential backoff and optional jitter\n */\nfunction calculateDelay(attempt: number, options: Required<Omit<RetryOptions, 'retryOn'>>): number {\n const { baseDelay, maxDelay, jitter } = options;\n\n // Exponential backoff: baseDelay * 2^attempt\n let delay = baseDelay * Math.pow(2, attempt);\n\n // Cap at maxDelay\n delay = Math.min(delay, maxDelay);\n\n // Add jitter (±25%)\n if (jitter) {\n const jitterAmount = delay * 0.25;\n delay += (Math.random() * jitterAmount * 2) - jitterAmount;\n }\n\n return Math.floor(delay);\n}\n\n/**\n * Execute a function with retry logic and exponential backoff\n * \n * @example\n * ```typescript\n * const result = await withRetry(\n * () => connection.getBalance(publicKey),\n * { maxAttempts: 3, baseDelay: 500 }\n * );\n * ```\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: RetryOptions = {}\n): Promise<T> {\n const {\n maxAttempts = 3,\n baseDelay = 500,\n maxDelay = 10000,\n jitter = true,\n retryOn = () => true,\n } = options;\n\n let lastError: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n // Check if we should retry this error\n if (!retryOn(error)) {\n throw error;\n }\n\n // Don't sleep after the last attempt\n if (attempt < maxAttempts - 1) {\n const delay = calculateDelay(attempt, {\n maxAttempts,\n baseDelay,\n maxDelay,\n jitter\n });\n await sleep(delay);\n }\n }\n }\n\n throw lastError;\n}\n\n/**\n * Check if error is a transient RPC error that should be retried\n */\nexport function isRetryableRPCError(error: unknown): boolean {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n\n // Rate limiting\n if (message.includes('429') || message.includes('rate limit')) {\n return true;\n }\n\n // Network errors\n if (message.includes('timeout') || message.includes('econnreset')) {\n return true;\n }\n\n // Server errors\n if (message.includes('503') || message.includes('502') || message.includes('500')) {\n return true;\n }\n\n // Solana-specific transient errors\n if (message.includes('blockhash not found') || message.includes('slot skipped')) {\n return true;\n }\n }\n\n return false;\n}\n"]}
@@ -234,12 +234,13 @@ var X402_HEADERS = {
234
234
  PAYMENT_RESPONSE: "X-Payment-Response"
235
235
  };
236
236
  function create402ResponseBody(requirement) {
237
+ const assetStr = typeof requirement.asset === "string" ? requirement.asset : requirement.asset.mint;
237
238
  return {
238
239
  error: "Payment Required",
239
240
  message: requirement.description,
240
241
  price: {
241
242
  amount: requirement.maxAmountRequired,
242
- asset: requirement.asset,
243
+ asset: assetStr,
243
244
  network: requirement.network
244
245
  }
245
246
  };