@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.
- package/README.md +100 -167
- package/dist/client/index.cjs +99 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +112 -0
- package/dist/client/index.d.ts +112 -0
- package/dist/client/index.js +95 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client-CSZHI8o8.d.ts +32 -0
- package/dist/client-vRr48m2x.d.cts +32 -0
- package/dist/index.cjs +624 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +604 -7
- package/dist/index.js.map +1 -1
- package/dist/memory-Daxkczti.d.cts +29 -0
- package/dist/memory-Daxkczti.d.ts +29 -0
- package/dist/middleware/index.cjs +261 -0
- package/dist/middleware/index.cjs.map +1 -0
- package/dist/middleware/index.d.cts +90 -0
- package/dist/middleware/index.d.ts +90 -0
- package/dist/middleware/index.js +255 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/nextjs-BK0pVb9Y.d.ts +78 -0
- package/dist/nextjs-Bm272Jkj.d.cts +78 -0
- package/dist/{client-kfCr7G-P.d.cts → payment-CTxdtqmc.d.cts} +23 -34
- package/dist/{client-kfCr7G-P.d.ts → payment-CTxdtqmc.d.ts} +23 -34
- package/dist/pricing/index.cjs +79 -0
- package/dist/pricing/index.cjs.map +1 -0
- package/dist/pricing/index.d.cts +67 -0
- package/dist/pricing/index.d.ts +67 -0
- package/dist/pricing/index.js +72 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/session/index.d.cts +29 -1
- package/dist/session/index.d.ts +29 -1
- package/dist/{index-uxMb72hH.d.cts → session-D2IoWAWV.d.cts} +1 -27
- package/dist/{index-uxMb72hH.d.ts → session-D2IoWAWV.d.ts} +1 -27
- package/dist/solana/index.cjs +193 -0
- package/dist/solana/index.cjs.map +1 -1
- package/dist/solana/index.d.cts +60 -3
- package/dist/solana/index.d.ts +60 -3
- package/dist/solana/index.js +190 -1
- package/dist/solana/index.js.map +1 -1
- package/dist/store/index.cjs +99 -0
- package/dist/store/index.cjs.map +1 -0
- package/dist/store/index.d.cts +38 -0
- package/dist/store/index.d.ts +38 -0
- package/dist/store/index.js +96 -0
- package/dist/store/index.js.map +1 -0
- package/dist/utils/index.cjs +68 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +30 -0
- package/dist/utils/index.d.ts +30 -0
- package/dist/utils/index.js +65 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/x402/index.cjs +2 -1
- package/dist/x402/index.cjs.map +1 -1
- package/dist/x402/index.d.cts +2 -1
- package/dist/x402/index.d.ts +2 -1
- package/dist/x402/index.js +2 -1
- package/dist/x402/index.js.map +1 -1
- 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":";;;AAqCO,SAAS,iBAAA,CAAkB,OAAA,GAA8B,EAAC,EAA2C;AACxG,EAAA,MAAM,EAAE,eAAA,GAAkB,GAAA,EAAM,GAAI,OAAA;AACpC,EAAA,MAAM,KAAA,uBAAY,GAAA,EAA0B;AAG5C,EAAA,MAAM,YAAA,GAAe,YAAY,MAAM;AACnC,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,MAAM,CAAA,IAAK,KAAA,CAAM,SAAQ,EAAG;AACzC,MAAA,IAAI,MAAA,CAAO,YAAY,GAAA,EAAK;AACxB,QAAA,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,MACpB;AAAA,IACJ;AAAA,EACJ,GAAG,eAAe,CAAA;AAElB,EAAA,OAAO;AAAA,IACH,MAAM,YAAY,SAAA,EAAqC;AACnD,MAAA,MAAM,MAAA,GAAS,KAAA,CAAM,GAAA,CAAI,SAAS,CAAA;AAClC,MAAA,IAAI,CAAC,QAAQ,OAAO,KAAA;AAGpB,MAAA,IAAI,MAAA,CAAO,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,EAAG;AAC/B,QAAA,KAAA,CAAM,OAAO,SAAS,CAAA;AACtB,QAAA,OAAO,KAAA;AAAA,MACX;AAEA,MAAA,OAAO,IAAA;AAAA,IACX,CAAA;AAAA,IAEA,MAAM,UAAA,CAAW,SAAA,EAAmB,UAAA,EAAoB,SAAA,EAAgC;AACpF,MAAA,KAAA,CAAM,IAAI,SAAA,EAAW;AAAA,QACjB,UAAA;AAAA,QACA,MAAA,EAAQ,KAAK,GAAA,EAAI;AAAA,QACjB,SAAA,EAAW,UAAU,OAAA;AAAQ,OAChC,CAAA;AAAA,IACL,CAAA;AAAA,IAEA,MAAM,SAAS,SAAA,EAAmD;AAC9D,MAAA,MAAM,MAAA,GAAS,KAAA,CAAM,GAAA,CAAI,SAAS,CAAA;AAClC,MAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AAGpB,MAAA,IAAI,MAAA,CAAO,SAAA,GAAY,IAAA,CAAK,GAAA,EAAI,EAAG;AAC/B,QAAA,KAAA,CAAM,OAAO,SAAS,CAAA;AACtB,QAAA,OAAO,IAAA;AAAA,MACX;AAEA,MAAA,OAAO;AAAA,QACH,SAAA;AAAA,QACA,YAAY,MAAA,CAAO,UAAA;AAAA,QACnB,MAAA,EAAQ,IAAI,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAAA,QAC9B,SAAA,EAAW,IAAI,IAAA,CAAK,MAAA,CAAO,SAAS,CAAA;AAAA,QACpC,eAAe,MAAA,CAAO;AAAA,OAC1B;AAAA,IACJ,CAAA;AAAA;AAAA,IAGA,KAAA,GAAc;AACV,MAAA,aAAA,CAAc,YAAY,CAAA;AAC1B,MAAA,KAAA,CAAM,KAAA,EAAM;AAAA,IAChB;AAAA,GACJ;AACJ;;;ACpEO,SAAS,iBAAiB,OAAA,EAA4C;AACzE,EAAA,MAAM,EAAE,MAAA,EAAQ,SAAA,GAAY,eAAA,EAAgB,GAAI,OAAA;AAEhD,EAAA,MAAM,WAAW,CAAC,SAAA,KAAsB,CAAA,EAAG,SAAS,GAAG,SAAS,CAAA,CAAA;AAEhE,EAAA,OAAO;AAAA,IACH,MAAM,YAAY,SAAA,EAAqC;AACnD,MAAA,MAAM,SAAS,MAAM,MAAA,CAAO,MAAA,CAAO,QAAA,CAAS,SAAS,CAAC,CAAA;AACtD,MAAA,OAAO,MAAA,GAAS,CAAA;AAAA,IACpB,CAAA;AAAA,IAEA,MAAM,UAAA,CAAW,SAAA,EAAmB,UAAA,EAAoB,SAAA,EAAgC;AACpF,MAAA,MAAM,GAAA,GAAM,SAAS,SAAS,CAAA;AAC9B,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAA,CAAO,SAAA,CAAU,OAAA,EAAQ,GAAI,IAAA,CAAK,GAAA,EAAI,IAAK,GAAI,CAAC,CAAA;AAE7E,MAAA,MAAM,MAAA,GAAyB;AAAA,QAC3B,SAAA;AAAA,QACA,UAAA;AAAA,QACA,MAAA,sBAAY,IAAA,EAAK;AAAA,QACjB;AAAA,OACJ;AAGA,MAAA,IAAI,OAAO,KAAA,EAAO;AACd,QAAA,MAAM,OAAO,KAAA,CAAM,GAAA,EAAK,KAAK,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA;AAAA,MACvD,CAAA,MAAO;AACH,QAAA,MAAM,MAAA,CAAO,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA,EAAG,EAAE,EAAA,EAAI,GAAA,EAAK,CAAA;AAAA,MAC7D;AAAA,IACJ,CAAA;AAAA,IAEA,MAAM,SAAS,SAAA,EAAmD;AAC9D,MAAA,MAAM,OAAO,MAAM,MAAA,CAAO,GAAA,CAAI,QAAA,CAAS,SAAS,CAAC,CAAA;AACjD,MAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAElB,MAAA,IAAI;AACA,QAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAE9B,QAAA,OAAO;AAAA,UACH,GAAG,MAAA;AAAA,UACH,MAAA,EAAQ,IAAI,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAAA,UAC9B,SAAA,EAAW,IAAI,IAAA,CAAK,MAAA,CAAO,SAAS;AAAA,SACxC;AAAA,MACJ,CAAA,CAAA,MAAQ;AACJ,QAAA,OAAO,IAAA;AAAA,MACX;AAAA,IACJ;AAAA,GACJ;AACJ","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"]}
|
package/dist/x402/index.cjs
CHANGED
|
@@ -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:
|
|
243
|
+
asset: assetStr,
|
|
243
244
|
network: requirement.network
|
|
244
245
|
}
|
|
245
246
|
};
|